Drawable学习

Drawable是什么
  • 一种可以在Canvas上进行绘制的抽象的概念

  • 颜色、图片等都可以是一个Drawable

  • Drawable可以通过XML定义,或者通过代码创建

  • Android中Drawable是一个抽象类,每个具体的Drawable都是其子类

优点:
  1. 使用简单,比自定义View成本低
  2. 非图片类的Drawable所占空间小,能减小apk大小
Drawable的内部宽/高
  • 一般getIntrinsicWidth/Height能获得内部宽/高

  • 图片Drawable其内部宽高就是图片的宽高

  • 颜色Drawable没有内部宽高的概念

  • 内部宽高不等同于它的大小,一般Drawable没有大小概念(作为View背景时,会被拉伸至View的大小)

分类

可见有很多种类,我们只需要了解几种常用的即可。

常用分类
BitmapDrawable的作用和使用

表示一种图片,可以直接引用原始图片或者通过XML进行描述

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@color/colorPrimary"
android:antialias="true"
android:dither="true"
android:filter="true"
android:gravity="center"
android:mipMap="false"
android:tileMode="disabled"
/>

属性:

gravity属性详情

NinePatchDrawable(.9图片)的作用
  1. 自动根据宽高进行缩放且不会失真
  2. 实际使用,可以直接引用图片或者通过XML描述
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<nine-patch
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@color/colorPrimary"
android:antialias="true"
android:dither="true"
android:filter="true"
android:gravity="center"
android:mipMap="false"
android:tileMode="disabled"
/>
ShapeDrawable的作用
  1. 通过颜色构造的图形
  2. 可以是纯色的图形
  3. 也可以是有渐变效果的图形
  4. shape标签创建的Drawable实体是GradientDrawable
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
37
38
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<corners
android:radius="10dp"
android:topLeftRadius="10dp"
android:topRightRadius="10dp"
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"/>
<gradient
android:angle="45"
android:centerX="30"
android:centerY="30"
android:centerColor="@color/colorAccent"
android:endColor="@color/colorPrimary"
android:startColor="@color/colorPrimaryDark"
android:gradientRadius="20"
android:type="linear"
android:useLevel="true" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
<size
android:width="200dp"
android:height="200dp" />
<solid
android:color="@color/colorPrimary"/>
<stroke
android:width="10dp"
android:color="@color/colorAccent"
android:dashWidth="5dp"
android:dashGap="3dp"/>

</shape>

ShapeDrawable的属性介绍

LayerDrawable的作用

XML标签为layer-list

层次化的Drawable合集

可以包含多个item,每个item表示一个Drawable

item中可以通过android:drawable直接引用资源

android:top等表示Drawable相当于View上下左右的偏移量

微信文本输入框

在drawable目录下创建一个layer_list.xml

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
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 前景层-->
<item>
<shape android:shape="rectangle">
<solid android:color="#0ac39e" />
</shape>
</item>
<!-- 第二层,当前drawable为一个纯白色,相对view底部6dp外间距。-->
<item android:bottom="6dp">
<shape android:shape="rectangle">
<solid android:color="#ffffff" />
</shape>
</item>
<!-- 第三层,当前drawable为一个纯白色,相对view下,左,右各1dp的外间距。-->
<item
android:bottom="1dp"
android:left="1dp"
android:right="1dp">
<shape android:shape="rectangle">
<solid android:color="#ffffff" />
</shape>
</item>

</layer-list>

在布局中使用

1
2
3
4
5
6
7
<EditText
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/layer_list"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="文本框" />

效果

StateListDrawable的使用和要点
  1. 对应于selector标签
  2. 用于View根据状态选择不同的Drawable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android"
android:constantSize="false" //StateListDrawable的固有大小是否根据状态而改变,默认false=根据状态而改变
android:dither="true" //是否开启抖动-让高质量图片在低质量屏幕上依旧效果好,默认true开启
android:variablePadding="false" //padding是否根据状态的改变而改变,不建议开启(false)
>
<item android:state_pressed="true" //Button被按下后却没有松开的状态
android:drawable="@color/colorAccent"/>
<item android:state_focused="true" //View获取了焦点
android:drawable="@color/colorPrimary"/>
<item android:state_selected="true" //用户选择了View
android:drawable="@color/colorPrimary"/>
<item android:state_checked="true" //用户选中了View,一般用于CheckBox这类在选中和没有选中状态之间切换的View
android:drawable="@drawable/ic_launcher_background"/>
<item android:state_enabled="true" //View处于可用状态
android:drawable="@drawable/ic_launcher_foreground"/>
<item android:drawable="#FFFFFF"/> //默认Drawable: 按顺序向下匹配,需要放在最下方,因为可以匹配任何状态
</selector>
LevelListDrawable的作用

对应于level-list标签

拥有多个item,每个item都有maxLevel和minLevel

Level的范围为0~10000

给定level后,会按从上至下的顺序匹配,直到找到范围合适的Drawable,并返回

item的level一定要降序或者升序

调用View的getBackground获得Drawable对象,并调用setLevel设置等级level

ImageView的setImageLevel()能快速指定src引用的Drawable的Level

LevelListDrawable是根据level改变,选择不同的Drawable,能用于实现进度条、音量调节等等

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:minLevel="0" android:maxLevel="10" android:drawable="@drawable/d1" />
<item android:minLevel="11" android:maxLevel="20" android:drawable="@drawable/d2" />
<item android:minLevel="21" android:maxLevel="30" android:drawable="@drawable/d3" />
<item android:minLevel="31" android:maxLevel="40" android:drawable="@drawable/d4" />
</level-list>
TransitionDrawable的作用

对应于transition标签

实现两个Drawable之间的淡入淡出效果

获得背景的TransitionDrawable后,通过startTransition和reverseTransition方法实现效果和逆过程

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:id="@+id/transition_drawable"
android:drawable="@drawable/ic_launcher"
android:top="10dp" //四周的偏移量
android:bottom="10dp"
android:right="10dp"
android:left="10dp"/>
<item android:drawable="@drawable/ic_launcher_round" />
</transition>
InsetDrawable的作用和使用
  1. 对应inset标签
  2. 将其他Drawable内嵌到自身,并在四周留出间距
  3. View需要背景比自己实际区域要小的时候,可以使用inset,layer-list也可以实现该需求
1
2
3
4
5
6
7
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_launcher"
android:insetTop="10dp"
android:insetBottom="10dp"
android:insetLeft="10dp"
android:insetRight="10dp">
</inset>
ScaleDrawable的作用

对应于scale标签

根据自己的等级level(0~10000)将指定的Drawable缩放到一定比例

android:scaleHeight=”70%”用于指定宽高的缩放比例=为原来的30%

ScaleDrawable的level为0,不可见。为10000时,不缩放。

一般将level设置为1,就会按照属性指定的比例缩放。其他值也会改变缩放效果。

android:scaleGravity属性和gravity属性完全一致

1
2
3
4
5
6
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_launcher"
android:scaleGravity="center"
android:scaleHeight="70%"
android:scaleWidth="70%">
</scale>
ClipDrawable的作用

对应于clip标签

根据自己当前的等级level(0~10000)来裁剪另一个Drawable

裁剪方向由clipOrientation和gravity属性共同控制

level为0,Drawable不可见;10000表示不裁剪;为8000,表示裁减了2000;为1,表示裁剪了9999

ClipDrawable的gravity

AnimationDrawable的作用

对应于animation-list标签

用于实现逐帧动画效果

android:oneShot决定是循环播放还是播放一次,false:循环播放

item中设置一帧一帧的Drawable以及持续时间

AnimationDrawable的setOneShot(boolean flag) 和android:oneShot配置一样

addFrame (Drawable frame, int duration) 动态的添加一个图片进入该动画中

stop()和start()用于停止和开始/继续播放,停止时会停留在当前一帧上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/shake_anim_01" android:duration="100"/>
<item android:drawable="@drawable/shake_anim_02" android:duration="100"/>
<item android:drawable="@drawable/shake_anim_03" android:duration="100"/>
<item android:drawable="@drawable/shake_anim_04" android:duration="100"/>
<item android:drawable="@drawable/shake_anim_05" android:duration="100"/>
<item android:drawable="@drawable/shake_anim_06" android:duration="100"/>
<item android:drawable="@drawable/shake_anim_07" android:duration="100"/>
<item android:drawable="@drawable/shake_anim_08" android:duration="100"/>
<item android:drawable="@drawable/shake_anim_09" android:duration="100"/>
<item android:drawable="@drawable/shake_anim_10" android:duration="100"/>
</animation-list>

在代码中定义出AnimationDrawable对象,并设置到view的background上,然后设置开始播放就可以

1
2
3
val animationDrawable = resources.getDrawable(R.drawable.animationdrawable) as AnimationDrawable
iv.setBackgroundDrawable(animationDrawable)
animationDrawable.start() //开始播放
ShapeDrawable的OvalShape、RectShape、ArcShape和PaintDrawable的作用和使用

1.用于获得有shape形状的drawable(椭圆、长方形、扇形以及更为通用PaintDrawable-具有圆角和边界)

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
 *  一个继承自ShapeDrawable更为通用的Drawable:具有圆角
*====================================================*/
PaintDrawable drawable3 = new PaintDrawable(Color.GREEN);
drawable3.setCornerRadius(30);
findViewById(R.id.textView3).setBackgroundDrawable(drawable3);

/**============================================
* 通过Shape构造出相应的ShapeDrawable
*=============================================*/
//椭圆形形状 : shape赋予ShapeDrawable
OvalShape ovalShape = new OvalShape();
ShapeDrawable drawable1 = new ShapeDrawable(ovalShape);
drawable1.getPaint().setColor(Color.BLUE);
drawable1.getPaint().setStyle(Paint.Style.FILL);
findViewById(R.id.textView1).setBackgroundDrawable(drawable1);

//矩形形状 : shape赋予ShapeDrawable
RectShape rectShape = new RectShape();
ShapeDrawable drawable2 = new ShapeDrawable(rectShape);
drawable2.getPaint().setColor(Color.RED);
drawable2.getPaint().setStyle(Paint.Style.FILL);
findViewById(R.id.textView2).setBackgroundDrawable(drawable2);

//扇形、扇面形状 : shape赋予ShapeDrawable
//顺时针,开始角度30, 扫描的弧度跨度180
ArcShape arcShape = new ArcShape(30, 180);
ShapeDrawable drawable4 = new ShapeDrawable(arcShape);
drawable4.getPaint().setColor(Color.YELLOW);
drawable4.getPaint().setStyle(Paint.Style.FILL);
findViewById(R.id.textView4).setBackgroundDrawable(drawable4);
自定义Drawable
概况
  1. 一般作为ImageView的图像来显示
  2. 另一个是作为View的背景
  3. 自定义Drawable主要就是实现draw方法
  4. setAlpha、setColorFilter、getOpacity也需要重写,但是模板固定
  5. 当自定义Drawable有固定大小时(比如绘制一张图片),需要重写getIntrinsicWidth()/getIntrinsicHeight()方法(默认返回-1),会影响到View的wrap_content布局
  6. 内部固定大小不等于Drawable的实际区域大小,getBounds能获得实际区域大小
自定义Drawable模板代码
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
class CustomDrawable(color: Int) : Drawable(){
var mPaint: Paint
init {
mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mPaint.color = color
}
override fun draw(canvas: Canvas) {
val rect = bounds
canvas.drawCircle(rect.exactCenterX(),
rect.exactCenterY(),
Math.min(rect.exactCenterX(), rect.exactCenterY()),
mPaint)
}

override fun setAlpha(alpha: Int) {
mPaint.alpha = alpha
invalidateSelf()
}
override fun setColorFilter(colorFilter: ColorFilter?) {
mPaint.colorFilter = colorFilter
invalidateSelf()
}
override fun getOpacity(): Int {
//not sure, so be safe
return PixelFormat.TRANSLUCENT
}
}
SVG矢量图
SVG?
  1. 可伸缩矢量图(Android 5.0推出)
  2. 定义用于网络的基于矢量的图形(在Web上应用非常广泛)
  3. 使用XML格式定义图形
  4. 图像缩放不会影响质量
  5. 万维网联盟标准(与DOM和XSL之类的W3C标准是一个整体)
SVG和Bitmap的区别
  1. SVG是一个绘图标准。
  2. Bitmap是通过每个像素点上存储色彩信息来表示图像。
  3. SVG放大不会失真, Bitmap会失真。
  4. Bitmap需要为不同分辨率设计多套图表,SVG绘制一张图就能适配不同分辨率。
静态矢量图SVG-VectorDrawable
  1. 基于XML的静态矢量图
  2. 采用标签vector
  3. vector中path是最小单位,创建SVG-用指令绘制SVG图形
  4. vector中group将不同path组合起来
VectorDrawable的vector标签有哪些属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp" // SVG的具体大小
android:height="200dp"
android:viewportWidth="100" //将宽度分为多少份,与path配合(50份等于100dp) 比如上面的代码,将200dp划分100份,如果在绘图中使用坐标(50,50),则意味着该坐标为正中间
android:viewportHeight="100">
<group> //将不同`path`组合起来
<path //SVG树形结构的最小单位,用指令绘制SVG图形
android:name="path1" //该path的名称
android:pathData="M 20,80 L 50,80 80,80"
android:strokeColor="@color/colorAccent"
android:strokeWidth="3"
android:strokeLineCap="round"/>
<path
.../>
</group>
</vector>
VectorDrawable的path标签的全部指令

  1. 坐标轴以(0, 0)为中心, X轴水平向右, Y轴水平向下
  2. 指令大写-绝对定位,参考全局坐标系;指令小写-相对定位,参考父容器坐标系
  3. 指令和数据间空格可以省略
  4. 同一指令出现多次,可以只用一个。
  5. A的参数:RX/RY:椭圆半轴大小 XROTATION:椭圆X轴与水平方向顺时针方向夹角 FLAG1:1-大角度弧线 0-小角度弧线 FLAG2:起点到终点的方向,1-顺时针,2-逆时针 X/Y:终点坐标
VectorDrawable实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//1. 使用`vector`标签定义矢量图VectorDrawable(ic_black_24dp.xml)
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<group
android:name="test"> //该组的名称:可以在AnimatedVectorDrawable中指定动画效果
<path
android:fillColor="#FF000000"
android:pathData="M12,6c1.11,0 2,-0.9 2,-2 0,-0.38 -0.1,-0.73 -0.29,-1.03L12,0l-1.71,2.97c-0.19,0.3 -0.29,0.65 -0.29,1.03 0,1.1 0.9,2 2,2zM16.6,15.99l-1.07,-1.07 -1.08,1.07c-1.3,1.3 -3.58,1.31 -4.89,0l-1.07,-1.07 -1.09,1.07C6.75,16.64 5.88,17 4.96,17c-0.73,0 -1.4,-0.23 -1.96,-0.61L3,21c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1v-4.61c-0.56,0.38 -1.23,0.61 -1.96,0.61 -0.92,0 -1.79,-0.36 -2.44,-1.01zM18,9h-5L13,7h-2v2L6,9c-1.66,0 -3,1.34 -3,3v1.54c0,1.08 0.88,1.96 1.96,1.96 0.52,0 1.02,-0.2 1.38,-0.57l2.14,-2.13 2.13,2.13c0.74,0.74 2.03,0.74 2.77,0l2.14,-2.13 2.13,2.13c0.37,0.37 0.86,0.57 1.38,0.57 1.08,0 1.96,-0.88 1.96,-1.96L20.99,12C21,10.34 19.66,9 18,9z"/>
</group>
</vector>
//2. 使用矢量图
<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@drawable/ic_black_24dp"/>
矢量图动画-AnimatedVectorDrawable

AnimatedVectorDrawable的作用是给VectorDrawable提供动画效果,Google的工程师将AnimatedVectorDrawable比喻一个胶水,通过AnimatedVectorDrawable来连接静态的VectorDrawable动态的objectAnimator

  1. 针对静态矢量图-VectorDrawable来做动画
  2. xml标签为animated-vector
  3. 在target子标签下指明VectorDrawable的名字(都是android:name=”…”属性指明),并指定动画效果android:animation=”@animator/…”
  4. AnimatedVectorDrawable中指明的target和name属性,必须与VectorDrawable中需要的name保持一致,这样系统能找到找到要实现的动画元素
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//1. 静态矢量图-VectorDrawable(vector_two_line.xml)
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="100"
android:viewportHeight="100">
<group>
<path
android:name="path1" //路径1的名称
android:pathData="M 20,80 L 50,80 80,80"
android:strokeColor="@color/colorAccent"
android:strokeWidth="3"
android:strokeLineCap="round"/>
<path
android:name="path2" //路径2的名称
android:pathData="M 20,20 L 50,20 80,20"
android:strokeColor="@color/colorAccent"
android:strokeWidth="3"
android:strokeLineCap="round"/>
</group>
</vector>

//2. 轨迹动画效果-属性动画ObjectAnimator(res/animator/trimpath_animator)
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:propertyName="trimPathEnd"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/accelerate_decelerate">
</objectAnimator>

//3. 粘合静态SVG和属性动画:AnimatedVectorDrawable(vector_trimpath_anim.xml)
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/vector_two_line"> //静态SVG
<target
android:animation="@animator/trimpath_animator" //属性动画
android:name="path1"> //静态SVG中路径1的名称
</target>
<target
android:animation="@animator/trimpath_animator" //属性动画
android:name="path2"> //静态SVG中路径2的名称
</target>
</animated-vector>

//4. 布局中使用AnimatedVectorDrawable
<ImageView
android:id="@+id/imageview"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@drawable/vector_trimpath_anim"/> //动画矢量图
开启动画
1
2
val drawable = imageview.drawable
(drawable as Animatable).start()
0%