可繪項目總覽

試用 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"
        />

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

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

NinePatch 可繪項目

NinePatchDrawable 圖形是可延展的點陣圖圖片,可做為檢視區塊的背景。Android 會自動調整圖形大小,以配合檢視區塊內容。使用 NinePatch 圖片的一個例子,是標準 Android 按鈕使用的背景—按鈕必須延展以容納各種長度的字串。NinePatch 圖像是標準 PNG 圖片,包含額外 1 像素的邊框。這個檔案必須與專案 res/drawable/ 目錄中的 9.png 擴充功能一起儲存。

您可以使用邊框來定義圖片的可延展區域和靜態區域。您可在邊框的左側和頂部繪製一條 (或更多) 1 像素寬的黑線 (其他邊框像素應為完全透明或白色) 來表示可延展部分。您可以擁有任意數量的可延展區段。可延展區段的相對大小保持不變,因此最大的區段始終是最大的。

您也可以在右側繪製線條,並在底部繪製線條,藉此定義圖片的可選可繪項目部分 (即邊框間距線條)。如果 View 物件將 NinePatch 圖像設為背景,然後指定該檢視區塊的文字,則會延展其本身,讓所有文字都只會佔用右側和底部線條指定的區域 (如有)。如果未包含邊框間距,Android 會使用左側和頂部線條定義這個可繪製區域。

左側和頂端線定義了可以複製圖片像素的哪些像素,以便延展圖片,藉此闡明這些線條之間的差異。底部和右線定義圖片中允許佔用檢視畫面內容的相對區域。

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

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

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

這個 NinePatch 圖像定義了一個可延展區域,包括左側和頂部線,以及含底部和右線的可繪製區域。在頂端圖片中,虛線代表為了延展圖片,而複製的圖片區域。底部圖片中的粉紅色矩形代表了允許檢視區塊內容的區域。如果內容不符合該區域,系統就會延展圖片,使其符合大小。

您可以使用 WYSIWYG 圖形編輯器建立 NinePatch 圖像,繪製 9-patch 工具是非常方便的方法。假如您為可延展區域定義的區域可能會因為像素複製而產生繪圖成果,也會發出警告。

以下版面配置 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 遮罩的 nine-patch 著色。您可以針對可解析顏色資源 (例如 ?android:attr/colorPrimary) 的顏色資源或主題屬性,為這些素材資源加上顏色。通常您只需要建立一次這些素材資源,就能依據主題自動為其加上顏色。

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

從圖片擷取顯著色彩

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