开发艺术之Drawable
一、Drawable 简介
Drawable 是一个抽象类,它是所有 Drawable 对象的基类,每个具体的 Drawable 都是它的子类,比如 ShapeDrawable、BitmapDrawable 等,Drawable 层次关系如图:
如何使用 Drawable:
- 一般都是通过 XML 来定义
- 也可以在代码中创建
关于 Drawable 的内部宽/高:
- 通过 getIntrinsicWidth 和 getIntrinsicHeight 获取
- 不是所有的 Drawable 都有内部宽/高,比如一个颜色所形成的 Drawable
- Drawable 的内部宽/高不等于它的大小,一般来说 Drawable 是没有大小概念的。当用作 View 的背景时,Drawable 会被拉伸至 View 的同等大小
二、Drawable 的分类
1、BitmapDrawable
这是最简单的 Drawable,它表示一张图片。我们可以在代码中引用原始的图片,也可以通过 XML 的方式来描述它,通过 XML 来描述的 BitmapDrawable 可以设置更多的效果:
bitmap
|- android:src="@drawable/res_id"
|- 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:tileMode="[disabled | repeat | mirror | clamp]"
-
android:src
图片的资源 id
-
android:antialias
是否开启图片抗锯齿功能。开启后图片会变得更加平滑,同时会在一定程度上降低图片的清晰度,但是这个降低的幅度较低可以忽略,应此需要开启
-
android:dither
是否开启抖动效果。让高质量的图片在低质量的屏幕上还能保持较好的显示效果,应此需要开启
-
android:filter
是否开启过滤效果。当图片尺寸被拉伸或者压缩时,开启过滤效果可以保持较好的显示效果,应此需要开启
-
android:gravity
当图片小雨容器的尺寸时,设置此选项可以对图片进行定位。不同选项可以通过 "|"来组合使用
-
android:tileMode
平铺模式。disable 为关闭平铺模式,也是默认值;repeat 表示水平和竖直方向上的平铺效果;mirror 表示水平和竖直方向上的镜面投影效果;clamp 表示图片四周的像素会扩散到周围区域
使用方法:
- xml 定义:
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:antialias="true"
android:dither="true"
android:filter="true"
android:src="@drawable/haha" />
- 代码中定义
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.haha);
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bitmap);
bitmapDrawable.setAntiAlias(true);
bitmapDrawable.setDither(true);
ivShow.setImageDrawable(bitmapDrawable);
2、NinePatchDrawable
它表示一张 .9 格式的图片,该格式可以自动根据所需的宽/高进行相应的缩放并且保证不失真
<nine-patch
|- src="@drawable/9_png_resid"
|- dither="[true | false]" />
它在 XML 中属性的含义与 BitmapDrawable 中对应属性的含义相同。另外,在 bitmap 标签中也可以使用 .9 图片,即 BitmapDrawable 也可以代表一个 .9 格式图片
3、ShapeDrawable
ShapeDrawable 也是一种很常见的 Drawable,可以理解为通过颜色来构造的图形,语法如下所示:
<?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:topLeftRaidus="integer"
android:topRightRaidus="integer"
android:bottomLeftRaidus="integer"
android:bottomRightRaidus="integer" />
<gradient
android:angle="integer"
android:centerX="integer"
android:centerY="integer"
android:centerColor="color"
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>
-
android:shape
表示图形的形状,有四个选项:rectangle(矩形)、oval(椭圆)、line(横线)、ring(圆环),它的默认值是矩形。
另外 line 和 ring 这两个选项必须要通过 <stroke> 标签来指定线的宽度和颜色。
最后,针对 ring 这个形状,有 5 个特殊的属性:
ring 的属性值.png
-
< corners >
表示 shape 的四个角的角度,它只适用于矩形 shape,用 px 表示,它有如下五个属性:
-
android:radius
:为四个角同事设定相同的角度。优先级比以下4个属性要 -
android:topLeftRadius
:左上角的角度。 -
android:topRightRadius
:右上角的角度。 -
android:bottomLeftRadius
:左下角的角度。 -
android:bottomRightRadius
:右下角的角度。
-
-
< gradient >
它与 <solid> 标签互斥,其中 solid 表示纯色填充,gradient 表示渐变效果,gradient 有如下几个属性:
-
android:angle
:渐变的角度。默认为0。值必须为45的倍数。0表示从左到右,90表示从下到上。 -
android:centerX
:渐变的中心点的X坐标。 -
android:centerY
:渐变的中心点的Y坐标。 -
android:startColor
:渐变的起始色。 -
android:centerColor
:渐变的中间色。 -
android:endColor
:渐变的结束色。 -
android:gradientRadius
:渐变半径。仅当android:type="radial"时有效 -
android:useLevel
:一般为false,当 Drawable 作 StateListDrawable 时为true -
android:type
:渐变的类别,可选值:linear(线性渐变)、radial(径向渐变)、sweep(扫描式线渐变)
-
-
< solid >
纯色填充,通过 android:color 即可指定 shape 中填充的颜色
-
< stroke >
Shape 的描边,有如下几个属性:
-
android:width
:描边的宽度,越大则 shape 的边缘线久看会看起越粗 -
android:color
:描边的颜色 -
android:dashWidth
:组成虚线的线段的宽度 -
android:dashGap
:组成虚线之间的线段之间的间隔,越大则虚线看起来空隙越大
如果
android:dashWidth
和android:dashGap
有任何一个为 0,则虚线效果将不能生效。<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#ff0000" /> <stroke android:width="3dp" android:color="#00ff00" android:dashWidth="2dp" android:dashGap="2dp" /> </shape>
-
-
< padding >
与四周空白的距离
-
< size >
设置图形的固有大小,非最终大小
3、LayerDrawable
LayerDrawable 对应的 XML 标签是 < layer-list >,它表示一种层次化的 Drawable 集合。它通过将不同的 Drawable 放置在不同的层面上面从而达到一种叠加后的效果。语法如下:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable=""
android:id=""
android:bottom=""
android:left=""
android:right=""
android:top="" />
</layer-list>
一个 layer-list 可以包含多个 item,每个 item 表示一个 Drawable。常用属性如下所示:
- android:bottom、android:left、android:right、android:top。分别代表表示 Drawable 相对于 View 的上下左右的偏移量
- 可以通过 android:drawable 来直接引用一个已有的 Drawable 资源
实现 bitmap 的简单叠加:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_launcher_foreground" />
<item
android:bottom="50dp"
android:drawable="@drawable/ic_launcher_foreground"
android:left="50dp" />
<item
android:bottom="100dp"
android:drawable="@drawable/ic_launcher_foreground"
android:left="100dp" />
<item
android:bottom="150dp"
android:drawable="@drawable/ic_launcher_foreground"
android:left="150dp" />
</layer-list>
效果图:
layer-list 的应用.jpg
5、StateListDrawable
stateListDrawable 对应于 < slector >标签,它也表示 Drawable 集合,每个 Drawable 都对应着 View 的一种状态,它的语法如下所示:
selector
|-constantSize="[true | false]"
|-dither="[true | false]"
|-variablePadding="[true | false]"
|- item
| |- drawable="@drawable/drawable_id"
| |- state_pressed="[true | false]"
| |- state_focused="[true | false]"
| |- state_selected="[true | false]"
| |- state_hovered="[true | false]"
| |- state_checked="[true | false]"
| |- state_checkable="[true | false]"
| |- state_enabled="[true | false]"
| |- state_activated="[true | false]"
| |- state_window_focused="[true | false]"
|
-
android:constanSize
StateListDrawable 的固有大小是否不随着其状态的改变和改变,因为不同状态的 Drawable 的固有大小不同。True 表示 StateListDrawable 的固有大小保持不变,此时它的固有大小是内部所有 Drawable 的固有大小的最大值,false 则会随着状态的改变而改变。此默认值为 false。
-
android:dither
是否开启抖动效果。此选项默认为 ture。
-
android:variablePadding
表示 StateListDrawable 的 padding 是否随着其状态的改变而改变。true 表示会随着状态的改变而改变,false 表示 padding 是内部所有 Drawable 的 padding 的最大值。此选项默认值为 false,并且不建议开启此选项。
< item >标签表示一个具体的 Drawable,其中 android:drawable 是一个已有 Drawable 的资源id,剩下的属性表示 View 的各状态,常见状态如下:
View 的常见状态.png示例代码如下:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/button_pressed" android:state_pressed="true" />
<item android:drawable="@drawable/button_focused" android:state_focused="true" />
<item android:drawable="@drawable/button_normal" />
</selector>
系统会按照从上到下的顺下查找,直到查找到第一条匹配的 item。一般来说,默认的 item 都应该放在 selecoor 的最后一条并不附带任何状态。
6、LevelListDrawable
对应于 < level-list > 标签,它表示一个 Drawable 集合,集合中的每个 Drawable 都对应一个等级。根据不同的等级,LevelListDrawable 会切换为对应的 Drawable,它的语法如下:
level-list
|- item
| |- drawable="@drawable/drawable_id"
| |- maxLevel="integer"
| |- minlevel="integer"
-
android:maxLevel
对应最大值,取值范围 0~10000,默认为0
-
android:minlevel
对应最小值,取值范围 0~10000,默认值为0
//在Drawable文件夹中创建bg_level.xml
<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:maxLevel="1" android:drawable="@drawable/image1" />
<item android:maxLevel="2" android:drawable="@drawable/image2" />
<item android:maxLevel="3" android:drawable="@drawable/image3" />
</level-list>
//在activity_main.xml中设置为ImageView背景
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/bg_level"/>
//在MainActivity调用setImageLevel()
ImageView imageView = (ImageView) findViewById(R.id.image);
imageView.setImageLevel(2);
运行结果:ImageView的背景为image2
7、TransitionDrawable
对应于 < transition > 标签,用于实现两个 Drawable 之间的淡入淡出效果,语法如下所示:
transition
|- item
| |- drawable="@drawable/drawable_id"
| |- id="@+id/xxx_id"
| |- top="dimension"
| |- left="dimension"
| |- right="dimension"
| |- bottom="dimension"
|
上面语法中的属性都已经介绍过了,其中 android:top、left、right、bottom 仍然表示 Drawable 四周的偏移量。
- 使用方法:
//在Drawable文件夹中创建bg_tran.xml
<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/image1"/>
<item android:drawable="@drawable/image2"/>
</transition>
//在activity_main.xml中设置为ImageView背景
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/bg_tran"/>
//在MainActivity调用startTransition()
ImageView imageView = (ImageView) findViewById(R.id.image);
TransitionDrawable td = (TransitionDrawable) imageView.getDrawable();
td.startTransition(3000);
上面通过 startTransition 和 reverseTransition 方法实现淡入淡出的效果以及它的逆过程。
8、InsetDrawable
对应于 < inset > 标签,它可以将其他 Drawable 内嵌到自己当中,并可以在四周留出一定的间距。语法如下所示:
inset
|- drawable="@drawable/drawable_id"
|- visible="[true | false]"
|- insetTop="dimension"
|- insetLeft="dimension"
|- insetRight="dimension"
|- insetBottom="dimension"
|
- android:drawable,代表所引用的资源id
- android:insetTop ,表示距离容器的上边距。其他同理
- android:visible,是否留有边距
当一个 View 希望自己的背景比自己的实际区域小时,可以用采用 InsetDrawable 来实现,示例如下:
// 在 drawable 文件夹下创建 inset_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/image"
android:insetLeft="10dp"
android:insetTop="20dp"
android:insetRight="30dp"
android:insetBottom="40dp"
android:visible="true" />
// 引用 inset_drawable
<View
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@drawable/inset_drawable" />
效果图:
insetDrawable 效果图.jpg
9、ScaleDrawable
对应于 < scale > 标签,它可以根据自己的等级将制定的 Drawable 缩放到一定比例,语法如下:
scale
|- drawable="@drawable/drawable_id"
|- scaleGravity="[top | bottom | left | right |
center_vertical | center_horizontal | center |
fill_vertical | fill_horizontal | fill |
clip_vertical | clip_horizontal]"
|- scaleWidth="percentage"
|- scaleHeight="percentage"
|
- android:drawable,表示引用的资源id
- android:scaleGravity,等同于 bitmapDrawable 的 android:gravity
- android:scaleWidth/android:scaleHeight,表示缩放比例,用百分比形式表示
示例:将一张图片缩小为原来的 30%,代码如下:
// 在 drawable 文件夹下创建 inset_drawable.xml
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/image"
android:scaleWidth="70%"
android:scaleHeight="70%"
android:scaleGravity="center" />
// 引用 inset_drawable.xml
<ImageView
android:id="@+id/image"
android:layout_width="300dp"
android:layout_height="300dp"
android:src="@drawable/scale_drawable" />
// 在代码中设置 ScaleDrawable 的 level
ImageView mImageView = findViewById(R.id.image);
ScaleDrawable drawable = (ScaleDrawable) mImageView.getDrawable();
drawable.setLevel(1);
注意:
- level 的取值范围为 0~1000,默认值为 0
- 若 level 为 0,那么 scaleDrawable 是不可见的,所以需要在代码中 通过 setLevel 设置
10、ClipDrawable
对应于 < clip > 标签,它可以根据自己当前的等级来裁剪另一个 Drawable,裁剪方向通过 android:clipOrientation 和 android:gravity 这两个属性来共同控制,语法如下:
scale
|- drawable="@drawable/drawable_id"
|- gravity="[top | bottom | left | right |
center_vertical | center_horizontal | center |
fill_vertical | fill_horizontal | fill |
clip_vertical | clip_horizontal]"
|- clipOrientation="[vertical | horizontal]"
|
其中 clipOrientation 表示裁剪方向,有水平和竖直两个方向。gravity 比较复杂,需要和 clipOrientation 一起才能发挥作用,另外它可通过 “|” 来组合使用
ClipDrawable 的 gravity 属性.png使用示例:
// 在 drawable 文件夹中创建 clip_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
android:clipOrientation="vertical"
android:drawable="@drawable/image"
android:gravity="bottom" />
// 使用 clip_drawable.xml 作为图像
<ImageView
android:id="@+id/image"
android:layout_width="300dp"
android:layout_height="300dp"
android:src="@drawable/clip_drawable" />
// 在代码中设置等级
mImageView = findViewById(R.id.image);
ClipDrawable drawable = (ClipDrawable) mImageView.getDrawable();
drawable.setLevel(7000);
效果图:
ClipDrawable 效果图.png
对于 ClipDrawable 来说,Drawable 的等级(level)为 0 时表示完全裁剪,即整个 Drawable 都不见了,而等级 10000 表示不裁剪。
三、自定义 Drawable
- Drawable 的使用范围单一,一个是作为 ImageView 中的图像来显示,另外一个就是作为 View 的背景,大多数情况下 Drawable 都是以 View 的背景这种形式出现的。
- 可以通过重写 Drawable 的 draw 方法来自定义 Drawable
- 但是,我们通常没必要去自定义 Drawable,因为自定义的 Drawable 无法复用
如果在某些特殊情况下,我们还是想要自定义 Drawable,也可以,下面给出示例代码:
//自定义Drawable
public class CustomDrawable extends Drawable {
private Paint mPaint;
public CustomDrawable(int color) {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(color);
}
@Override
public void draw(Canvas canvas) {
final Rect rect = getBounds();
float cx = rect.exactCenterX();
float cy = rect.exactCenterY();
canvas.drawCircle(cx, cy, Math.min(cx, cy), mPaint);
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
invalidateSelf();
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
invalidateSelf();
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
在自定义 Drawable 时注意:
- draw、setAlpha、setColorFilter、getOpacity 这几个方法都是必须要实现的
- 当自定义 Drawable 时绘制一张图片时,最好重写 getIntrinsicWidth 和 getIntrinsicHeight 方法。(上面例子中 Drawable 是颜色填充,所以没有固定大小,就不需要重写)
- Drawable 的内部大小不等于 Drawable 的实际区域大小,Drawable 的实际区域大小可以通过它的 getBounds 方法来得到,一般来说和它的 View 的尺寸相同