可繪項目總覽

試試 Compose
Jetpack Compose 是 Android 推薦的 UI 工具包。瞭解如何在 Compose 中顯示圖形。

如要在應用程式中顯示靜態圖片,可以使用 Drawable 類別及其子類別繪製形狀和圖片。Drawable 是「可繪製的項目」的一般抽象概念。各個子類別可協助處理特定圖片情境,您也可以擴充這些子類別,定義以獨特方式運作的可繪項目物件。

除了使用類別建構函式,您還可以透過下列兩種方式定義及例項化 Drawable

  • 將專案中儲存的圖片資源 (點陣圖檔案) 擴充。
  • 擴充定義可繪項目屬性的 XML 資源。

注意: 您可能比較偏好使用向量可繪項目,這類項目會以一組點、線和曲線定義圖片,並提供相關色彩資訊。這樣一來,向量可繪項目就能縮放成不同大小,而不會降低畫質。詳情請參閱「向量可繪項目總覽」。

從資源圖片建立可繪項目

您可以從專案資源參照圖片檔案,將圖片新增至應用程式。支援的檔案類型包括 PNG (建議)、JPG (可接受) 和 GIF (不建議)。應用程式圖示、標誌和其他圖像 (例如遊戲中使用的圖像) 都很適合採用這項技巧。

如要使用圖片資源,請將檔案新增至專案的 res/drawable/ 目錄。圖片資源加入專案後,您就可以從程式碼或 XML 版面配置參照該資源。無論是哪種方式,系統都會使用資源 ID 參照檔案,也就是不含副檔名的檔案名稱。舉例來說,請將 my_image.png 稱為 my_image

注意:建構程序期間,aapt 工具可能會自動對 res/drawable/ 目錄中的圖片資源進行無失真壓縮,以達到最佳化效果。舉例來說,真實色彩 PNG 如果需要的顏色不超過 256 種顏色,也許可以利用調色盤轉換成 8 位元 PNG。這種方式產生的圖片品質相同,但所需的記憶體較少。因此,此目錄中的圖片二進位檔可能會在建構期間發生變化。如果是為了要將圖片轉換成點陣圖,而想要以位元串流讀取圖片,請改為將圖片存放在 res/raw/ 資料夾中,這樣 aapt 工具才不會修改圖片。

下列程式碼片段示範如何建構 ImageView,使用從可繪製資源建立的圖片,並將圖片新增至版面配置:

Kotlin

private lateinit var constraintLayout: ConstraintLayout

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Instantiate an ImageView and define its properties
    val i = ImageView(this).apply {
        setImageResource(R.drawable.my_image)
        contentDescription = resources.getString(R.string.my_image_desc)

        // set the ImageView bounds to match the Drawable's dimensions
        adjustViewBounds = true
        layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT)
    }

    // Create a ConstraintLayout in which to add the ImageView
    constraintLayout = ConstraintLayout(this).apply {

        // Add the ImageView to the layout.
        addView(i)
    }

    // Set the layout as the content view.
    setContentView(constraintLayout)
}

Java

ConstraintLayout constraintLayout;

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  // Create a ConstraintLayout in which to add the ImageView
  constraintLayout = new ConstraintLayout(this);

  // Instantiate an ImageView and define its properties
  ImageView i = new ImageView(this);
  i.setImageResource(R.drawable.my_image);
  i.setContentDescription(getResources().getString(R.string.my_image_desc));

  // set the ImageView bounds to match the Drawable's dimensions
  i.setAdjustViewBounds(true);
  i.setLayoutParams(new ViewGroup.LayoutParams(
          ViewGroup.LayoutParams.WRAP_CONTENT,
          ViewGroup.LayoutParams.WRAP_CONTENT));

  // Add the ImageView to the layout and set the layout as the content view.
  constraintLayout.addView(i);
  setContentView(constraintLayout);
}

在其他情況下,您可能需要將圖片資源做為 Drawable 物件處理,如下列範例所示:

Kotlin

val myImage: Drawable = ResourcesCompat.getDrawable(context.resources, R.drawable.my_image, null)

Java

Resources res = context.getResources();
Drawable myImage = ResourcesCompat.getDrawable(res, R.drawable.my_image, null);

警告:專案中的每個專屬資源只能維持一種狀態,無論您為該資源例項化多少不同的物件,舉例來說,如果您從同一個圖片資源例項化兩個 Drawable 物件,並變更其中一個物件的屬性 (例如 Alpha),另一個物件也會受到影響。處理圖片資源的多個執行個體時,您應執行補間動畫,而非直接轉換 Drawable 物件。

以下 XML 程式碼片段說明如何在 XML 版面配置中,將可繪製資源新增至 ImageView

<ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/my_image"
        android:contentDescription="@string/my_image_desc" />

如要進一步瞭解如何使用專案資源,請參閱「資源和素材資源」。

注意:使用圖片資源做為可繪項目的來源時,請務必確保圖片大小適合各種像素密度。如果圖片不正確,系統會放大圖片以符合大小,這可能會導致可繪項目出現影像失真。 詳情請參閱「支援不同的像素密度」。

從 XML 資源建立可繪項目

如果想建立的 Drawable 物件一開始並非取決於程式碼或使用者互動定義的變數,那麼在 XML 中定義 Drawable 是不錯的選擇。即使您預期 Drawable 會在使用者與應用程式互動時變更屬性,也應考慮在 XML 中定義物件,因為您可以在物件例項化後修改屬性。

使用 XML 定義 Drawable 後,請將檔案儲存到專案的 res/drawable/ 目錄中。以下範例顯示定義 TransitionDrawable 資源的 XML,該資源會從 Drawable 繼承:

<!-- res/drawable/expand_collapse.xml -->
<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/image_expand"/>
    <item android:drawable="@drawable/image_collapse"/>
</transition>

然後,呼叫 Resources#getDrawable() 並傳遞 XML 檔案的資源 ID,即可擷取及例項化物件。任何支援 inflate() 方法的 Drawable 子類別都可以在 XML 中定義,並由應用程式例項化。

支援 XML 膨脹的每個可繪項目類別,都會使用特定 XML 屬性來定義物件屬性。下列程式碼會將 TransitionDrawable 例項化,並設為 ImageView 物件的內容:

Kotlin

val transition= ResourcesCompat.getDrawable(
        context.resources,
        R.drawable.expand_collapse,
        null
) as TransitionDrawable

val image: ImageView = findViewById(R.id.toggle_image)
image.setImageDrawable(transition)

// Description of the initial state that the drawable represents.
image.contentDescription = resources.getString(R.string.collapsed)

// Then you can call the TransitionDrawable object's methods.
transition.startTransition(1000)

// After the transition is complete, change the image's content description
// to reflect the new state.

Java

Resources res = context.getResources();
TransitionDrawable transition =
    (TransitionDrawable) ResourcesCompat.getDrawable(res, R.drawable.expand_collapse, null);

ImageView image = (ImageView) findViewById(R.id.toggle_image);
image.setImageDrawable(transition);

// Description of the initial state that the drawable represents.
image.setContentDescription(getResources().getString(R.string.collapsed));

// Then you can call the TransitionDrawable object's methods.
transition.startTransition(1000);

// After the transition is complete, change the image's content description
// to reflect the new state.

如要進一步瞭解支援的 XML 屬性,請參閱上方列出的類別。

形狀可繪項目

如要動態繪製二維圖像,ShapeDrawable 物件是不錯的選擇。您可以在 ShapeDrawable 物件上以程式輔助方式繪製基本圖形,並套用應用程式所需的樣式。

ShapeDrawableDrawable 的子類別。因此,您可以在預期使用 Drawable 的位置使用 ShapeDrawable。舉例來說,您可以將 ShapeDrawable 物件傳遞至檢視區塊的 setBackgroundDrawable() 方法,藉此設定檢視區塊的背景。您也可以將形狀繪製為自己的自訂檢視區塊,並新增至應用程式的版面配置。

由於 ShapeDrawable 有自己的 draw() 方法,因此您可以建立 View 的子類別,在 onDraw() 事件期間繪製 ShapeDrawable 物件,如下列程式碼範例所示:

Kotlin

class CustomDrawableView(context: Context) : View(context) {
    private val drawable: ShapeDrawable = run {
        val x = 10
        val y = 10
        val width = 300
        val height = 50
        contentDescription = context.resources.getString(R.string.my_view_desc)

        ShapeDrawable(OvalShape()).apply {
            // If the color isn't set, the shape uses black as the default.
            paint.color = 0xff74AC23.toInt()
            // If the bounds aren't set, the shape can't be drawn.
            setBounds(x, y, x + width, y + height)
        }
    }

    override fun onDraw(canvas: Canvas) {
        drawable.draw(canvas)
    }
}

Java

public class CustomDrawableView extends View {
  private ShapeDrawable drawable;

  public CustomDrawableView(Context context) {
    super(context);

    int x = 10;
    int y = 10;
    int width = 300;
    int height = 50;
    setContentDescription(context.getResources().getString(
            R.string.my_view_desc));

    drawable = new ShapeDrawable(new OvalShape());
    // If the color isn't set, the shape uses black as the default.
    drawable.getPaint().setColor(0xff74AC23);
    // If the bounds aren't set, the shape can't be drawn.
    drawable.setBounds(x, y, x + width, y + height);
  }

  protected void onDraw(Canvas canvas) {
    drawable.draw(canvas);
  }
}

您可以像使用任何其他自訂檢視區塊一樣,在上述程式碼範例中使用 CustomDrawableView 類別。舉例來說,您可以透過程式輔助方式將其新增至應用程式的活動,如下列範例所示:

Kotlin

private lateinit var customDrawableView: CustomDrawableView

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    customDrawableView = CustomDrawableView(this)

    setContentView(customDrawableView)
}

Java

CustomDrawableView customDrawableView;

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  customDrawableView = new CustomDrawableView(this);

  setContentView(customDrawableView);
}

如要在 XML 版面配置中使用自訂檢視區塊,CustomDrawableView 類別必須覆寫 View(Context, AttributeSet) 建構函式,這個函式會在類別從 XML 加載時呼叫。以下範例說明如何在 XML 版面配置中宣告 CustomDrawableView

<com.example.shapedrawable.CustomDrawableView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        />

android.graphics.drawable 套件中的許多其他可繪項目類型一樣,ShapeDrawable 類別可讓您使用公開方法定義物件的各種屬性。您可能想調整的屬性包括 Alpha 透明度、色彩濾鏡、抖動、不透明度和顏色。

您也可以使用 XML 資源定義基本可繪項目形狀。詳情請參閱「 形狀可繪項目」一文中的「 可繪製資源類型」。

NinePatch 可繪項目

NinePatchDrawable 圖形是可延展的點陣圖圖片,可用做檢視區塊的背景。Android 會自動調整圖像大小,以配合檢視畫面內容。舉例來說,標準 Android 按鈕使用的背景就是 NinePatch 圖片,因為按鈕必須延展,才能容納各種長度的字串。NinePatch 圖像是一種標準 PNG 圖片,包含額外的 1 像素邊界。必須以 9.png 副檔名儲存在專案的 res/drawable/ 目錄中。

使用邊框定義圖片的可延展和靜態區域。 如要指出可延展的部分,請在邊框的左側和頂端繪製一或多條 1 像素寬的黑線 (其他邊框像素應完全透明或白色)。你可以視需要加入任意數量的可延展區段。可延展區段的相對大小維持不變,因此最大區段永遠是最大區段。

您也可以在右側和底部繪製線條,定義圖片的可繪項目區域 (實際上是邊框間距線條)。如果 View 物件將 NinePatch 圖像設為背景,然後指定檢視區塊的文字,系統會延伸該物件,讓所有文字只佔用右側和底部線條 (如有) 指定的區域。如果未納入邊框間距線,Android 會使用左側和頂端線定義這個可繪項目區域。

為釐清兩條線之間的差異,左側和頂端線條會定義允許複製的圖片像素,以便延展圖片。底線和右線定義圖片內的相對區域,視圖內容可佔用該區域。

圖 1 顯示用於定義按鈕的 NinePatch 圖像範例:

可延展區域和邊框間距方塊的圖片

圖 1:定義按鈕的 NinePatch 圖像範例

這個 NinePatch 圖像會使用左側和頂端線條定義一個可延展區域,並使用底部和右側線條定義可繪製區域。在上圖中,灰色虛線代表圖片中複製的區域,目的是為了延展圖片。下圖中的粉紅色矩形代表可顯示檢視畫面內容的區域。如果內容不符合這個區域,系統會將圖片延展至符合區域大小。

9-patch 繪製工具提供非常實用的方式,可使用所見即所得的圖像編輯器建立 NinePatch 圖像。如果因像素複製而可能導致繪圖失真,系統甚至會發出警告。

以下範例版面配置 XML 說明如何將 NinePatch 圖像新增至幾個按鈕。NinePatch 圖像會儲存至 res/drawable/my_button_background.9.png

<Button android:id="@+id/tiny"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerInParent="true"
        android:text="Tiny"
        android:textSize="8sp"
        android:background="@drawable/my_button_background"/>

<Button android:id="@+id/big"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerInParent="true"
        android:text="Biiiiiiig text!"
        android:textSize="30sp"
        android:background="@drawable/my_button_background"/>

請注意,layout_widthlayout_height 屬性已設為 wrap_content,讓按鈕能整齊地圍繞文字。

圖 2 顯示從 XML 和 NinePatch 圖片 (如上所示) 算繪的兩個按鈕。請注意,按鈕的寬度和高度會隨著文字而變化,背景圖片也會隨之延展。

小型和一般大小按鈕的圖片

圖 2:使用 XML 資源和 NinePatch 圖形算繪的按鈕

自訂可繪項目

如要建立一些自訂繪圖,可以擴充 Drawable 類別 (或其任何子類別)。

最重要的實作方法是 draw(Canvas),因為這個方法會提供您必須用來提供繪圖指令的 Canvas 物件。

下列程式碼顯示 Drawable 的簡單子類別,可繪製圓形:

Kotlin

class MyDrawable : Drawable() {
    private val redPaint: Paint = Paint().apply { setARGB(255, 255, 0, 0) }

    override fun draw(canvas: Canvas) {
        // Get the drawable's bounds
        val width: Int = bounds.width()
        val height: Int = bounds.height()
        val radius: Float = Math.min(width, height).toFloat() / 2f

        // Draw a red circle in the center
        canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, redPaint)
    }

    override fun setAlpha(alpha: Int) {
        // This method is required
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        // This method is required
    }

    override fun getOpacity(): Int =
        // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
        PixelFormat.OPAQUE
}

Java

public class MyDrawable extends Drawable {
    private final Paint redPaint;

    public MyDrawable() {
        // Set up color and text size
        redPaint = new Paint();
        redPaint.setARGB(255, 255, 0, 0);
    }

    @Override
    public void draw(Canvas canvas) {
        // Get the drawable's bounds
        int width = getBounds().width();
        int height = getBounds().height();
        float radius = Math.min(width, height) / 2;

        // Draw a red circle in the center
        canvas.drawCircle(width/2, height/2, radius, redPaint);
    }

    @Override
    public void setAlpha(int alpha) {
        // This method is required
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        // This method is required
    }

    @Override
    public int getOpacity() {
        // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
        return PixelFormat.OPAQUE;
    }
}

接著,您可以在所需位置新增可繪項目,例如新增至 ImageView,如下所示:

Kotlin

val myDrawing = MyDrawable()
val image: ImageView = findViewById(R.id.imageView)
image.setImageDrawable(myDrawing)
image.contentDescription = resources.getString(R.string.my_image_desc)

Java

MyDrawable mydrawing = new MyDrawable();
ImageView image = findViewById(R.id.imageView);
image.setImageDrawable(mydrawing);
image.setContentDescription(getResources().getString(R.string.my_image_desc));

在 Android 7.0 (API 級別 24) 以上版本中,您也可以透過下列方式,在 XML 中定義自訂可繪項目的例項:

  • 使用完整類別名稱做為 XML 元素名稱。採用這種做法時,自訂可繪項目類別必須是公開的頂層類別:
    <com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="#ffff0000" />
  • 使用 drawable 做為 XML 標記名稱,並從類別屬性指定完整類別名稱。這種方法可用於公開頂層類別和公開靜態內部類別:
    <drawable xmlns:android="http://schemas.android.com/apk/res/android"
        class="com.myapp.MyTopLevelClass$MyDrawable"
        android:color="#ffff0000" />

為可繪項目著色

在 Android 5.0 (API 級別 21) 以上版本中,您可以為定義為 Alpha 遮罩的點陣圖和九宮格圖片著色。您可以使用顏色資源或主題屬性,將這些資產染成顏色資源 (例如 ?android:attr/colorPrimary)。通常您只需要建立一次這些資產,系統就會自動將其染成符合主題的顏色。

您可以使用 setTint() 方法,為 BitmapDrawableNinePatchDrawableVectorDrawable 物件套用色調。您也可以使用 android:tintandroid:tintMode 屬性,在版面配置中設定色調顏色和模式。

從圖片擷取顯著色彩

Android 支援資料庫包含 Palette 類別,可讓您從圖片擷取醒目顯示色彩。您可以將可繪項目載入為 Bitmap,並傳遞至 Palette,存取其顏色。 詳情請參閱「使用 Palette API 選取顏色」。