1. 概述
在实际开发的过程中,除了广为人知的利用 StateListDrawable 设置按钮点击特效,我们有时可能会接到一些这样的需求,比如要求我们的头像显示成圆形或者圆角矩形,甚至要加上可变颜色的边框,或者要求你做一套启动、暂停、快进和快退的视频控制按钮并且可以改变按钮图标颜色。可能某些时候第一反应就是用自定义 View 来实现,但是如果熟悉了 Drawable 的用法之后,这些效果同样可以利用它来完成,而选择哪种 Drawable 来实现也大有讲究。
Google 官方文档中列出了各种各种各样的 Drawable,那么它们都是如何使用的呢?

本系列文章将分为两个部分,介绍其中大部分 Drawable 的用法。
2. BitmapDrawable
BitmapDrawable 可以看作是对 Bitmap 的一种包装,它可以设定 Bitmap 的显示方式、位置等属性。
2.1 语法
BitmapDrawable 对应 <bitmap> 标签定义,xml 语法如下:
<?xml version="1.0" encoding="utf-8"?>
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@[package:]drawable/drawable_resource"
android:antialias=["true" | "false"]
android:dither=["true" | "false"]
android:filter=["true" | "false"]
android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
"fill_vertical" | "center_horizontal" | "fill_horizontal" |
"center" | "fill" | "clip_vertical" | "clip_horizontal"]
android:mipMap=["true" | "false"]
android:tileMode=["disabled" | "clamp" | "repeat" | "mirror"] />
其中各个属性的含义分别是:
| 属性 | 含义 |
|---|---|
| src | Bitmap 对象的引用路径 |
| antialias | 抗锯齿效果,建议开启 |
| dither | 是否防抖动。当位图像素与屏幕像素不匹配(如 ARGB_8888 的位图显示在 RGB_565 的屏幕上)时,防止图片失真,建议开启 |
| filter | 是否启用位图过滤。开启后,当图片进行拉伸或者压缩时,能够进行平滑过渡 |
| gravity | 当位图尺寸小于容器尺寸时在容器中的摆放位置 |
| mipMap | 启用或停用 mipmap 提示,默认为 false |
| tileMode | 平铺模式。默认:disable;clamp:复制边沿的颜色;repeat:水平和垂直方向重复绘制图片;mirror:水平和垂直方向交替镜像进行重复绘制 |
2.2 用法示例
下面以定义一个使用图片作为背景的 Drawable 为例,展示 BitmapDrawable 的简单实用方法。
定义
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@mipmap/kakarotto"
android:antialias="true"
android:dither="true"
android:filter="true"
android:gravity="center"
android:tileMode="repeat"/>
使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bitmap_drawable"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
效果图

3. ShapeDrawable 和 GradientDrawable
官方文档中对 ShapeDrawable 的定义是这样的:
A Drawable object that draws primitive shapes. A ShapeDrawable takes a
Shapeobject and manages its presence on the screen. If no Shape is given, then the ShapeDrawable will default to aRectShape.This object can be defined in an XML file with the
<shape>element.
即它是一个用来绘制原始形状的 Drawable 对象。
而对 GradientDrawable 的定义是:
A Drawable with a color gradient for buttons, backgrounds, etc.
It can be defined in an XML file with the
<shape>element. For more information, see the guide to Drawable Resources.
根据描述可知,它是一个具有色彩梯度(color gradient)的 Drawable。
3.1 语法
GradientDrawable 和 ShapeDrawable 都采用 shape 标签来定义,和 ShapeDrawable 最大的不同的就是它拥有 gradient 属性,下面以 GradientDrawable 为例,讲解 shape 标签的用法,它的语法如下:
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape=["rectangle" | "oval" | "line" | "ring"] >
<corners
android:radius="integer"
android:topLeftRadius="integer"
android:topRightRadius="integer"
android:bottomLeftRadius="integer"
android:bottomRightRadius="integer" />
<gradient
android:angle="integer"
android:centerX="integer"
android:centerY="integer"
android:centerColor="integer"
android:endColor="color"
android:gradientRadius="integer"
android:startColor="color"
android:type=["linear" | "radial" | "sweep"]
android:usesLevel=["true" | "false"] />
<padding
android:left="integer"
android:top="integer"
android:right="integer"
android:bottom="integer" />
<size
android:width="integer"
android:height="integer" />
<solid
android:color="color" />
<stroke
android:width="integer"
android:color="color"
android:dashWidth="integer"
android:dashGap="integer" />
</shape>
其中各个属性标签的含义分别是:
android:shape
表示形状,它的值可以是 rectangle(矩形)、oval(椭圆)、line(横线)和 ring(圆环),默认为 rectangle。 此外,当形状值是 ring 的时候,还有一下几个属性可配置:
| 属性 | 含义 |
|---|---|
| android:innerRadius | 圆环的内半径。与 innerRadiusRatio 同时设置时,以 innerRadiusRatio 为准 |
| android:innerRadiusRatio | 圆环的内半径占环宽度的比率 |
| android:thickness | 圆环厚度。与 thicknessRatio同时设置时,以 thicknessRatio 为准 |
| android:thicknessRatio | 圆环的厚度占环宽度的比率 |
| android:useLevel | 一般为 false,否则可能达不到预期显示效果,除非把它当作 LevelListDrawable 来使用 |
<corners>
<corners
android:radius="integer"
android:topLeftRadius="integer"
android:topRightRadius="integer"
android:bottomLeftRadius="integer"
android:bottomRightRadius="integer" />
指图形的圆角半径,仅当 shape 属性为 rectangle 即形状是矩形时生效,数值越小越接近直角,android:radius 同时设置四个角的半径,其他四个属性则可单独设置某个角的半径。
<gradient>
<gradient
android:angle="integer"
android:centerX="integer"
android:centerY="integer"
android:centerColor="integer"
android:endColor="color"
android:gradientRadius="integer"
android:startColor="color"
android:type=["linear" | "radial" | "sweep"]
android:usesLevel=["true" | "false"] />
表示颜色渐变,它的各个属性值的含义分别是:
| 属性 | 含义 |
|---|---|
| android:angle | 渐变的角度。必须是 45 的倍数,默认值为 0。0 为从左到右,90 为从上到下 |
| android:centerX | 渐变中心的相对 X 轴位置 (0 - 1.0) |
| android:centerY | 渐变中心的相对 Y 轴位置 (0 - 1.0) |
| android:startColor | 渐变的起始颜色 |
| android:centerColor | 渐变的中间颜色 |
| android:endColor | 渐变的结束颜色 |
| android:gradientRadius | 渐变的半径,仅在 android:type="radial" 时适用 |
| android:useLevel | 一般为 false,否则可能达不到预期显示效果,除非把它当作 LevelListDrawable 来使用 |
| android:type | 渐变类别。它的值可以为:linear(线性),默认值、radial(径内渐变)和sweep(扫描渐变) |
<padding>
距离内容或者子元素的内边距,每个方向可以单独设置。
<size>
设置 shape 大小,width 表示宽度,height 表示高度。需要注意的是,这个一般并不是 shape 的最终大小,如果用作 View 的背景,它的大小是由 View 的大小来决定的。
<solid>
表示纯色填充,color 属性为填充的颜色。
<stroke>
边框描述,它的各个属性值的含义分别是:
| 属性 | 含义 |
|---|---|
| android:width | 边框宽度 |
| android:color | 边框颜色 |
| android:dashWidth | 虚线的线段宽度 |
| android:dashGap | 虚线之间的空白间隔 |
需要注意的是,如果需要设置边框虚线效果,则需要同时设置 dashWidth 和 dashGap 的值不为 0,否则无法显示虚线效果。
3.2 用法示例
下面以定义一个圆角并带有其他效果的 Drawable 为例,展示 GradientDrawable 的简单用法。
定义
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!--圆角半径-->
<corners
android:topLeftRadius="15dp"
android:topRightRadius="15dp"
android:bottomLeftRadius="15dp"
android:bottomRightRadius="15dp"/>
<!--内边距-->
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
<!--渐变效果-->
<gradient android:angle="45"
android:type="linear"
android:startColor="#ff0000"
android:centerColor="#00ff00"
android:endColor="#0000ff" />
<!--预设大小-->
<size
android:width="200dp"
android:height="100dp" />
<!--边框样式-->
<stroke
android:width="2dp"
android:color="#000000"
android:dashWidth="7dp"
android:dashGap="3dp" />
</shape>
使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".GradientDrawableActivity">
<Button
android:text="Button"
android:layout_width="200dp"
android:layout_height="100dp"
android:background="@drawable/gradient_drawable"
android:id="@+id/textView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
效果图

4. StateListDrawable
StateListDrawable 可以根据对象的状态并使用不同的 item(Drawable) 对象来表示同一个图形。如可以根据 Button 的状态(按下、获取焦点等)来显示不同的 Drawable 从而实现点击的效果。
4.1 语法
定义 StateListDrawable 的语法格式如下:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:constantSize=["true" | "false"]
android:dither=["true" | "false"]
android:variablePadding=["true" | "false"]
android:autoMirrored=["true" | "false"]
android:enterFadeDuration="integer"
android:exitFadeDuration="integer">
<item
android:drawable="@[package:]drawable/drawable_resource"
android:state_pressed=["true" | "false"]
android:state_focused=["true" | "false"]
android:state_hovered=["true" | "false"]
android:state_selected=["true" | "false"]
android:state_checkable=["true" | "false"]
android:state_checked=["true" | "false"]
android:state_enabled=["true" | "false"]
android:state_activated=["true" | "false"]
android:state_window_focused=["true" | "false"] />
</selector>
StateListDrawable 的根标签为 **<selector>,**各个属性标签的含义分别是:
android:constantSize
由于 StateListDrawable 会根据不同的状态来显示不同的 Drawable,而每个 Drawable 的大小不一定相同,因此当 constantSize 属性的值为 true 时表示固定大小(值为所有 Drawable 固有大小的最大值),值为 false 时则大小为当前状态下对应的 Drawable 的大小。默认值为 false。
android:variablePadding
表示 StateListDrawable 的 padding 值是否随状态的改变而改变,默认为 false。
android:dither
是否开启抖动效果,默认为 true,建议开启。
android:autoMirrored
某些西亚国家文字是从右至左的,设置此值表示当系统为 RTL (right-to-left) 布局的时候,是否对图片进行镜像翻转。
android:enterFadeDuration 和 android:exitFadeDuration
状态改变时的淡入淡出效果的持续时间
<item>
每个 item 表示一个 Drawable,item 的属性含义分别是:
| 属性 | 含义 |
|---|---|
| android:drawable | drawable 资源,可引用现有的的 Drawable |
| android:state_pressed | 是否处于被按下状态 |
| android:state_focused | 是否已得到焦点状态 |
| android:state_hovered | 光标是否停留在View的自身大小范围内的状态 |
| android:state_selected | 是否处于被选中状态 |
| android:state_checkable | 是否处于可勾选状态 |
| android:state_checked | 是否处于已勾选状态 |
| android:state_enabled | 是否处于可用状态 |
| android:state_active | 是否处于激活状态 |
| android:state_window_focused | 是否窗口已得到焦点状态 |
4.2 用法示例
下面以定制一个具有点击效果 Button 的背景为例,展示 StateListDrawable 的用法。
定义
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android"
android:visible="true"
android:dither="true"
android:autoMirrored="true"
android:enterFadeDuration="200"
android:exitFadeDuration="200" >
<!--获取焦点状态-->
<item
android:state_focused="true"
android:drawable="@drawable/shape_dark" />
<!--按下状态-->
<item
android:state_pressed="true"
android:drawable="@drawable/shape_dark"/>
<!--默认状态-->
<item
android:drawable="@drawable/shape_light"/>
</selector>
使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_orange_dark"
tools:context=".LayerDrawableActivity">
<Button
android:text="Button"
android:background="@drawable/drawable_state_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
效果图

5. LayerDrawable
LayerDrawable 是管理 Drawable 列表的 Drawable。列表中的每个 item 按照列表的顺序绘制,列表中的最后 item 绘于顶部。
5.1 语法
定义 LayerDrawable 的语法格式如下:
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:drawable="@[package:]drawable/drawable_resource"
android:id="@[+][package:]id/resource_name"
android:top="dimension"
android:right="dimension"
android:bottom="dimension"
android:left="dimension"
android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
"fill_vertical" | "center_horizontal" | "fill_horizontal" |
"center" | "fill" | "clip_vertical" | "clip_horizontal"] />
</layer-list>
LayerDrawable 顶层标签为 <layer-list>,它可以包含多个 <item> 标签,每个 item 表示一个 Drawable,item 的属性含义分别是:
| 属性 | 含义 |
|---|---|
| android:drawable | drawable 资源,可引用现有的的 Drawable |
| android:id | item 的 id,使用"@+id/name"的形式表示。可通过 View.findViewById() 或者 Activity.findViewById() 方法查找到这个 Drawable |
| android:top、android:right、android:bottom、android:left | Drawable 相对于 View 在各个方向的偏移量 |
| android:gravity | 尺寸小于容器尺寸时在容器中的摆放位置 |
5.2 用法示例
下面以定义一个圆角并带阴影效果的 Drawable 为例,展示 LayerDrawable 的简单使用。
定义
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--内部定义一个 Drawable-->
<item
android:left="2dp"
android:top="4dp">
<shape>
<solid android:color="@android:color/darker_gray" />
<corners android:radius="10dp" />
</shape>
</item>
<!--指定现有的 Drawable-->
<item
android:bottom="4dp"
android:right="2dp"
android:drawable="@drawable/shape_light">
</item>
</layer-list>
使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_orange_dark"
tools:context=".LayerDrawableActivity">
<LinearLayout
android:orientation="vertical"
android:background="@drawable/layer_drawable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_margin="20dp"
tools:layout_editor_absoluteY="331dp"
tools:layout_editor_absoluteX="190dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#000000"
android:text="I'm a title......."
android:textSize="20sp" />
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/darker_gray"
android:text="content content content content content content content content..."
android:textSize="16sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
效果图

6. LevelListDrawable
LevelListDrawable 同样表示一个 Drawable 列表,列表中的每个 item 都有一个 level 值, LevelListDrawable 会根据不同的 level 在不同的 item 之间进行切换。
6.1 语法
定义 LevelListDrawable 的语法格式如下:
<?xml version="1.0" encoding="utf-8"?>
<level-list
xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:drawable="@drawable/drawable_resource"
android:maxLevel="integer"
android:minLevel="integer" />
</level-list>
LayerDrawable 根标签为 <layer-list>,它可以包含多个 <item> 标签,每个 item 表示一个 Drawable,item 的属性含义分别是:
| 属性 | 含义 |
|---|---|
| android:drawable | drawable 资源,可引用现有的的 Drawable |
| android:maxLevel | 该 item 允许的最大级别,取值范围为[0, 10000] |
| android:minLevel | 该 item 允许的最小级别,取值范围为[0, 10000] |
6.2 用法示例
定义
<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/kakarotto1"
android:maxLevel="0" />
<item
android:drawable="@drawable/kakarotto2"
android:maxLevel="1" />
<item
android:drawable="@drawable/kakarotto3"
android:maxLevel="2" />
<item
android:drawable="@drawable/kakarotto4"
android:maxLevel="3" />
<item
android:drawable="@drawable/kakarotto5"
android:maxLevel="4" />
</level-list>
使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".GradientDrawableActivity">
<ImageView
android:text="Button"
android:layout_width="230dp"
android:layout_height="150dp"
android:src="@drawable/drawable_level_list"
android:id="@+id/img"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
然后控制 ImageView 的 level 即可显示出效果:
class LevelListDrawableActivity : AppCompatActivity() {
lateinit var mImageView: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_level_list_drawable)
mImageView = findViewById(R.id.img)
for (i in 0..15) {
mHandler.sendEmptyMessageDelayed(i, (1000 * i).toLong())
}
}
var mHandler: Handler = object: Handler() {
override fun handleMessage(msg: Message?) {
msg?.what?.let { mImageView.setImageLevel(it%5) }
}
}
}
效果图

7. InsetDrawable
在有些场景下,我们可能需要设置一个全屏的背景图片,但又想让背景图片跟边框留出一些间隙,这时使用 InsetDrawable 就能很好地解决问题了。
7.1 语法
<?xml version="1.0" encoding="utf-8"?>
<inset
xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_resource"
android:insetTop="dimension"
android:insetRight="dimension"
android:insetBottom="dimension"
android:insetLeft="dimension" />
根标签为 <inset>,它的各个属性含义分别是:
| 属性 | 含义 |
|---|---|
| android:drawable | drawable 资源,可引用现有的的 Drawable |
| android:insetTop、android:insetRight、android:insetBottom、android:insetLeft | 内容距离各个边框的距离 |
7.2 用法示例
定义
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/shape_dark"
android:insetBottom="10dp"
android:insetTop="10dp"
android:insetLeft="10dp"
android:insetRight="10dp" />
使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/drawable_inset">
<TextView
android:textSize="20sp"
android:text="TextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
效果图

8. ScaleDrawable
ScaleDrawable 可以根据 level 值动态地将 Drawable 进行一定比例的缩放。当 level 的取值范围为 [0, 10000],当 level 为 0 时表示隐藏;当 level 值为 1 时,Drawable 的大小为初始化时的缩放比例,当 level 值为 10000 时,Drawable 大小为 100% 缩放比例。
8.1 语法
定义 ScaleDrawable 的语法如下:
<?xml version="1.0" encoding="utf-8"?>
<scale
xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_resource"
android:scaleGravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
"fill_vertical" | "center_horizontal" | "fill_horizontal" |
"center" | "fill" | "clip_vertical" | "clip_horizontal"]
android:scaleHeight="percentage"
android:scaleWidth="percentage" />
它的根标签为 <scale>,它的各个属性的含义分别是:
android:drawable
drawable 资源,可引用现有的的 Drawable
android:scaleGravity
当图片尺寸小于 View 时,设置这个属性值可以对图片进行定位,可以使用 ”|“ 符号组合使用,所有值的含义分别为:
| 值 | 说明 |
|---|---|
| top | 将对象放在其容器顶部,不改变其大小。 |
| bottom | 将对象放在其容器底部,不改变其大小。 |
| left | 将对象放在其容器左边缘,不改变其大小。这是默认值。 |
| right | 将对象放在其容器右边缘,不改变其大小。 |
| center_vertical | 将对象放在其容器的垂直中心,不改变其大小。 |
| fill_vertical | 按需要扩展对象的垂直大小,使其完全适应其容器。 |
| center_horizontal | 将对象放在其容器的水平中心,不改变其大小。 |
| fill_horizontal | 按需要扩展对象的水平大小,使其完全适应其容器。 |
| center | 将对象放在其容器的水平和垂直轴中心,不改变其大小。 |
| fill | 按需要扩展对象的垂直大小,使其完全适应其容器。 |
| clip_vertical | 可设置为让子元素的上边缘和/或下边缘裁剪至其容器边界的附加选项。裁剪基于垂直重力:顶部重力裁剪上边缘,底部重力裁剪下边缘,任一重力不会同时裁剪两边。 |
| clip_horizontal | 可设置为让子元素的左边和/或右边裁剪至其容器边界的附加选项。裁剪基于水平重力:左边重力裁剪右边缘,右边重力裁剪左边缘,任一重力不会同时裁剪两边。 |
android:scaleHeight
Drawable 高的缩放比例,值越高最终结果越小。
android:scaleWidth
Drawable 宽的缩放比例
8.2 用法示例
这里采用定制一个大小可变的背景为例,展示 ScaleDrawable 的简单用法。
定义
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/kakarotto"
android:scaleHeight="80%"
android:scaleWidth="80%"
android:scaleGravity="center" />
使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="Button"
android:layout_width="200dp"
android:layout_height="100dp"
android:background="@drawable/drawable_scale"
android:id="@+id/button"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
class ScaleDrawableActivity : AppCompatActivity() {
lateinit var scaleDrawable: ScaleDrawable
var curLevel = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scale_drawable)
scaleDrawable = findViewById<Button>(R.id.button).background as ScaleDrawable
scaleDrawable.level = 0
Observable.interval(200, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe {
scaleDrawable.level = curLevel
curLevel += 200
if (curLevel >= 10000) {
curLevel = 0
}
Log.e("gpj", "level ${curLevel}")
}
}
}
效果图

9. ClipDrawable
与 ScaleDrawable 原理相同,ClipDrawable 则可以根据 level 值动态地将 Drawable 进行一定比例的剪裁。当 level 的取值范围为 [0, 10000],当 level 为 0 时表示隐藏;当 level 值为 1 时,Drawable 的大小为初始化时的剪裁比例,当 level 值为 10000 时,Drawable 大小为 100% 剪裁比例。
9.1 语法
定义 ClipDrawable 的语法规则如下:
<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_resource"
android:clipOrientation=["horizontal" | "vertical"]
android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
"fill_vertical" | "center_horizontal" | "fill_horizontal" |
"center" | "fill" | "clip_vertical" | "clip_horizontal"] />
它的根标签为 <clip>,各个属性的含义分别是:
android:drawable
drawable 资源,可引用现有的的 Drawable
android:clipOrientation
剪裁方向,horizontal 表示水平方向剪裁,vertical 表示竖直方向剪裁
android:gravity
gravity 属性需要配合 clipOrientation 来使用,可以使用 ”|“ 符号组合使用,所有值的含义分别为:
| 值 | 说明 |
|---|---|
| top | 将对象放在其容器顶部,不改变其大小。当 clipOrientation 是 "vertical" 时,在可绘制对象的底部裁剪。 |
| bottom | 将对象放在其容器底部,不改变其大小。当 clipOrientation 是 "vertical" 时,在可绘制对象的顶部裁剪。 |
| left | 将对象放在其容器左边缘,不改变其大小。这是默认值。当 clipOrientation 是 "horizontal" 时,在可绘制对象的右边裁剪。这是默认值。 |
| right | 将对象放在其容器右边缘,不改变其大小。当 clipOrientation 是 "horizontal"时,在可绘制对象的左边裁剪。 |
| center_vertical | 将对象放在其容器的垂直中心,不改变其大小。裁剪行为与重力为 "center" 时相同。 |
| fill_vertical | 按需要扩展对象的垂直大小,使其完全适应其容器。当 clipOrientation 是 "vertical" 时,不会进行裁剪,因为可绘制对象会填充垂直空间(除非可绘制对象级别为 0,此时它不可见)。 |
| center_horizontal | 将对象放在其容器的水平中心,不改变其大小。裁剪行为与重力为 "center" 时相同。 |
| fill_horizontal | 按需要扩展对象的水平大小,使其完全适应其容器。当 clipOrientation 是 "horizontal" 时,不会进行裁剪,因为可绘制对象会填充水平空间(除非可绘制对象级别为 0,此时它不可见)。 |
| center | 将对象放在其容器的水平和垂直轴中心,不改变其大小。当 clipOrientation 是 "horizontal" 时,在左边和右边裁剪。当 clipOrientation 是 "vertical" 时,在顶部和底部裁剪。 |
| fill | 按需要扩展对象的垂直大小,使其完全适应其容器。不会进行裁剪,因为可绘制对象会填充水平和垂直空间(除非可绘制对象级别为 0,此时它不可见)。 |
| clip_vertical | 可设置为让子元素的上边缘和/或下边缘裁剪至其容器边界的附加选项。裁剪基于垂直重力:顶部重力裁剪上边缘,底部重力裁剪下边缘,任一重力不会同时裁剪两边。 |
| clip_horizontal | 可设置为让子元素的左边和/或右边裁剪至其容器边界的附加选项。裁剪基于水平重力:左边重力裁剪右边缘,右边重力裁剪左边缘,任一重力不会同时裁剪两边。 |
9.2 用法示例
定义
<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_resource"
android:clipOrientation=["horizontal" | "vertical"]
android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
"fill_vertical" | "center_horizontal" | "fill_horizontal" |
"center" | "fill" | "clip_vertical" | "clip_horizontal"] />
使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="Button"
android:layout_width="200dp"
android:layout_height="100dp"
android:background="@drawable/drawable_clip"
android:id="@+id/button"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
class ClipDrawableActivity : AppCompatActivity() {
lateinit var clipDrawable: ClipDrawable
var curLevel = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_clip_drawable)
clipDrawable = findViewById<Button>(R.id.button).background as ClipDrawable
clipDrawable.level = 0
Observable.interval(50, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe {
clipDrawable.level = curLevel
curLevel += 200
if (curLevel >= 10000) {
curLevel = 0
}
Log.e("gpj", "level ${curLevel}")
}
}
}
效果图

10. RotateDrawable
与 ScaleDrawable 和 ClipDrawable 类似,RotateDrawable 可以根据 level 值将 Drawable 进行动态旋转。
10.1 语法
RotateDrawable 的定义方法如下:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_resource"
android:visible=["true" | "false"]
android:fromDegrees="integer"
android:toDegrees="integer"
android:pivotX="percentage"
android:pivotY="percentage" />
它的根标签为 <clip>,各个属性的含义分别是:
| 属性 | 含义 |
|---|---|
| android:drawable | drawable 资源,可引用现有的的 Drawable |
| android:visible | 是否可见 |
| android:fromDegrees | 旋转起始角度 |
| android:toDegrees | 旋转结束角度 |
| android:pivotX | 旋转中心位于 X 轴的百分比 |
| android:pivotY | 旋转中心位于 Y 轴的百分比 |
10.2 用法示例
下面以定义一个可旋转的背景为例展示 RotateDrawable 的简单使用
定义
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/kakarotto"
android:fromDegrees="0"
android:toDegrees="180"
android:pivotX="50%"
android:pivotY="50%"/>
使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="Button"
android:layout_width="200dp"
android:layout_height="100dp"
android:background="@drawable/drawable_rotate"
android:id="@+id/button"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
class RotateDrawableActivity : AppCompatActivity() {
lateinit var rotateDrawable: RotateDrawable
var curLevel = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_rotate_drawable)
rotateDrawable = findViewById<Button>(R.id.button).background as RotateDrawable
rotateDrawable.level = 0
Observable.interval(50, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe {
rotateDrawable.level = curLevel
curLevel += 200
if (curLevel >= 10000) {
curLevel = 0
}
Log.e("gpj", "level ${curLevel}")
}
}
}
效果图

11. TransitionDrawable
有时候我们可能需要在两个图片切换的时候增加渐变效果,除了使用动画之外,这里还可以用 TransitionDrawable 轻松实现。
11.1 语法
<?xml version="1.0" encoding="utf-8"?>
<transition
xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:drawable="@[package:]drawable/drawable_resource"
android:id="@[+][package:]id/resource_name"
android:top="dimension"
android:right="dimension"
android:bottom="dimension"
android:left="dimension" />
</transition>
它的根标签为 <transition>,它可以包含多个 <item> 标签,每个 item 表示一个 Drawable,item 的属性含义分别是:
| 属性 | 含义 |
|---|---|
| android:drawable | drawable 资源,可引用现有的的 Drawable |
| android:id | iitem 的 id,使用"@+id/name"的形式表示。可通过 View.findViewById() 或者 Activity.findViewById() 方法查找到这个 Drawable |
| android:top、android:right、android:bottom、android:left | Drawable 相对于 View 在各个方向的偏移量 |
11.2 用法示例
这里定义一个淡入淡出效果的图片切换效果,展示 TransitionDrawable 的基本使用。
定义
<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/kakarotto1" />
<item android:drawable="@drawable/kakarotto2" />
</transition>
使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="230dp"
android:layout_height="150dp"
android:background="@drawable/drawable_transition"
android:id="@+id/img"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
class TransitionDrawableActivity : AppCompatActivity() {
lateinit var disposable: Disposable
var reverse = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_transition_drawable)
var transitionDrawable = findViewById<ImageView>(R.id.img).background as TransitionDrawable
Observable.interval(3000, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<Long> {
override fun onSubscribe(d: Disposable) {
disposable = d
}
override fun onComplete() {
}
override fun onNext(t: Long) {
if (!reverse) {
transitionDrawable.startTransition(3000)
reverse = true
} else {
transitionDrawable.reverseTransition(3000)
reverse = false
}
}
override fun onError(e: Throwable) {
}
})
}
效果图

11. RippleDrawable
记得谷歌刚发布 Android 5.0 系统时,用 API 21 的镜像启动模拟器后看到一个很明显的变化就是,很多按钮上都加了点击波纹效果,
11.1 语法
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="color"
android:radius="dimension">
<item
android:id="@[package:]id/resource_name"
android:drawable="@[package:]drawable/drawable_resource"
android:top="dimension"
android:right="dimension"
android:bottom="dimension"
android:left="dimension"
android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
"fill_vertical" | "center_horizontal" | "fill_horizontal" |
"center" | "fill" | "clip_vertical" | "clip_horizontal"] />
</ripple>
RippleDrawable 顶层标签为 <ripple>,它的两个属性的含义分别是:
android:color
ripple 效果的颜色
android:radius
ripple 完全扩散开始的半径。默认会根据容器大小来计算。
除此之外,它可以包含多个 <item> 标签,每个 item 表示一个 Drawable,item 的属性含义分别是:
| 属性 | 含义 |
|---|---|
| android:drawable | drawable 资源,可引用现有的的 Drawable |
| android:id | 如果 item 的 id 设置成 @android:id/mask,在初始化时这个 item 不会被绘制,只会在点击的时候以蒙层的形式限制波纹的范围在这个 item 之内 |
| android:top、android:right、android:bottom、android:left | Drawable 相对于 View 在各个方向的偏移量 |
| android:gravity | 尺寸小于容器尺寸时在容器中的摆放位置 |
11.2 用法示例
定义
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorAccent"
android:radius="90dp">
<item
android:id="@android:id/mask"
android:drawable="@android:color/white" />
<item android:drawable="@color/colorPrimary" />
</ripple>
使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="I' m a Button"
android:layout_width="200dp"
android:layout_height="50dp"
android:background="@drawable/drawable_ripple"
android:id="@+id/button"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
效果图

12. VectorDrawable
从 API 21(Android 5.0) 开始,Google 开始支持使用 Vector,VectorDrawable 应运而生。相比于普通的 Drawable,它具有以下优点:
- Vector 图像可以自动进行适配,不需要通过分辨率来设置不同的图片
- Vector 图像可以大幅减少图像的体积,同样一张图,用 Vector 来实现,可能只有 PNG 的几十分之一
- 使用简单,很多设计工具都可以直接导出 SVG 图像,从而转换成 Vector 图像
- 功能强大,不用写很多代码就可以实现非常复杂的动画
- 成熟、稳定,目前已经非常广泛地进行使用了
12.1 语法
定义一个 VectorDrawable 的语法如下:
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="string"
android:width="dimension"
android:height="dimension"
android:viewportHeight="float"
android:viewportWidth="float"
android:tint="color"
android:tintMode=["add" | "multiply" | "src_top" | "src_in" | "src_over" | "screen"]
android:autoMirrored=["true" | "false"]
android:alpha="integer" />
<group
android:name="string"
android:pivotX="float"
android:pivotY="float"
android:rotation="integer"
android:translationX="float"
android:translationY="float"
android:scaleX="float"
android:scaleY="float">
<path
android:name="string"
android:pathData="path"
android:fillColor="color"
android:fillAlpha="integer"
android:strokeColor="color"
android:strokeWidth="integer"
android:strokeAlpha="integer"
android:trimPathStart="float"
android:trimPathEnd="float"
android:trimPathOffset="float"
android:strokeLineCap=["butt" | "round" | "square"]
android:strokeLineJoin=["round" | "bevel" | "miter"]
android:strokeMiterLimit="integer"
android:fillType=["nonZero" | "evenOdd"] />
<clip-path
android:name="string"
android:pathData="path" />
</group>
</vector>
VectorDrawable 的根标签为 <vector>,老规矩,先看看它的子元素属性和含义:
| 属性 | 含义 |
|---|---|
| android:name | drawable 的名字 |
| android:width、android:height | 内部(intrinsic)宽度和高度。一般使用 dp |
| android:viewportWidth、android:viewportHeight | 矢量图视图的宽度和高度。视图就是矢量图 path 路径数据所绘制的虚拟画布 |
| android:tint | 给矢量图着色 |
| android:tintMode | 着色模式。共支持六种模式,默认为“src_in",详情请参考 PorterDuff.Mode |
| android:autoMirrored | 自动翻转 |
| android:alpha | 图片透明度。取值范围为 [0, 255]VectorDrawble 支持 |
一张矢量图可以由多个 path 组成,<group> 标签可以对多个 path 进行分组,标签内的属性值对组内所有 path 都生效,<group> 标签的各个属性及其含义分别为如下:
| 属性 | 含义 |
|---|---|
| android:name | 分组的名字 |
| android:pivotX、android:pivotY | 缩放和旋转时候的 X 和 Y 的基准点。该值是相对于 vector 的 viewport 值来指定的 |
| android:translationX、android:translationY | X 轴和 Y 轴方向的平移位移。该值同样是相对于 viewport 值来指定的 |
| android:rotation | 旋转角度 |
| android:scaleX、android:scaleY | 分别在 X 轴和 Y 轴方向的缩放比例 |
接下来就是 <path> 标签了,<path> 标签定了的矢量图的绘制方法,包括绘制路径、颜色、边框样式等属性,它的所有属性及其含义如下:
| 属性 | 含义 |
|---|---|
| android:pathData | path 指令。指令格式参考:路径 |
| android:fillColor | path 填充颜色。一般为纯色,API 24 开始支持 Gradient 渐变色,详情请参考:vectordrawable-gradients-part1 和 vectordrawable-gradients-part1-2/ |
| android:fillAlpha | X 轴和 Y 轴方向的平移位移。该值同样是相对于 viewport 值来指定的 |
| android:fillType | path 的填充模式。默认是"noneZero",详情参考:非零环绕数规则和奇-偶规则 和 Android 关于Path的FillType |
| android:strokeWidth | path 边框宽度 |
| android:strokeColor | path 边框颜色 |
| android:strokeAlpha | path 边框透明度 |
| android:strokeLineCap | path 线头的形状。buff 平头、round 圆头和 square 方头。默认为 buff |
| android:strokeLineJoin | path 拐角的形状。miter 尖角、 bevel 平角和 round 圆角。默认为 miter |
| android:strokeMiterLimit | 设置拐角的形状为 miter 时,拐角的延长线的最大值。当小到一定程度时,miter 效果将会失效从而变成 bevel 效果 |
| android:trimPathStart | 从 path 起始位置截断路径的比率。取值范围为[0, 1] |
| android:trimPathEnd | 从 path 结束位置截断路径的比率。取值范围为[0, 1] |
| android:trimPathOffset | path 截取起点的偏移量。取值范围为[0, 1],由于 path 的起点和终点可以看作的首尾相连的,因此起点和终点是一起发生偏移的 |
12.2 用法示例
定义
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportHeight="600"
android:viewportWidth="600">
<path
android:strokeWidth="15"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:pathData="M5 10 l200 0"/>
<!--路径起点/终点往后偏移 10%,从新起点开始往后截断至 50% 的路径-->
<path
android:strokeWidth="15"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:trimPathStart="0.5"
android:trimPathOffset="0.1"
android:pathData="M5 80 l200 0"/>
<!--路径起点/终点往后偏移 50%,从新终点往前截断至 70% 部分-->
<!--也可以理解为从新起点往后截取至 70% 部分-->
<path
android:strokeWidth="15"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:trimPathEnd="0.7"
android:trimPathOffset="0.5"
android:pathData="M5 150 l200 0"/>
<path
android:strokeWidth="15"
android:strokeColor="#000000"
android:strokeLineCap="square"
android:strokeLineJoin="round"
android:pathData="M5 230 l200 0 l-100 30"/>
<path
android:strokeWidth="15"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter"
android:pathData="M5 290 l200 0 l-100 30"/>
<path
android:strokeWidth="15"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="miter"
android:strokeMiterLimit="7"
android:pathData="M5 350 l200 0 l-100 30"/>
<group
android:name="name"
android:translateX="10"
android:translateY="10"
android:rotation="90"
android:pivotX="300"
android:pivotY="300">
<path
android:name="noneZero"
android:strokeWidth="2"
android:strokeColor="#ffffff"
android:fillColor="#3C8FC1"
android:pathData="M20 120 a100 100 0 1 1 200 0 a100 100 0 1 1 -200 0
M40 120 a80 80 0 1 1 160 0 a80 80 0 1 1 -160 0"/>
<path
android:name="evenOdd"
android:strokeWidth="5"
android:strokeColor="#ffffff"
android:strokeAlpha="128"
android:fillColor="#3C8FC1"
android:fillType="evenOdd"
android:pathData="M260 120 a100 100 0 1 1 200 0 a100 100 0 1 1 -200 0
M280 120 a80 80 0 1 1 160 0 a80 80 0 1 1 -160 0"/>
</group>
</vector>
使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:srcCompat="@drawable/drawable_vector"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
效果图

12.3 兼容性问题
由于 VectorDrawable 是从 Android 5.0 之后引入的,如果想要在旧设备上运行,就必须要进行兼容性设置,由于篇幅有限,可以参考Android Vector曲折的兼容之路,这里不再赘述。
13. AnimatedVectorDrawable
当你以为 VectorDrawable 除了替代传统图标别无它用那你就实在 too young 了,与 VetorDrawable 一起诞生的还有 AnimatedVectorDrawable。还记得 VectorDrawable 中的 group 和 path 有个 name 属性吗?这时候它们就派上用场了,AnimatedVectorDrawable 可以通过 name 属性为 group 和 path 绑定一个属性动画,让这些 path 可以动起来,做出比较炫酷的动画效果。在 API 25 之前,因为渲染是在 UI 线程进行的的,因此性能不是很好,加上兼容性问题,目前使用得并不多。自从 API 25 之后,Google 将 AnimatedVectorDrawable 的渲染放在了 RenderThered 中执行,这显然减轻了不少 UI 线程的压力,Google 官方描述是:
This means animations in AnimatedVectorDrawable can remain smooth even when there is heavy workload on the UI thread.
因此,如果运行在新设备上,大家大可不必操心性能问题了。
13.1 语法
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@[package:]drawable/drawable_resource" >
<target
android:name="string"
android:animation="@[package:]animator/animator_resource" />
</animated-vector>
AnimatedVectorDrawable 的根标签为 <animated-vector>,android:drawable 属性用来指定 VectorDrawable 资源,<target> 标签将它的子元素 name 属性指定的 VectorDrawable 中需要添加动画效果的 path 或者 group 与 animation 属性中的 animator 资源绑定起来。animation 资源同样可以通过标签定义或者指向现有的 animator 文件。
13.2 用法示例
下面以前面文章中提到的 Demo 中的一个效果为例,展示一个 AnimatedVectorDrawable 的基本用法。
定义
需要添加动画效果的 VectorDrawable,一共有两个 path:
<?xml version="1.0" encoding="utf-8"?>
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/ic_arrow">
<target android:name="left">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="1000"
android:interpolator="@android:interpolator/anticipate_overshoot"
android:propertyName="translateX"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="-10"
android:valueType="floatType"/>
</aapt:attr>
</target>
<target android:name="right">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="1000"
android:interpolator="@android:interpolator/anticipate_overshoot"
android:propertyName="translateX"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="10"
android:valueType="floatType"/>
</aapt:attr>
</target>
</animated-vector>
使用
在代码中监听和控制动画:
class AnimatedVectorDrawableActivity : AppCompatActivity() {
private lateinit var animatable2Compat: Animatable2Compat
@RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_animated_vector_drawable)
var image = findViewById<ImageView>(R.id.image)
var animatedVectorDrawableCompat = AnimatedVectorDrawableCompat.create(this, R.drawable.drawable_animated_vector)
image.setImageDrawable(animatedVectorDrawableCompat)
animatable2Compat = image.drawable as Animatable2Compat
animatable2Compat.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
override fun onAnimationStart(drawable: Drawable?) {
Log.e("gpj", "onAnimationStart")
}
override fun onAnimationEnd(drawable: Drawable?) {
Log.e("gpj", "onAnimationEnd")
}
})
animatable2Compat.start()
}
override fun onDestroy() {
super.onDestroy()
animatable2Compat.stop()
}
}
效果图

14. AnimatedStateListDrawable
前面提到的 StateListDrawable 只能使用静态的资源在不同的状态之间进行切换,同样的,在 Android 5.0 之后,状态列表里可以使用动态资源了,它就是 AnimatedStateListDrawable。
14.1 语法
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:constantSize=["true" | "false"]
android:dither=["true" | "false"]
android:variablePadding=["true" | "false"]
android:autoMirrored=["true" | "false"]
android:enterFadeDuration="integer"
android:exitFadeDuration="integer">
<item
android:id="@[+][package:]id/resource_name"
android:drawable="@[package:]drawable/drawable_resource"
android:state_pressed=["true" | "false"]
android:state_focused=["true" | "false"]
android:state_hovered=["true" | "false"]
android:state_selected=["true" | "false"]
android:state_checkable=["true" | "false"]
android:state_checked=["true" | "false"]
android:state_enabled=["true" | "false"]
android:state_activated=["true" | "false"]
android:state_window_focused=["true" | "false"] />
<transition
android:drawable="@[package:]drawable/drawable_resource"
android:fromId="@[package:]id/item_name"
android:toId="@[package:]id/item_name" />
</selector>
可以发现,相对于 StateListDrawable,这里只多出一个 <transition> 标签,它的各个属性含义分别是:
android:drawable
定义或者指向一个 AnimatedVectorDrawable 资源。不难理解,这里需要指定状态变化的动画。
android:fromId 和 android:toId
分别指定状态变化的起始和结束 item 的 id。详情请看示例。
14.2 用法示例
定义
<?xml version="1.0" encoding="utf-8"?>
<animated-selector
xmlns:android="http://schemas.android.com/apk/res/android"
android:visible="true"
android:dither="true">
<!--勾选状态-->
<item
android:id="@+id/checked"
android:drawable="@drawable/ic_checked"
android:state_checked="true" />
<!--未勾选状态-->
<item
android:id="@+id/unchecked"
android:drawable="@drawable/ic_unchecked" />
<!--未勾选状态过度到勾选状态-->
<transition
android:drawable="@drawable/toggle_unchecked_checked"
android:fromId="@id/unchecked"
android:toId="@id/checked" />
<!--勾选状态过度到未勾选状态-->
<transition
android:drawable="@drawable/toggle_checked_unchecked"
android:fromId="@id/checked"
android:toId="@id/unchecked" />
</animated-selector>
正常状态到勾选状态过度动画:
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/ic_checked">
<!--打勾 path 动画-->
<target android:name="tick">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="200"
android:interpolator="@android:interpolator/accelerate_cubic"
android:propertyName="trimPathEnd"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
</aapt:attr>
</target>
<!--圆圈 path 动画-->
<target android:name="circle">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="500"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="strokeColor"
android:valueFrom="#A0A0A0"
android:valueTo="#1E9618"
android:valueType="intType" />
</aapt:attr>
</target>
</animated-vector>
勾选状态到未勾选状态过度动画:
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/ic_checked">
<!--打勾 path 动画-->
<target android:name="tick">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="100"
android:interpolator="@android:interpolator/decelerate_cubic"
android:propertyName="trimPathEnd"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType" />
</aapt:attr>
</target>
<!--圆圈 path 动画-->
<target android:name="circle">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="500"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="strokeColor"
android:valueFrom="#1E9618"
android:valueTo="#A0A0A0"
android:valueType="intType" />
</aapt:attr>
</target>
</animated-vector>
使用
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatCheckBox
android:layout_width="wrap_content"
android:layout_height="50dp"
android:button="@drawable/drawable_animated_state_list"
android:paddingEnd="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingStart="8dp"
android:text="I'm a CheckBox"
android:textColor="#ff00ff"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
效果图

15. AnimatedImageDrawable
大家都知道,在 Android 8.0 之前,如果我们要在设备上显示 GIF 图,一般都要借助一些第三方的库(如 Glide)来实现。记得 Android 8.0 刚发布的时候,提到一个 ImageDecoder 类,它除了可以解析 PNG、JEPG 类型的文件之外,还可以解析 WebP 和 GIF,而 GIF 文件解析出来的正是 AnimatedImageDrawable。由于目前资源有限,Google 也没有提供使用 XML 来定义 AnimatedImageDrawable 的例子,这里就在 Kotlin 代码里面介绍 AnimatedImageDrawable 和 ImageDecoder 的简单使用。
使用
@RequiresApi(Build.VERSION_CODES.P)
class AnimatedImageDrawableActivity : AppCompatActivity() {
var mAnimatedImageDrawable: AnimatedImageDrawable? = null
private val cacheAsset: CacheAsset by lazy {
CacheAsset(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_animated_image_drawable)
button.setOnClickListener {
ImageDecoder.createSource(cacheAsset.file("gif_example.gif")).also { source ->
ImageDecoder.decodeDrawable(source).also { drawable ->
image.setImageDrawable(drawable)
if(drawable is AnimatedImageDrawable) {
mAnimatedImageDrawable = drawable
drawable.start()
}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
mAnimatedImageDrawable?.run { stop() }
}
}
效果图

到此,常见的 Drawable 的用法已经全部讲完了,如果要加深理解,建议把 Demo 跑一遍。
文章转载自:
附:文章中的 Demo 地址:https://github.com/guanpj/DrawableDemo
本文详细介绍了Android中各种Drawable的使用方法,包括BitmapDrawable、ShapeDrawable、GradientDrawable、StateListDrawable、LayerDrawable等,涵盖基本语法、属性解释及示例代码。
518

被折叠的 条评论
为什么被折叠?



