文章目录
1. 概述
Drawable 是一种媒介,它可以把内容绘制到 Canvas 上。
- 一种可以在Canvas上进行绘制的抽象的概念
- 颜色、图片等都可以是一个Drawable
- Drawable可以通过XML定义,或者通过代码创建
- Android中Drawable是一个抽象类,每个具体的Drawable都是其子类
Drawable的优点
- 使用简单,比自定义View成本低
- 非图片类的Drawable所占空间小,能减小apk大小
Drawable的内部宽/高
- 通过
getIntrinsicWidth/Height
方法能获得内部宽/高,注意和setBounds
方法搭配使用 - 图片Drawable其内部宽高就是图片的宽高
- 颜色Drawable没有内部宽高的概念
- 内部宽高不等同于它的大小,一般Drawable没有大小概念(作为View背景时,会被拉伸至View的大小)
2. Drawable主要类型及创建
Drawable资源是可绘制到屏幕上的图形的一般概念,可以使用getDrawable(int)
之类的API检索该图形。有几种不同类型的Drawable:
类型 | 描述 |
---|---|
BitmapDrawable | 位图图形文件(.png, .jpg或.gif)。 |
NinePatchDrawable | 具有可拉伸区域的PNG文件,允许基于内容调整图像大小(.9 PNG)。 |
LayerDrawable | 管理其他可绘制项数组的Drawable。这些是按数组顺序绘制的,因此索引最大的元素是在顶部绘制的。 |
StateListDrawable | 不同的状态引用不同的位图图形(例如,当按下按钮时使用按下效果的图形)。 |
LevelListDrawable | 定义了一个可绘制项,该Drawable管理多个可替换的Drawable,每个可绘制项分配一个最大数值。 |
TransitionDrawable | 定义可绘制资源的XML文件,可在两个Drawable之间交叉淡出。 |
InsetDrawable | 该XML文件以指定的距离插入另一个Drawable。当视图需要一个比视图实际边界小的背景可绘制项时,这是非常有用的。 |
ClipDrawable | 定义一个drawable,根据这个Drawable的当前级别值剪辑另一个drawable。 |
ScaleDrawable | 定义可绘制项的XML文件,该文件根据另一个Drawable的当前级别值更改其大小 |
GradientDrawable | 定义几何形状(包括颜色和梯度)的XML文件。 |
注意:颜色资源也可以用作XML中的可绘制项。例如,当创建一个状态列表drawable时,可以为
android:drawable
属性引用一个颜色资源(android:drawable="@color/green")
Drawable继承关系说明:
-
直接继承的Drawable的子类则没有child
-
继承DrawableWrapper的子类只能有一个child
-
继承DrawableContainer的子类可以有多个child
-
本身LayerDrawable与DrawableContainer一样,可以有多个child。
android.graphics.drawable.DrawableInflater
类负责创建对应的Drawable对象,关键代码如下:
public final class DrawableInflater {
/**
* Version of {@link #inflateFromXml(String, XmlPullParser, AttributeSet, Theme)} that accepts
* an override density.
*/
@NonNull
Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, int density, @Nullable Theme theme)
throws XmlPullParserException, IOException {
// 1. xml形式:定义drawable标签,class属性值为自定义Drawable的完整类路径
if (name.equals("drawable")) {
name = attrs.getAttributeValue(null, "class");
if (name == null) {
throw new InflateException("<drawable> tag must specify class attribute");
}
}
// 2. 尝试解析系统Drawable的标签名
Drawable drawable = inflateFromTag(name);
if (drawable == null) {
// 3. 反射创建自定义Drawable
drawable = inflateFromClass(name);
}
drawable.setSrcDensityOverride(density);
// 4. 解析Drawable下的visible属性值
drawable.inflate(mRes, parser, attrs, theme);
return drawable;
}
}
可以看到,主要分为系统的Drawable创建和自定义的Drawable创建,补充一下系统Drawable创建代码:
public final class DrawableInflater {
// 默认是解析xml下定义的系统Drawable标签名
private Drawable inflateFromTag(@NonNull String name) {
switch (name) {
case "selector":
return new StateListDrawable();
case "animated-selector":
return new AnimatedStateListDrawable();
case "level-list":
return new LevelListDrawable();
case "layer-list":
return new LayerDrawable();
case "transition":
return new TransitionDrawable();
case "ripple":
return new RippleDrawable();
case "adaptive-icon":
return new AdaptiveIconDrawable();
case "color":
return new ColorDrawable();
case "shape":
return new GradientDrawable();
case "vector":
return new VectorDrawable();
case "animated-vector":
return new AnimatedVectorDrawable();
case "scale":
return new ScaleDrawable();
case "clip":
return new ClipDrawable();
case "rotate":
return new RotateDrawable();
case "animated-rotate":
return new AnimatedRotateDrawable();
case "animation-list":
return new AnimationDrawable();
case "inset":
return new InsetDrawable();
case "bitmap":
return new BitmapDrawable();
case "nine-patch":
return new NinePatchDrawable();
case "animated-image":
return new AnimatedImageDrawable();
default:
return null;
}
}
}
3. Bitmap
位图图像。Android支持三种格式的位图文件:.png(首选)、.jpg(可接受)、.gif(不推荐)。
XML位图是用XML定义的指向位图文件的资源。其效果是原始位图文件的别名。XML可以为位图指定额外的属性,比如抖动和平铺。
-
位置:
res/drawable/filename.xml
-
类型:
BitmapDrawable
-
继承关系:BitmapDrawable -> Drawable
-
语法:
<?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"] />
部分名词解释:
属性名称 | 描述 |
---|---|
android:antialias | 图片抗锯齿。图片平滑,清晰度降低 |
android:dither | 位图抖动。如果位图没有与屏幕相同的像素配置,则启用或禁用位图的抖动(例如:具有RGB 565屏幕的ARGB 8888位图)。 用于高质量图片在低质量屏幕上保存较好的显示效果(不会失真) |
android:filter | 位图筛选。当位图收缩或拉伸用来平滑其外观,使用过滤。 |
android:mipMap | 纹理映射。图像处理技术。默认值为false。 |
android:tileMode | 定义平铺模式。当启用平铺模式时,将重复位图。启用平铺模式时将忽略android:gravity 属性。默认disable。 |
4. Nine-Patch
NinePatch
是一款PNG图像,当视图中的内容超过正常图像边界时,你可以定义Android缩放的可拉伸区域。通常,将这种类型的图像指定为至少将一个维度设置为wrap_content
的视图的背景,当视图增长以适应内容时,还将缩放Nine-Patch 的图像以匹配视图的大小。
-
位置:
res/drawable/filename.9.png
-
类型:
NinePatchDrawable
-
继承关系:NinePatchDrawable -> Drawable
-
语法:
<?xml version="1.0" encoding="utf-8"?> <nine-patch xmlns:android="http://schemas.android.com/apk/res/android" android:src="@[package:]drawable/drawable_resource" android:dither=["true" | "false"] />
android:src
的图片类型必须为res/drawable/filename.9.png
5. Layer list
LayerDrawable
是一个管理多个drawable的层次容器。列表中的每个drawable会按照顺序依次绘制,即列表中的最后一个drawable会在顶部绘制的。
每个drawable由单个<layer-list>
元素中的<item>
元素表示。
-
位置:
res/drawable/filename.xml
-
类型:
LayerDrawable
-
继承关系:LayerDrawable -> Drawable
-
语法:
<?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" /> </layer-list>
top、bottom、left、right
为各自方向上的偏移量。其实LayerDrawable好比一个FrameLayout容器,而当中的各个Item按照Z轴方向上排布各个层面
6. State list
StateListDrawable
是一个用XML定义的Drawable,它使用几个不同的图像来表示相同的图形,这取决于对象的状态。例如,Button可以存在于几种不同的状态之一(按下、聚焦或都不存在),并且使用状态列表drawable,可以为每种状态提供不同的背景图像。
-
位置:
res/drawable/filename.xml
-
类型:
StateListDrawable
-
继承关系:StateListDrawable -> DrawableContainer -> Drawable
-
语法:
<?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:constantSize | 当状态改变时,true值的Drawable内部大小保持不变(大小为所有状态的最大值);反之,大小根据当前状态而变化。默认是false。 |
android:variablePadding | 当为true值,Padding值不随当前状态而改变;反之,Padding保持不变(基于所有状态的最大填充)。默认为false |
android:state_pressed | 当处于touched/clicked状态,该Item将会生效 |
android:state_focused | 当处于input focus(如用户输入text)时,该Item将会生效 |
android:state_selected | 当处于selected状态时,该Item将会生效 |
android:state_enabled | 当处于enable状态时,该Item将会生效 |
注意:Drawable状态列表中会优先匹配第一项,不满足则继续找下一个状态。故全匹配状态应放到最后的位置上,示例如下:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/button_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/button_focused" /> <!-- focused -->
<item android:state_hovered="true"
android:drawable="@drawable/button_focused" /> <!-- hovered -->
<item android:drawable="@drawable/button_normal" /> <!-- default -->
</selector>
7. Level list
管理多个可替换的Drawable,每个Drawable分配一个最大数值。使用setLevel()
设置drawable的level值将加载level列表中具有android:maxLevel值大于或等于传递给方法的值的drawable资源。
-
位置:res/drawable/filename.xml
-
类型:
LevelListDrawable
-
继承关系:LevelListDrawable -> DrawableContainer -> Drawable
-
语法:
<?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>
动态改变level:
ImageView imageView = (ImageView) findViewById(R.id.level_list_drawable);
imageView.getBackground().setLevel(...);
8. Transition drawable
TransitionDrawable是一个可绘制对象,它可以在两个可绘制资源之间交叉淡出。
每个Drawable由单个<transition>
元素中的<item>
元素表示。不支持超过两个项目。要向前转换,请调用startTransition()
。要向后转换,请调用reverseTransition()
。
-
类型:
TransitionDrawable
-
继承关系:TransitionDrawable -> LayerDrawable -> Drawable
-
语法:
<?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>
代码调用:
ImageView imageView = (ImageView) findViewById(R.id.transition);
TransitionDrawable drawable = (TransitionDrawable) imageView.getBackground();
if (!isReverse) {
drawable.startTransition(2000);
isReverse = true;
} else {
drawable.reverseTransition(2000);
isReverse = false;
}
9. Inset drawable
将一个Drawable在指定的距离插入其中。当所需视图边界比实际视图的边界小时,就变得很有用了。
-
类型:
InsetDrawable
-
继承关系:InsetDrawable -> DrawableWrapper -> Drawable
-
语法:
<?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" />
10. Clip drawable
根据该Drawable的当前级别裁剪出所需的Drawable。可以根据级别控制子drawable在宽度和高度上被裁剪的程度,以及控制它在整个容器中的位置的Gravity。最常用来实现进度条之类的东西。
-
类型:
ClipDrawable
-
继承关系:ClipDrawable -> DrawableWrapper -> Drawable
-
语法:
<?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"] />
android:clipOrientation
:裁剪的方向android:gravity
:控制裁剪开始的地方
代码调用例子:
ImageView imageview = (ImageView) findViewById(R.id.image);
Drawable drawable = imageview.getBackground();
if (drawable instanceof CLipDrawable) {
// 范围区间:from=0, to=10000
((ClipDrawable)drawable).setLevel(drawable.getLevel() + 1000);
}
11. Scale drawable
根据另一个Drawable的当前级别更改其大小。
-
类型:
ScaleDrawable
-
继承关系:ScaleDrawable -> DrawableWrapper -> Drawable
-
语法:
<?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" />
android:scaleGravity
:指定开始缩放的位置android:scaleHeight
:初始状态时,可用来缩放高度的百分比android:scaleWidth
:初始状态时,可用来缩放宽度的百分比
12. Shape drawable
用XML定义的通用形状。
-
类型:
GradientDrawable
-
继承关系:GradientDrawable -> 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: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>
corners
标签:圆角android:radius
:圆角的半径gradient
:渐变色android:angle
:渐变色起始角度,必须是能被45整除的值。默认为0度,即Y轴正方向android:useLevel
:渐变色的变化是否启用LevelListDrawable的level功能,即调用setLevel
方法控制stroke
标签:描边,相当于Paint.STROKE
- stroke标签下的
android:width
:描边的宽度 android:dashWidth
:每个分割线的厚度android:dashGap
:每个分割线的间隔大小。只有在设置android:dashWidth时才有效。solid
标签:填充,相当于Paint.FILL
13. RotateDrawable
根据level值可旋转的Drawable
-
类型:
RotateDrawable
-
继承关系:RotateDrawable -> DrawableWrapper -> Drawable
-
语法:
<?xml version="1.0" encoding="utf-8"?> <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:fromDegrees="0" android:toDegrees="180" android:pivotX="50%" android:pivotY="50%" android:visible="true"> <bitmap android:gravity="center" android:src="@drawable/sample_1"/> </rotate>
pivotX
和pivotY
控制旋转的中心,我们定义为50%,就是图片的中心位置。fromDegrees
和toDegrees
是选择的起始角度和终结角度。
14. RippleDrawable
水波纹效果的 Drawable
- 类型:
RippleDrawable
- 继承关系:RippleDrawable -> LayerDrawable -> Drawable
14.1 无边界水波纹效果
RippleDrawable 是继承自 LayerDrawable 的,如果一个 Layer 层都没有的话,就是无边界效果
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorAccent">
</ripple>
14.2 有边界水波纹效果
如果加了一个图层作为背景就会有边界效果。
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorAccent">
<item android:drawable="@drawable/ripple_shape_drawable"/>
</ripple>
<!--ripple_shape_drawable.xml-->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#b4abab"/>
<corners android:radius="40dp"/>
</shape>
15. 自定义Drawable
当我们想创建一些自定义绘图时,可以通过扩展Drawable类(或其任何子类)来实现。
主要是实现draw()
方法,因为它提供了绘图指令的Canvas对象。
以下是一个自定义Drawable实现画圆形的实例:
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/2f, height/2f, 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;
}
}
在API 24以前,只能在xml文件中使用framework中自带的那些Drawable类,要用自定义的Drawable只能在Java代码中使用。
在API 24+开始可以在xml文件中使用我们自定义的Drawable了。