在Android开发中恰当地使用Drawable能节省很多工作量, Drawable有很多子类各自都有自己的适用范围, 我这里对它们的用法及适用场景做个总结, 方便日后查看

Drawable是什么

官方对它的解释是:

A Drawable is a general abstraction for “something that can be drawn.”

就是说它代表了一种可以被画出来的抽象的东西.
最简单的Drawable可以由位图(jpg, png, gif)构造, 除此之外还可以由: .9.png图片, 颜色, 形状, 多个Drawable组合
大部分Drawable对象都同时对应一个Java类与一个XML标签, 通常情况我们都是直接使用XML这种方式来定义Drawable, 因为这样更方便, 但有时也需要自定义Drawable类

Drawable 与 Bitmap

在刚开始学习Android开发时, 对于Drawable与Bitmap这两个概念搞不太清楚它们之间的区别, 这里先简单说一下它们之前的区别:

  1. Bitmap直译过来就是位图, 它其实就代表了Png/jpg/gif这些位图, 仅此而已
  2. Drawable是一种很抽象的概念, 代表了所有可以被画出来的东西. 它可以是Bitmap(BitmapDrawable), 也可以不是(比如:颜色/形状/…)

Drawable与它的子类们(UML结构图)

我画了一个Drawable的类派生结构图, 通过这张图能清楚的看出都有哪些Drawable及它们之间的关系.

从上图中能够很清楚的看出来所有的Drawable之间的关系, 下面详细介绍一下几个比较常用的.

BitmapDrawable

BitmapDrawable应该是最简单的Drawable了, 它其实就是表示一张力片, 对应在XML中的标签就是<bitmap/>:

BitmapDrawablelink
1
2
3
4
5
6
7
8
9
10
11
12
<?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"] />

  1. src指定源图片
  2. antialias是否开启抗锯齿, 默认不开启
  3. dither是否开启dither效果, 当图片的像素配置与手机屏幕的像素配置不一致时, 开启这个选项可以让高质量的图片在低质量的屏幕上有较好的显示效果, 默认开启
  4. filter是否开启过滤效果, 当图片被拉伸或压缩尺寸时, 开启这个效果可以保持较好的图片显示效果, 默认开启
  5. gravity 当bitmap的尺寸比它的容器小时, 如何来放置自己, 用来指定居左/居中/居右/…, 与带内容的View的Gravity一样. 但不知道为什么, 通过我自己的实验设置这个属性并没有生效
  6. tileMode 平铺模式, 当bitmap比它的容器小时, 指定如何填充容器中的其它部分, 这个与BitmapShader中的tileMode的作用相同, 具体可到这里BitmapShader看一下各个模式下的效果. 默认不开启
  7. mipMap 这个选项应该只有在你的bitmap作为应用的lancher图标时才需要去开启, 具体可以了解一下Mipmap纹理技术简介默认值是False

ColorDrawable

这个最简单, 你只需要指定一下这个Drawable的颜色即可.

1
<drawable name="color_drawable_sample">#ff00ff</drawable>

ShapeDrawable

ShapeDrawable应该是最常用的一个Drawable了, 我们经常用它来定义各种按钮的背景, 它的语法也比较复杂:

ShapeDrawablelink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?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="float"
android:centerY="float"
android:centerColor="integer"
android:endColor="color"
android:gradientRadius="integer"
android:startColor="color"
android:type=["linear" | "radial" | "sweep"]
android:useLevel=["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>

1. shape用来表示图形的形状, 有4个选项:rectangle(矩形)/oval(椭圆)/line(线)/ring(圆环), shape的默认值是rectangle. ring有5个独有的属性:

属性 描述
android:innerRadius 圆环的内半径, 优先级比android:innerRadiusRatio高.
android:thickness 圆环的厚度, 即外半径减去内半径的长度,优先级比android:thicknessRatio高.
android:innerRadiusRadio 内半径占整个Drawable宽度的比例, 默认值为9.
android:thicknessRadio 厚度占整个Drawable宽度的比例, 默认值为3.
android:useLevel 用作LevelListDrawable为true, 其它情况下都要设为false, 否则可能不会显示

2. corners 表示4个角的角度, 只适用于rectangle, android:radius的优先级最低.
3. gradient 它与solid标签是相互排斥的, solid表示纯色填充, gradient是渐变填充.

属性 描述
android:angle 渐变的角度, 默认值为0, 取值必须是45的位数, 0表示从左到右, 90表示从上到下, …
android:centerX 渐变中心点的横坐标
android:centerY 渐变中心点的纵坐标
android:startColor 渐变起始色
android:centerColor 渐变中间色
android:endColor 渐变结束色
android:gradientRadius 渐变的半径, 仅当android:type=”radial”时有效
android:useLevel 只有当作StateListDrawable使用时为true, 其它情况下都要设为false
android:type 渐变的类型, linear(线性渐变)/radial(圆形渐变)/sweep(扫描渐变)

4. solid 纯色填充
5. stroke 线条的属性

属性 描述
android:color 线条颜色
android:width 线条粗细
android:dashWidth 虚线线段的宽度
android:dashGap 虚线线段间的间隔

dashWidthdashGap中有任何一个为0, 则虚线效果不生效.

6. padding 表示使用此drawable的view的空白间隔.
7. size 设置drawable的固有大小, 但是若此Drawable作为View的背景时还是会被缩放到与view相同大小.

LayerDrawable

对应的xml标签是layer-list, 它表示一种层次化的Drawable组合, 有点类似于FrameLayout , 像PS中图层的概念.

LayerDrawablelink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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" />

<item
...
/>

</layer-list>

其中item的上下左右4个属性与view的margin效果类似, 看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 红色阴影 -->
<item
android:left="20dp"
android:top="30dp">

<shape>
<solid android:color="@android:color/holo_red_light" />
<corners android:radius="10dp" />
</shape>
</item>
<!-- 白色前景 -->
<item
android:bottom="20dp"
android:right="10dp">

<shape>
<solid android:color="#FFFFFF" />
<corners android:radius="10dp" />
</shape>
</item>
</layer-list>

实现的效果如下:

StateListDrawable

对应标签selector, 它也是一个drawable集合, 用来指定在View特定状态下选取特定的Drawable使用, 比如在点击时是一种背景, 正常情况下又是一种背景.

StateListDrawablelink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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"] >

<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>

其中, android:contentSize属性用来控制, 当在切换Drawable时StateListDrawable的大小是否改变(不同的Drawable的固有大小可能不同), 若值为True则此StateListDrawable的大小为此其对应的所有Drawable中size最大的那个.
android:variablePadding属性用来控制, 此组内所有Drawable的padding值是否使用各自设置的, 默认值为false, 即所有Drawable对应的padding值为所有Drawable中padding值最大的那个.

LevelListDrawable

LevelListDrawable也是一个组合Drawable, 每个item对应一个maxLevelminLevel, 通过Drawable的方法setLevel(int level)来改变当前LevelListDrawable的level值, level值的大小落在了哪个item的max/min level之间当前LevelListDrawable就显示哪个item对应的Drawable.
level值的大小范围在 0~10000之间.
另外, 针对ImageView若ImageView的src是一个LevelListDrawable则, ImageView可以通过方法setImageLevel直接改变level值. 它的语法结构如下:

LevelListDrawablelink
1
2
3
4
5
6
7
8
<?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>

TransitionDrawable

用来设置两个Drawable之间的淡入淡出效果, 对应<transition/>标签语法结构如下:

TransitionDrawablelink
1
2
3
4
5
6
7
8
9
10
11
<?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>

TransitionDrawable对应两个item, 属性比较简单这里不再赘述. 具体使用如何使用看下面的例子:

1
2
3
4
5
6
7
8
9
<ImageButton
android:id="@+id/button"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:src="@drawable/transition" />

ImageButton button = (ImageButton) findViewById(R.id.button);
TransitionDrawable drawable = (TransitionDrawable) button.getDrawable();
drawable.startTransition(500); //500毫秒的时间在这个item间渐变.

InsetDrawable

通常情况下一个View的背景会被你设置的 background colorbackground drawable 所完全覆盖, 若你想让这个view的背景比它的实际区域小, 就可以通过<inset/>标签来实现. 而且这种情况你是无法通过padding来实现的. 语法结构如下:

InsetDrawablelink
1
2
3
4
5
6
7
8
<?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" />

属性很简单, 这里不再赘述.

ScaleDrawable

ScaleDrawable用来缩小一个Drawable, 是的只能缩小. 先看一下它的语法结构:

1
2
3
4
5
6
7
8
9
<?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" />

关键的属性就scaleHeightscaleWidth这两个属性对应的值类似这样:xx%, 比如20%, 表示缩小20%(并不是大小缩小到原来的20%).
使用ScaleDrawable有个特别需要注意的地方, ScaleDrawable的显示效果同时受到Drawable的level属性的影响.可以看一下ScaleDrawable的onBoundsChange的方法, 其中计算宽度的一段代码:

1
2
3
4
5
int w = bounds.width();
if (mState.mScaleWidth > 0) {
final int iw = min ? d.getIntrinsicWidth() : 0;
w -= (int) ((w - iw) * (MAX_LEVEL - level) * mState.mScaleWidth / MAX_LEVEL);
}

可以看到实际上宽度w与level正相关, 若level=MAX_LEVEL(MAX_LEVEL = 10000), 则无缩小效果. mState.mScaleWidth就是xml中对应的scale百分比, 可以看到这个值原大, 则缩小的越大.
通常我们在使用ScaleDrawable时可以只通过xml中的scaleHeightscaleWidth来控制缩小效果, 但是记得要通过代码将这个drawable的level值设置为非0(level值默认为0)通常设为1即可, 这样可以近似实现实际缩小的百分比与scaleHeight, scaleHeight设置的一致.

ClipDrawable

对应与<clip/>标签, 它可以根据自己当前的等级来裁剪另一个Drawable, 语法结构如下:

ClipDrawablelink
1
2
3
4
5
6
7
8
9
<?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"] />

`

其中, clipOrientation表示裁剪的方向, drawable表示要裁剪的对象, 至于裁剪的比例由Drawable的level属性来控制, level值为0则表示完全裁剪即看不到drawable, level值为10000则表示不裁剪, 为中间的值则表示部分裁剪, 值越大裁剪的越少.

总结

为什么要总结Drawable呢? 主要是为了加深对常用Drawable的使用及适用范围的了解, 我们在开发时有些效果如果知道利用Drawable的话实现起来会更简单, 但通常情况下我们对这些Drawable掌握的并不是很清楚. 关于自定义Drawable的内容在这篇没有写, 放到下篇再说.

参考

  1. Drawable Resources
  2. Android开发艺术探索

版权声明

文章版权归本人所有,如需转载需在明显位置处保留作者信息及原文链接 !