本篇文章主要介绍以下几个知识点:
- Drawable 简介
- Drawable 的分类
- 自定义 Drawable
Drawable,使用简单,比自定义 View 成本低,非图片类型的 Drawable 占用空间小。
6.1 Drawable 简介
Drawable,一种图像的概念(不一定是图片,如通过颜色也可构造出图像)。
在 Android 设计中,Drawable 是一个抽象类,是所有 Drawable 对象的基类,其层次关系如下:
通过 getIntrinsicWidth
和 getIntrinsicHeight
可获取 Drawable 的内部宽高。一张图片的宽高是其内部宽高,但一个颜色形成的 Drawable 没有内部宽高概念。(注:Drawable 的内部宽高不等同于其大小,一般是无大小概念,如作 View 的背景时会被拉伸至 View 的同等大小)
6.2 Drawable 的分类
Drawable 的种类繁多,如 BitmapDawable
、ShapDrawable
、LayerDrawable
、StateListDrawable
等。
6.2.1 BitmapDrawable
表示一张图片。实际开发中直接引用原始图片即可,也可通过 XML 的方式来描述,如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- src 图片的资源id
antialias 是否开启图片抗锯齿功能
dither 是否开启抖动效果
filter 是否开启过滤效果
gravity 对图片进行定位
mipMap 纹理映射(默认为 false)
tileMode 平铺模式(默认 disabled),开启后 gravity 属性会被忽略-->
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@mipmap/ic_header"
android:antialias="true"
android:dither="true"
android:filter="true"
android:gravity="center"
android:mipMap="false"
android:tileMode="disabled"/>
其中 gravity 属性的可选项如下:
其中 tileMode 属性开启后各效果如下:
NinePatchDrawable,.9格式的图片,可自动根据所需的宽高进行相应的缩放而不失真,和 BitmapDrawable 一样,在实际开发中直接引用图片即可,也可通过 XML 来描述如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- src 图片的资源id
dither 是否开启抖动效果 -->
<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/message_left"
android:dither="true" />
实际使用中在 bitmap 标签中也可用 .9 图,即 BitmapDrawable 也可代表一个 .9 格式的图片。
6.2.2 ShapeDrawable
通过颜色来构造的图形,可以是纯色的图形,也可以是具有渐变效果的图形。其语法如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- shape 图形的形状:rectangle 矩形、 oval 椭圆、
line 横线、 ring 圆环
注:ling 和 ring 需要<stroke>标签来指定线的宽度和颜色等信息-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 表示 shape 的四个角的角度,只适用于矩形 shape
radius 为四个角设定相同的角度,优先级低
其他分别指设定左上角、右上角、左下角、右下角的角度-->
<corners
android:radius="32px"
android:topLeftRadius="32px"
android:topRightRadius="32dp"
android:bottomLeftRadius="32px"
android:bottomRightRadius="32px"/>
<!-- 表渐变效果,与 <solid> 标签是互相排斥的
angle 渐变的角度,默认0,其值必须为45的倍数(0:从左到右 90:从上到下)
centerX、centerY 渐变的中心点的横坐标、纵坐标
startColor、centerColor、endColor 渐变的起始、中间、结束色
gradientRadius 渐变的半径,仅当 android:type="radial" 时有效
type 渐变的类别,1.linear:线性 2.radial:径向 3.sweep:扫描线
useLevel 一般为false,当Drawable作为StateListDrawable使用时为true -->
<gradient
android:angle="0"
android:centerX="32"
android:centerY="32"
android:startColor="@color/colorPrimary"
android:centerColor="@color/colorPrimaryDark"
android:endColor="@color/colorAccent"
android:gradientRadius="32dp"
android:type="linear"
android:useLevel="false"/>
<!-- 表示包含它的 View 的空白-->
<padding
android:left="32dp"
android:right="32dp"
android:top="32dp"
android:bottom="32dp"/>
<!-- shape 的大小
width、height 宽、高
(注:ShapeDrawable 的固有宽高,但作为View背景时无效)-->
<size
android:width="32dp"
android:height="32dp" />
<!-- 表示纯色填充
color 指定 shape 中填充的颜色-->
<solid
android:color="@color/colorAccent" />
<!-- 描边
width 描边的宽度
color 描边的颜色
dashWidth 组成虚线的线段的宽度
dashGap 组成虚线的线段间的间隔
(注:dashWidth 和 dashGap 有任何一个为 0,则虚线效果无效)-->
<stroke
android:width="32dp"
android:color="@color/colorAccent"
android:dashWidth="32dp"
android:dashGap="32dp"/>
</shape>
其中 shape 属性中的 ring 这个形状,有 5 个特殊属性如下:
其中 type 属性各类别的区别如下:
6.2.3 LayerDrawable
其对应的 XML 标签是<layer-list>,表示一种层次化的 Drawable 集合,一种叠加的效果,其语法如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- 一个 layer-list 中可以包含多个 item,每个 item 表示一个 Drawable
layer-list 有层次的概念,下面的 item 会覆盖 上面的 item -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- drawable 可以直接引用已有的 Drawable 资源
top、bottom、left、right:Drawable相对于View的上下左右的偏移量,单位为像素-->
<item
android:drawable="@drawable/drawable_test"
android:id="@id/btn_chapter06"
android:top="@dimen/drawable_dimen"
android:right="@dimen/drawable_dimen"
android:bottom="@dimen/drawable_dimen"
android:left="@dimen/drawable_dimen"/>
</layer-list>
下面是一个 layer-list 具体使用例子,它实现了微信中的文本输入框效果,代码如下:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#0ac39e"/>
</shape>
</item>
<item android:bottom="6dp">
<shape android:shape="rectangle">
<solid android:color="#ffffff"/>
</shape>
</item>
<item
android:bottom="1dp"
android:left="1dp"
android:right="1dp">
<shape android:shape="rectangle">
<solid android:color="#ffffff"/>
</shape>
</item>
</layer-list>
运行效果如下:
6.2.4 StateListDrawable
其对应的 XML 标签是<selector>,也是表示 Drawable 集合,每个 Drawable 对应着 View 的一种状态,其语法如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- constantSize StateListDrawable 的固有大小是否随着其状态的改变而改变
默认 false,即随着状态的改变而改变
dither 是否开启抖动效果 默认 true
layer-variablePadding StateListDrawable 的 padding是否随着其状态的
改变而改变,默认 false
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:constantSize="true"
android:dither="true"
android:variablePadding="false">
<!-- drawable 已有 Drawable 的资源id
其他表示 View 的各种状态 -->
<item
android:drawable="@drawable/drawable_test"
android:state_pressed="true"
android:state_focused="true"
android:state_hovered="true"
android:state_selected="true"
android:state_checkable="true"
android:state_checked="true"
android:state_enabled="true"
android:state_activated="true"
android:state_window_focused="true" />
</selector>
其中 <item> 标签中的 View 的常见状态如下:
StateListDrawable 主要用于设置可单击的 View 的背景,如 Button,常见的例子如下:
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:drawable="@drawable/drawable_pressed"
android:state_pressed="true" />
<item
android:drawable="@drawable/drawable_pressed"
android:state_focused="true" />
<!-- default -->
<item android:drawable="@drawable/drawable_normal" />
</selector>
6.2.5 LevelListDrawable
其对应的 XML 标签是<level-list>,也是表示 Drawable 集合,每个 Drawable 都有一个等级的概念。LevelListDrawable 根据不同的等级切换对应的 Drawable,其语法如下:
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- maxLevel、minLevel 指定等级范围,在最小值和最大值之间
的等级会对应此 item 中的 Drawable-->
<item
android:drawable="@drawable/drawable_test"
android:maxLevel="100"
android:minLevel="32"/>
</level-list>
LevelListDrawable 作为 View 的背景时可通过 Drawable 的 setLevel
来设置不同的等级,作为 ImageView 的前景时可通过 ImageView 的 setImageLevel
来切换 Drawable。
Drawable 等级范围为 0 ~ 10000,例子如下:
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/drawable_off"
android:maxLevel="0" />
<item
android:drawable="@drawable/drawable_on"
android:maxLevel="1" />
</level-list>
6.2.6 TransitionDrawable
其对应的 XML 标签是<transition>,用于实现两个 Drawable 之间的淡入淡出效果,其语法如下:
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<!-- top、bottom、left、right 指 Drawable 四周的偏移量-->
<item
android:drawable="@drawable/drawable_test"
android:top="@dimen/drawable_dimen"
android:bottom="@dimen/drawable_dimen"
android:right="@dimen/drawable_dimen"
android:left="@dimen/drawable_dimen"/>
</transition>
使用步骤如下:
1. 定义 TransitionDrawable
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/drawable_test"/>
<item android:drawable="@drawable/drawable_test_02" />
</transition>
2. 设置为 View 的背景
<!-- 注:在 ImageView 中也可直接作为 Drawable 来用 -->
<TextView
android:id="@+id/tv_transition"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@drawable/chapter_06_transition_drawable"/>
3. 通过startTransition
和 reverseTransition
来开启效果及其逆过程
TextView tvTransition = (TextView) findViewById(R.id.tv_transition);
TransitionDrawable drawable = (TransitionDrawable) tvTransition.getBackground();
drawable.startTransition(2000);
运行效果如下:
6.2.7 InsetDrawable
其对应的 XML 标签是<inset>,可以将其他 Drawable 内嵌到自己当中,并可在四周留出一定的间距,其语法如下:
<!-- insetTop、insetBottom、insetRight、insetLeft
表示 顶部、底部、右边、左边 内凹的大小 -->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_test"
android:insetTop="@dimen/drawable_dimen"
android:insetBottom="@dimen/drawable_dimen"
android:insetRight="@dimen/drawable_dimen"
android:insetLeft="@dimen/drawable_dimen"/>
当一个 View 的背景比自己的实际区域小时可采用 InsetDrawable 实现(也可采用 LayerDrawable 来实现),如下:
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_test"
android:insetTop="15dp"
android:insetBottom="15dp"
android:insetRight="15dp"
android:insetLeft="15dp">
<shape android:shape="rectangle">
<solid android:color="#ff0000"/>
</shape>
</inset>
运行效果:
6.2.8 ScaleDrawable
其对应的 XML 标签是<scale>,它可以根据自己的等级(level)将指定的 Drawable 缩放到一定比例,其语法如下:
<!-- scaleGravity 等同于 shape 中的 android:gravity
scaleWidth、scaleHeight 指定对 Drawable 宽高的缩放比例,百分比表示 -->
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_test"
android:scaleGravity="center"
android:scaleHeight="32%"
android:scaleWidth="32%" />
ScaleDrawable 的默认等级为 0,即不可见,要 ScaleDrawable 可见则等级不为 0。如将一张图片缩小为原来的30%,如下:
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_test"
android:scaleGravity="center"
android:scaleHeight="70%"
android:scaleWidth="70%" />
然后设置 ScaleDrawable 的等级大于 0且小等于10000的值,如下:
TextView tvScale = (TextView) findViewById(R.id.tv_scale);
ScaleDrawable scaleDrawable = (ScaleDrawable) tvScale.getBackground();
scaleDrawable.setLevel(1);
6.2.9 ClipDrawable
其对应的 XML 标签是<clip>,它可以根据自己的等级(level)来剪裁另一个Drawable,其语法如下:
<!-- clipOrientation 剪裁方向:水平或竖直
gravity 需要和 clipOrientation 一起才能发挥作用 -->
<clip xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_test"
android:clipOrientation="horizontal"
android:gravity="center" />
其中 gravity 的属性如下:
下面实现将一张图片从上往下进行裁剪的效果:
1. 定义 ClipDrawable
<!-- 实现顶部的裁剪效果,方向设为竖直,gravity 设为 bottom -->
<clip xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/drawable_test"
android:clipOrientation="vertical"
android:gravity="bottom" />
2. 设置给 ImageView (也可作为 View 的背景)
<ImageView
android:id="@+id/iv_clip"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/chapter_06_clip_drawable"
android:layout_gravity="center_horizontal" />
3. 在代码中设置 ClipDrawable 的等级
ImageView ivClip = (ImageView) findViewById(R.id.iv_clip);
ClipDrawable clipDrawable = (ClipDrawable) ivClip.getDrawable();
// 0-10000,其中0表示完全裁剪,10000表示不裁剪
clipDrawable.setLevel(5000);
运行效果如下:
6.3 自定义 Drawable
Drawable 使用范围单一,作为 ImageView 的图像显示或者作为 View 的背景。
Drawable 的工作原理很简单,其核心是 draw
方法,可通过重写其 draw
方法来自定义 Drawable。(注:自定义的 Drawable 无法在 XML 中使用)
下面自定义一个圆形的 Drawable,它的半径会随着 View 的变化而变化,可作为 View 的通用背景,如下:
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(@NonNull Canvas canvas) {
final Rect r = getBounds();
float cx = r.exactCenterX();
float cy = r.exactCenterY();
canvas.drawCircle(cx, cy ,Math.min(cx, cy), mPaint);
}
@Override
public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
mPaint.setAlpha(alpha);
invalidateSelf();
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
invalidateSelf();
}
@Override
public int getOpacity() {
// not sure, so be safe
return PixelFormat.TRANSLUCENT;
}
}
在代码中设置自定义的 Drawale:
ImageView ivCustom = (ImageView) findViewById(R.id.iv_custom_drawable);
CustomDrawable customDrawable = new CustomDrawable(getResources().getColor(R.color.colorAccent));
ivCustom.setImageDrawable(customDrawable);
运行效果如下:
以上便是完整的自定义 Drawable 的流程。值得注意的是,自定义的 Drawable 有固定大小时最好重写 getIntrinsicWidth
和 getIntrinsicHeight
这两方法。
本篇文章就介绍到这。