可繪項目總覽

試用 Compose
Jetpack Compose 是 Android 建議使用的 UI 工具包。瞭解如何在 Compose 中顯示圖形。

如果需要在應用程式中顯示靜態圖片,可以使用 Drawable 類別及其子類別繪製形狀和圖片。Drawable可繪製項目的一般抽象概念。各種子類別都有助於處理特定圖像情境,而您可以擴充這些子類別,定義自己的可繪項目物件,並以獨特的方式表現。

除了使用類別建構函式之外,定義 Drawable 並執行個體化的方法有兩種:

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

注意:建議您改用向量可繪項目,這種可繪項目定義了一組點、線、曲線的圖片,以及相關顏色資訊。如此一來,向量可繪項目就能針對不同大小縮放,而不會影響品質。詳情請參閱「向量可繪項目總覽」。

從資源圖片建立可繪項目

您可以參照專案資源的圖片檔,在應用程式中加入圖形。支援的檔案類型包括 PNG (建議)、JPG (可接受) 和 GIF (不建議)。應用程式圖示、標誌和其他圖形 (例如遊戲中使用的圖片) 非常適合使用這項技巧。

如要使用圖片資源,請將檔案新增至專案的 res/drawable/ 目錄。進入專案後,您就可以從程式碼或 XML 版面配置參照圖片資源。不論是哪一種方式,參照資源 ID 都是使用資源 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/ 目錄中。以下範例顯示定義從 Drawable 繼承的 TransitionDrawable 資源的 XML:

<!-- 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 屬性,請參閱上述類別。

形狀可繪項目

如果想要動態繪製 2D 圖形,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 會自動調整圖形大小,以配合檢視畫面的內容。使用 NinePatch 圖片的一個例子,是標準 Android 按鈕使用的背景;按鈕必須延展以容納各種長度的字串。NinePatch 圖像是標準 PNG 圖片,包含額外的 1 像素邊框。您必須將 9.png 擴充功能儲存在專案的 res/drawable/ 目錄中。

您可以使用邊框定義圖片的可延展和靜態區域,您可以繪製一條 (或多) 1 像素寬的黑色線條 (其他邊框像素應該完全透明或白色),指出可延展部分。您可以視需求建立任意數量的延展部分。可延展部分的相對大小會保持不變,因此最大的區段會保持最大。

您也可以藉由在右側繪製一條線,在底部繪製線條,定義圖片的可選用的可繪項目部分 (實際上為邊框間距線)。如果 View 物件將 NinePatch 圖像設為背景,然後指定檢視區塊的文字,系統會自行延展畫面文字,讓所有文字只涵蓋右側和底部線條指定的區域 (如有)。如果不含邊框間距,Android 會使用左側和頂線定義此可繪項目區域。

為釐清兩行間的差異,左右線會定義為了延展圖片而複製的圖片像素。底部和右側行則定義圖片內可佔用檢視畫面內容的相對區域。

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

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

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

此 NinePatch 圖像定義了左側和頂線的可延展區域,以及底部和右側線條的可延展區域。在頂端圖片中,虛線虛線表示為了延展圖片而複製的圖片區域。底部圖片中的粉紅色矩形代表允許檢視內容的區域。如果內容不適合在這個區域,系統會延展圖片來配合這個區域。

「9-patch 繪製工具」工具可讓您使用 WYSIWYG 圖形編輯器建立 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) 以上版本中,您可以將點陣圖和 nine-patches 設為 Alpha 遮罩。您也可以使用可解析為顏色資源 (例如 ?android:attr/colorPrimary) 的顏色資源或主題屬性進行色調。一般而言,您只需要建立這些素材資源一次,並根據您的主題自動顏色。

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

從圖片擷取顯著色彩

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