可绘制对象概览

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

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

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

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

注意:您可能更喜欢使用矢量可绘制对象,它通过一组点、线条和曲线以及相关颜色信息定义图片。这样,矢量可绘制对象就能针对不同的大小进行缩放,而不会有损图片质量。如需了解详情,请参阅矢量可绘制对象概览

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

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

如需使用图片资源,请将文件添加到项目的 res/drawable/ 目录中。进入项目后,您可以从代码或 XML 布局中引用图片资源。无论采用哪种方式,都可以使用资源 ID 来引用资源 ID,资源 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/ 目录中。以下示例展示了定义从 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 属性,请参阅上面列出的类。

形状可绘制对象

如果您想动态绘制二维图形,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 选择颜色