当您需要在应用中显示静态图片时,可以使用 Drawable
类及其子类来绘制形状和图片。 Drawable
是对可绘制内容的通用抽象。各种子类有助于处理特定的图片场景,您可以扩展它们来定义以独特方式行为的自定义 drawable 对象。
除了使用类构造函数之外,还有两种定义和实例化 Drawable
的方式
- 膨胀项目中的图片资源(一个 bitmap 文件)。
- 膨胀定义 drawable 属性的 XML 资源。
注意:您可能更喜欢使用矢量 drawable,它使用一组点、线和曲线以及相关的颜色信息来定义图片。这使得矢量 drawable 可以缩放以适应不同尺寸,而不会损失质量。如需了解更多信息,请参阅矢量 drawable 资源概览。
从资源图片创建 drawable
您可以通过引用项目资源中的图片文件来向应用添加图形。支持的文件类型包括 PNG(推荐)、JPG(可接受)和 GIF(不推荐)。应用图标、徽标以及其他图形(例如游戏中使用的图形)非常适合使用此技术。
要使用图片资源,请将文件添加到项目的 res/drawable/
目录中。添加到项目后,您可以在代码或 XML 布局中引用图片资源。无论哪种方式,都使用资源 ID 来引用,资源 ID 是不带文件扩展名的文件名。例如,将 my_image.png
引用为 my_image
。
注意:在构建过程中,放置在 res/drawable/
目录中的图片资源可能会被 aapt
工具使用无损图片压缩进行自动优化。例如,不需要超过 256 色的真彩色 PNG 可能会转换为带颜色调色板的 8 位 PNG。这会产生质量相同但所需内存更少的图片。因此,放置在此目录中的图片二进制文件可能会在构建时发生变化。如果您计划以比特流形式读取图片以将其转换为 bitmap,请将图片放入 res/raw/
文件夹中,aapt
工具不会修改这些文件。
以下代码段演示了如何构建一个使用 drawable 资源创建的图片,并将其添加到布局中的 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
添加 drawable 资源
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/my_image" android:contentDescription="@string/my_image_desc" />
如需了解有关使用项目资源的更多信息,请参阅资源和素材资源。
注意:将图片资源用作 drawable 的来源时,请确保图片大小适合各种像素密度。如果图片不正确,它们将被放大以适应,这可能会导致 drawable 中出现伪影。如需了解更多信息,请阅读支持不同的像素密度。
从 XML 资源创建 drawable
如果您想要创建一个最初不依赖于代码或用户互动定义的变量的 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 膨胀的 drawable 类都使用特定的 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 属性的更多信息,请参阅上面列出的类。
形状 drawable
当您想要动态绘制二维图形时,ShapeDrawable
对象是一个不错的选择。您可以在 ShapeDrawable
对象上以编程方式绘制基本形状并应用应用所需的样式。
ShapeDrawable
是 Drawable
的子类。因此,您可以在任何需要 Drawable
的地方使用 ShapeDrawable
。例如,您可以通过将其传递给 View 的 setBackgroundDrawable()
方法来使用 ShapeDrawable
对象设置 View 的背景。您还可以将形状绘制为其自己的自定义 View 并将其添加到应用的布局中。
由于 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
类,就像使用任何其他自定义 View 一样。例如,您可以像以下示例所示,以编程方式将其添加到应用的 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 布局中使用自定义 View,则 CustomDrawableView
类必须重写 View(Context, AttributeSet)
构造函数,当从 XML 膨胀该类时会调用此构造函数。以下示例展示了如何在 XML 布局中声明 CustomDrawableView
<com.example.shapedrawable.CustomDrawableView android:layout_width="fill_parent" android:layout_height="wrap_content" />
与 android.graphics.drawable
软件包中的许多其他 drawable 类型一样,ShapeDrawable
类允许您使用公共方法定义对象的各种属性。您可能想要调整的一些属性示例包括 alpha 透明度、颜色滤镜、抖动、不透明度和颜色。
您还可以使用 XML 资源定义基本 drawable 形状。如需了解更多信息,请参阅 drawable 资源类型中的形状 drawable。
NinePatch drawable
NinePatchDrawable
图形是一种可拉伸的 bitmap 图片,您可以将其用作 View 的背景。Android 会自动调整图形大小以适应 View 的内容。NinePatch 图片的一个示例用途是标准 Android 按钮使用的背景——按钮必须拉伸以适应各种长度的字符串。NinePatch 图形是标准的 PNG 图片,包含一个额外的 1 像素边框。它必须以 9.png
扩展名保存在项目的 res/drawable/
目录中。
使用边框定义图片的拉伸区域和静态区域。您可以通过在边框的左侧和顶部绘制一条(或多条)1 像素宽的黑线来指示可拉伸部分(其他边框像素应完全透明或为白色)。您可以拥有任意数量的可拉伸部分。可拉伸部分的相对大小保持不变,因此最大的部分始终保持最大。
您还可以通过在右侧和底部绘制一条线来定义图片的可选可绘制部分(实际上是内边距线)。如果 View
对象将其背景设置为 NinePatch 图形,然后指定 View 的文本,它会自行拉伸,以便所有文本仅占用由右侧和底部线指定的区域(如果包含)。如果未包含内边距线,Android 会使用左侧和顶部线来定义此可绘制区域。
为了阐明线之间的区别,左侧和顶部线定义了图片中哪些像素可以复制以拉伸图片。底部和右侧线定义了 View 内容在图片中允许占用的相对区域。
图 1 显示了用于定义按钮的 NinePatch 图形示例

图 1:定义按钮的 NinePatch 图形示例
此 NinePatch 图形使用左侧和顶部线定义了一个可拉伸区域,并使用底部和右侧线定义了可绘制区域。在顶部图片中,灰色虚线标识了为拉伸图片而复制的图片区域。底部图片中的粉色矩形标识了 View 内容允许所在的区域。如果内容不适合此区域,则图片会被拉伸以使其适合。
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_width
和 layout_height
属性设置为 wrap_content
可以使按钮整齐地包围文本。
图 2 显示了从上面所示的 XML 和 NinePatch 图片渲染的两个按钮。请注意按钮的宽度和高度如何随文本变化,以及背景图片如何拉伸以适应文本。

图 2:使用 XML 资源和 NinePatch 图形渲染的按钮
自定义 drawable
当您想要创建一些自定义绘制时,可以通过扩展 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; } }
然后,您可以将 drawable 添加到任何您喜欢的地方,例如添加到 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 定义自定义 drawable 的实例
- 使用完全限定类名作为 XML 元素名称。对于此方法,自定义 drawable 类必须是公共的顶级类
<com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android" android:color="#ffff0000" />
- 使用
drawable
作为 XML 标签名称,并从 class 属性指定完全限定类名。此方法可用于公共顶级类和公共静态内部类<drawable xmlns:android="http://schemas.android.com/apk/res/android" class="com.myapp.MyTopLevelClass$MyDrawable" android:color="#ffff0000" />
为 drawable 添加着色
借助 Android 5.0(API 级别 21)及更高版本,您可以为定义为 alpha 遮罩的 bitmap 和 Nine-patch 图片添加着色。您可以使用颜色资源或解析为颜色资源的主题属性(例如 ?android:attr/colorPrimary
)为它们着色。通常,您只需创建一次这些素材资源,并自动着色以匹配您的主题。
您可以使用 setTint()
方法为 BitmapDrawable
、NinePatchDrawable
或 VectorDrawable
对象应用着色。您还可以使用 android:tint
和 android:tintMode
属性在布局中设置着色颜色和模式。
从图片中提取主色调
Android Support Library 包含 Palette
类,该类允许您从图片中提取主色调。您可以将 drawable 加载为 Bitmap
并将其传递给 Palette
以访问其颜色。如需了解更多信息,请阅读使用 Palette API 选择颜色。