可绘制对象概览

试用 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 资源(继承自 Drawable)的 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 属性,请参阅上面列出的类。

形状可绘制对象

如果您想动态绘制二维图形,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 工具使用 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)及更高版本,您可以对定义为 Alpha 蒙版的位图和九宫图进行色调调节。您可以使用颜色资源或解析为颜色资源(例如,?android:attr/colorPrimary)的主题属性为其着色。通常,您只需创建一次这些资源,然后自动为其着色以与您的主题相匹配。

您可以使用 setTint() 方法对 BitmapDrawableNinePatchDrawableVectorDrawable 对象着色。您还可以使用 android:tintandroid:tintMode 属性在布局中设置着色颜色和模式。

从图片中萃取突出颜色

Android 支持库包含 Palette 类,可让您从图片中提取突出颜色。您可以将可绘制对象作为 Bitmap 加载,并将其传递给 Palette 以获取其颜色。如需了解详情,请参阅使用 Palette API 选择颜色