可绘制对象概览

试用 Compose 方式
Jetpack Compose 是推荐用于 Android 的界面工具包。了解如何在 Compose 中显示图形。

如果您需要在应用中显示静态图片,可以使用 Drawable 类及其子类绘制形状和图片。Drawable可绘制内容的一般抽象。各种子类有助于处理特定的图片场景,您也可以扩展它们,以定义自己的可绘制对象,这些对象的行为方式独特。

除了使用类构造函数之外,还可以通过另外两种方法定义和实例化 Drawable

  • 扩充保存在项目中的图片资源(位图文件)。
  • 扩充用于定义可绘制属性的 XML 资源。

注意:您可能更倾向于使用矢量可绘制对象,它通过一组点、线条和曲线以及相关颜色信息定义图片。这样就可以在不降低质量的情况下,针对不同尺寸缩放矢量可绘制对象。如需了解详情,请参阅矢量可绘制对象概览

通过资源图片创建可绘制对象

您可以通过引用项目资源中的图片文件向应用添加图形。支持的文件类型包括 PNG(首选)、JPG(可接受)和 GIF(不建议)。此方法非常适合采用应用图标、徽标和其他图形(例如游戏中使用的图片)。

如需使用图片资源,请将您的文件添加到项目的 res/drawable/ 目录中。进入项目中后,您可以从代码或 XML 布局中引用图片资源。无论采用哪种方式,它都会使用资源 ID(不带文件类型扩展名的文件名)进行引用。例如,my_image.png 引用为 my_image

注意:在构建过程中,aapt 工具可能会通过无损图片压缩自动优化位于 res/drawable/ 目录中的图片资源。例如,可以通过调色板将不需要超过 256 种颜色的真彩色 PNG 转换为 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 类。例如,您可以通过编程方式将其添加到应用中的 activity,如以下示例所示:

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 图形通过左侧和顶部的线定义可拉伸区域,通过底部和右侧的线定义可绘制区域。在上面的图片中,灰色虚线表示图片中为了拉伸图片而复制的区域。底部图片中的粉色矩形标识允许视图内容的区域。如果内容不适合此区域,系统会拉伸图片以使其适合该区域。

Draw 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 选择颜色