Drawable简介
Drawable是一种 可以在Canvas上进行绘制的对象,即可绘制物 。与View不同,Drawable没有事件和交互的方法。
在实际开发中,Drawable通常被用作View的背景,一般通过XML进行定义,也支持通过代码去实现(例如动画样式的Drawable )。
Drawable是一个抽象类,是所有Drawable的基类。例如常用的ShapeDrawable、BitmapDrawable,LayerDrawable等。
Drawable使用方式
Drawable宽高 Drawable可以通过getIntrinsicWidth()和getIntrinsicHeight()获取内部宽高。其实在View工作原理的measure过程中有用到这个方法,就是为了View设置背景时可以精确的确定其宽高 。
不是所有的Drawable都有宽高
图片所形成的Drawable内部宽高就是图片的宽高
颜色所形成的Drawable没有内部宽高的概念
Drawable没有大小的概念,当用作View的背景时,Drawable会被拉伸至View的同等大小。
Drawable使用范围
作为ImageView的图像显示——ImageView.setImageDrawable()
作为View的背景——View.setBackgroundDrawable()
Drawable的种类
Drawable种类
对应tag
描述
BitmapDrawable
bitmap
表示一张图片
ColorDrawable
color
表示一个纯色的Drawable
NinePatchDrawable
nine-patch
表示一张.9格式的图片
ShapeDrawable/GradientDrawable
shape
可表示纯色、有渐变效果的基础几何图形(例如矩形、圆形等)
LayerDrawable
layer-list
表示一种层次性的Drawable集合,实现一种叠加效果。 下层的item会覆盖上层
StateListDrawable
selector
表示Drawable的集合,每个Drawable对应着一种View的状态
LevelListDrawable
level-list
表示Drawable的集合,每个Drawable对应着一种层级
TransitionDrawable
transition
表示两个Drawable间的淡入淡出效果
InsetDrawable
inset
表示把一个Drawable嵌入到内部,并留有间隙
ScaleDrawable
scale
表示将Drawable缩放到一定比例
ClipDrawable
clip
表示裁剪一个Drawable
RotateDrawable
rotate
表示旋转一个Drawable
VectorDrawable
vector
表示一个SVG的Drawable
1.BitmapDrawable
表示一张图片
根节点为bitmap。在使用过程中,我们可以直接引用原始图片即可,也可以通过XML的方式来描述他。
1 2 3 4 5 6 7 8 9 10 11 12 <bitmap xmlns:android ="http://schemas.android.com/apk/res/android" android:src ="" android:antialias ="[true | false]" android:dither ="[true | false]" android:filter ="[true | false]" android:tileMode ="[disabled | clamp | repeat | mirror]" android:gravity ="[top | bottom | left | right | center_vertical | |fill_vertical | center_horizontal | fill_horizontal | |center | fill | clip_vertical | clip_horizontal]" > </bitmap >
下面是各个属性的含义:
android:src:图片的资源id
android:antialias:是否开启抗锯。,开启会让图片变得平滑。可能导致图片清晰度下降,基本可以忽略,应该开启。
android:dither:是否开启抖动效果。可以让高质量的图片在低质量的屏幕上还能保持较好的显示效果,是图片显示不过于失真。应该开启。
android:filter:是否开启过滤效果。当图片被拉伸或者压缩时,开启可以保持较好的显示效果。应该开启 。
android:tileMode:平铺模式。开启平铺模式时,gravity属性会被忽略。
可选项
含义
disabled
关闭平铺模式默认值
clamp
图片四周的像素会拓展到周围区域就是边缘像素拉伸
repeat
水平和竖直方向平铺
mirror
水平和竖直方向镜像显示
android:gravity:对图片位置进行定位。可以通过”|”进行组合使用。
可选项
含义
top
不改变大小,置于容器顶部
bottom
不改变大小,置于容器底部
left
不改变大小,置于容器左部
right
不改变大小,置于容器右部
center_vertical
不改变大小,置于容器竖直居中
fill_vertical
图片竖直拉伸填满容器
center_horizontal
不改变大小,置于容器水平居中
fill_horizontal
图片水平拉伸填满容器
center
不改变大小,置于容器水平和竖直居中
fill
水平竖直方向拉伸填满容器默认值
clip_vertical
竖直方向进行裁剪
clip_horizontal
水平方向进行裁剪
应用代码 1 2 3 4 5 6 7 8 9 10 11 //repeat_bitmap.xml<?xml version="1.0" encoding="utf-8" ?> <bitmap xmlns:android ="http://schemas.android.com/apk/res/android" android:dither ="true" android:src ="@mipmap/ic_launcher" android:tileMode ="repeat" > </bitmap > //布局文件引用 android:background="@drawable/repeat_bitmap"
1 2 3 4 5 6 Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);BitmapDrawable bitDrawable = new BitmapDrawable (bitmap); bitDrawable.setDither(true ); bitDrawable.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); view.setBackground(bitmapDrawable);
2.NinePatchDrawable
表示一张.9格式的图片
1 2 3 4 <nine-patch xmlns:android ="http://schemas.android.com/apk/res/android" android:dither ="[true|false]" android:src ="" > </nine-patch >
android:src:图片的资源id
android:dither:是否开启抖动效果。可以让高质量的图片在低质量的屏幕上还能保持较好的显示效果,是图片显示不过于失真。应该开启。
应用代码 1 2 3 4 5 6 7 8 9 10 //对应 ninepatch.9.png ninepatch.xml<?xml version="1.0" encoding="utf-8" ?> <nine-patch xmlns:android ="http://schemas.android.com/apk/res/android" android:dither ="true" android:src ="@drawable/ninepatch" > </nine-patch > //布局文件引用 android:background="@drawable/ninepatch"
3.ShapeDrawable — 实际为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 <?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:图形的形状。
rectabgle:矩形
oval:椭圆
line:横线。必须设置<stroke>属性指定直线宽度及颜色
ring:圆环。必须设置<stroke>属性指定圆环宽度及颜色
<corners>:表示四个圆角的角度,只适合矩形。android:radius这个属性的设置优先级低于单独设置各个圆角角度。
<gradient>:可以设置渐变效果
android:angle:设置渐变角度。默认为0,且要求值必须为45的倍数,0表示从左到右,90表示从下到上
android:centerX:渐变的中心点的X坐标。
android:centerY:渐变的中心点的Y坐标。
android:startColor:渐变的起始色。
android:centerColor:渐变的中间色。
android:endColor:渐变的结束色。
android:gradient:渐变半径。仅当**android:type=”radial”**时有效。
android:type:渐变的类型
linear:线性渐变
radial:径向渐变。类似扩散效果
sweep:扫描线渐变。类似雷达效果。
<padding>:设置四周空白距离
<size>:设置图形的固有大小,但不是最终的大小。作为View背景时,大小还是跟着View走的
<solid>:设置纯色填充
<stroke>:设置描边
stroke属性
作用
android:width
描边的宽度,越大边缘越明显
android:color
描边的颜色
android:dashwidth
虚线的宽度
android:dashGap
虚线的空隙间隔
如果android:dashWidth或android:dashGap 有任何一个为0,则虚线效果无法生效。
应用代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 //shpae.xml<?xml version="1.0" encoding="utf-8" ?> <shape xmlns:android ="http://schemas.android.com/apk/res/android" android:shape ="rectangle" > <solid android:color ="#ffffff" /> <padding android:bottom ="7dp" android:left ="7dp" android:right ="7dp" android:top ="7dp" /> <stroke android:width ="3dp" android:color ="#FFFF00" /> <corners android:radius ="3dp" /> </shape > android:background="@drawable/shape"
4.LayerDrawable
表示一种层次性的Drawable集合,通过将不同的Drawable放置在不同的层上面从而达到一种叠加的效果。
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="utf-8" ?> <layer-list xmlns:android ="http://schemas.android.com/apk/res/android" > <item android:drawable ="" android:id ="@+id" android:left ="integer" android:right ="integer" android:top ="integer" android:bottom ="integer" > </item > </layer-list >
android:drawable:引用的背景资源
android:id:层id
android:top:layer相对于容器的上边距
android:bottom:layer相对于容器的下边距
android:left:layer相对于容器的左边距
android:right:layer相对于容器的右边距
Layer-list有层次的概念,下面的item会覆盖上面的item 。
应用代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <layer-list xmlns:android ="http://schemas.android.com/apk/res/android" > <item android:drawable ="@drawable/red_color" android:bottom ="10dp" android:left ="10dp" android:right ="10dp" android:top ="10dp" /> <item android:drawable ="@drawable/green_color" android:bottom ="20dp" android:left ="20dp" android:right ="20dp" android:top ="20dp" /> <item android:drawable ="@drawable/blue_color" android:bottom ="30dp" android:left ="30dp" android:right ="30dp" android:top ="30dp" /> </layer-list >
5.StateListDrawable
对应着<selector>标签,也表示Drawable集合,每个Drawable对应着View的一种状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <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 ="" android:state_pressed ="[true | false]" android:state_focused ="[true | false]" android:state_selected ="[true | false]" android:state_hovered ="[true | false]" android:state_checked ="[true | false]" android:state_checkable ="[true | false]" android:state_enabled ="[true | false]" android:state_activated ="[true | false]" android:state_window_focused ="[true | false]" > </item > </selector >
android:constantSize:固有大小是否不随着其状态的改变而改变。由于状态改变会导致StateListDrawable切换到对应的Drawable,导致大小发生变化。
设置为 true 固有大小是固定值,就是所有item中的最大值
设置为 false 固有大小跟着切换的item发生变化 默认值
android:variblePadding:padding是否随着状态改变而改变
true padding随着状态改变而改变
false padding是固定值,取内部所有item中padding的最大值 默认值
<item>
状态
含义
android:state_pressed
表示手指按下的状态
android:state_focused
表示获取焦点的状态
android:state_selected
表示选中的状态
android:state_checked
表示选中的状态。一般用与CheckBox
android:state_enabled
表示当前可用的状态
应用代码 1 2 3 4 5 6 7 8 <?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/state_pressed" /> <item android:state_focused ="true" android:drawable ="@drawable/state_focused" /> <item android:drawable ="@drawable/state_normal" /> </selector >
6.LevelListDrawable
表示一个Drawable集合,集合中的每一个Drawable都有等级 的概念。
1 2 3 4 5 <level-list xmlns:android ="http://schemas.android.com/apk/res/android" > <item android:drawable ="" android:maxLevel ="integer" android:minLevel ="integer" /> </level-list >
android:maxLevel:对应的最大值,取值范围为0~10000,默认0 常用该属性
android:minLevel:对应的最小值,取值范围为0~10000,默认0
使用方法:无论是用xml还是代码实现
作为View背景:都需要在Java代码中调用setLevel()
作为图片前景:需要调用setImageLevel()
应用代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 //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" /> </level-list > <View android:background ="@drawable/level" /> <ImageView android:src ="@drawable/level" /> // 设置View背景 view.setLevel(1) //设置ImageView imageView.setImageLevel(1);
拓展:ImageView的android:setBackground和android:src有什么区别?
android:setBackground:会根据ImageView控件的宽高去拉伸图片
android:src:保持原图大小
7.TransitionDrawable
表示两个Drawable之间的淡入淡出效果
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="utf-8" ?> <transition xmlns:android ="http://schemas.android.com/apk/res/android" > <item android:drawable ="" android:id ="@+id" android:left ="integer" android:right ="integer" android:top ="integer" android:bottom ="integer" > </item > </transition >
应用代码 1 2 3 4 5 6 7 8 // transition.xml<?xml version="1.0" encoding="utf-8" ?> <transition xmlns:android ="http://schemas.android.com/apk/res/android" > <item android:drawable ="@drawable/bg1" /> <item android:drawable ="@drawable/bg2" /> </transition > <view android:background ="@drawable/transition" />
1 2 3 TransitionDrawable drawable = (TransitionDrawable)view.getBackground(); drawable.startTransition(1000 );
8.InsetDrawable
表示把一个Drawable嵌入到另一个Drawable的内部,并在四周留一些间距
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="utf-8" ?> <insert xmlns:android ="http://schemas.android.com/apk/res/android" android:drawable ="" android:visible ="[true | false]" android:insertLeft ="integer" android:insertRight ="integer" android:insertTop ="integer" android:insertBottom ="integer" > </transition >
android:visible:是否保留边距。默认保留
应用代码 1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="utf-8" ?> <inset xmlns:android ="http://schemas.android.com/apk/res/android" android:drawable ="@drawable/image" android:insetBottom ="20dp" android:insetLeft ="20dp" android:insetRight ="20dp" android:insetTop ="20dp" android:visible ="true" > </inset >
9.ScaleDrawable
表示将Drawable缩放到一定比例
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="utf-8" ?> <scale xmlns:android ="http://schemas.android.com/apk/res/android" android:drawable ="" android:scaleWidth ="percentage" android:scaleHeight ="percentage" android:scaleGravity ="[top | bottom | left | right | center_vertical | center_horizontal | center | fill_vertical | fill_horizontal | fill | clip_vertical | clip_horizontal]" > </scale >
android:scaleGravity:效果同android:gravity
android:scaleWidth/android:scaleHeight:指定宽/高缩放比例。以百分比形式展示(25%)。
应用代码 1 2 3 4 5 6 7 8 9 // scale.xml<?xml version="1.0" encoding="utf-8" ?> <scale xmlns:android ="http://schemas.android.com/apk/res/android" android:drawable ="@drawable/drawable_test" android:scaleGravity ="center" android:scaleHeight ="70%" android:scaleWidth ="70%" /> <ImageView android:background ="@drawable/scale" > </ImageView >
1 2 ScaleDrawable scaleDrawable = (ScaleDrawable) imageView.getDrawable(); scaleDrawable.setLevel(1 );
setLevel填值不可以为0,取值范围为0~10000,0表示不可见。
10.ClipDrawable
表示裁剪一个Drawable
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 ="" android:clipOrientation ="[horizonal | vertical]" android:scaleGravity ="[top | bottom | left | right | center_vertical | center_horizontal | center | fill_vertical | fill_horizontal | fill | clip_vertical | clip_horizontal]" ></clip >
android:clipOrientation:表示裁剪方向 可选值为 horizonal 水平,vertical 竖直
android:gravity:表示对齐方式
可选项
含义
top
放在容器的顶部,不改变大小 若为竖直裁剪,则从底部开始裁剪
bottom
放在容器的底部,不改变大小 若为竖直裁剪,则从顶部开始裁剪
left
放在容器的左边,不改变大小 若为水平裁剪,则从右部开始裁剪
right
放在容器的右边,不改变大小 若为水平裁剪,则从左边开始裁剪
center_vertical
放在容器的竖直居中,不改变大小 若为竖直裁剪,则从上下开始裁剪
fill_vertical
竖直方向填充容器 若为竖直裁剪,仅当level为0才开始裁剪
center_horizontal
放在容器的水平居中,不改变大小 若为水平裁剪,则从左右同时开始裁剪
fill_horizontal
水平方向填充容器 若为水平裁剪,仅当level为0才开始裁剪
center
放在容器的中心,不改变大小 若为竖直裁剪,则从上下开始裁剪 若为水平裁剪,则从左右开始裁剪
fill
放在容器的顶部,不改变大小 若为竖直裁剪,则从底部开始裁剪*
clip_vertical
竖直方向进行裁剪
clip_horizontal
水平方向进行裁剪
应用代码 1 2 3 4 5 6 7 8 9 10 // clip.xml<?xml version="1.0" encoding="utf-8" ?> <clip xmlns:android ="http://schemas.android.com/apk/res/android" android:drawable ="@drawable/bg1" android:clipOrientation ="vertical" android:scaleGravity ="bottom" > </clip > <ImageView android:backgrounf ="@drawable/clip" />
1 2 3 ClipDrawable clipDrawable = (ClipDrawable) imageView.getDrawable(); clipDrawable.setLevel(5000 )
setLevel()数值范围为0~10000,0代表完全裁剪,8000代表裁剪20%
11.RotateDrawable
表示旋转Drawable
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="utf-8" ?> <rotate xmlns:android ="http://schemas.android.com/apk/res/android" android:drawable ="" android:fromDegrees ="integer" android:pivotX ="percentage" android:pivotY ="percentage" android:toDegrees ="integer" android:visible ="[true | false]" > </rotate >
android:fromDegrees RotateDrawable实例起始角度,大于0是顺时针旋转,小于0是逆时针旋转;android:toDegrees RotateDrawable实例最终角度,大于0是顺时针旋转,小于0是逆时针旋转;android:pivotX RotateDrawable实例旋转中心点X轴坐标相对自身位置;android:pivotY RotateDrawable实例旋转中心点Y轴坐标相对自身位置;
应用代码 1 2 3 4 5 6 7 8 9 10 11 12 //rotate.xml<?xml version="1.0" encoding="utf-8" ?> <rotate xmlns:android ="http://schemas.android.com/apk/res/android" android:drawable ="@mipmap/rotate_round" android:fromDegrees ="0" android:pivotX ="50%" android:pivotY ="50%" android:toDegrees ="360" android:visible ="true" > </rotate > <view android:background ="@drawable/rotate"
1 2 RotateDrawable rotateDrawable = (RotateDrawable) view.getDrawable(); rotateDrawable.setLevel(5000 )
自定义Drawable
需要去复合实现 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 28 29 30 31 32 33 34 35 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作为ImageView的图像或者View的背景去使用。
需要实现自定义Drawable的话,就必须要实现draw(),setAlpha(),setColorFilter(),getOpacity()这几个方法。
如果自定义的Drawable设置了固有大小,最好重写getInstrinsicWidrh()和getInstrinsicHeight(),因为会影响到wrap_content属性。
Drawable相关
android中的dp、px、dip、sp,dpi相关概念
px:就是像素单位,例如手机分辨率1080*1920单位就是px
dp:设备独立像素,不同的设备有不同的效果,在不同的像素密度的设备上会自动适配
dpi:每英寸像素树,有设备决定是固定的。计算方法:横向分辨率/横向英寸数
sp:同dp相似,会根据用户的字体大小偏好进行缩放
Drawable文件过多如何整理
Drawable原理
Drawable和View的关系
在View执行到draw过程时,出现了Drawable的身影,在measure、layout过程中都不涉及。
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 public void draw (Canvas canvas) { ... if (!dirtyOpaque) { drawBackground(canvas); } ... } private void drawBackground (Canvas canvas) { final Drawable background = mBackground; setBackgroundBounds(); ... background.draw(canvas); ... } void setBackgroundBounds () { if (mBackgroundSizeChanged && mBackground != null ) { mBackground.setBounds(0 , 0 , mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false ; rebuildOutline(); } }
要使用Drawable的话,必须调用setBounds()否则无法使用,接下来调用draw()进行绘制任务。
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 public void setBackground (Drawable background) { setBackgroundDrawable(background); }public void setBackgroundDrawable (Drawable background) { ... if (mBackground != null ) { if (isAttachedToWindow()) { mBackground.setVisible(false , false ); } mBackground.setCallback(null ); unscheduleDrawable(mBackground); } ... if (background != null ) { background.setCallback(this ); if (mBackground == null || mBackground.getMinimumHeight() != background.getMinimumHeight() || mBackground.getMinimumWidth() != background.getMinimumWidth()) { requestLayout = true ; } ... } else { mBackground = null ; requestLayout = true ; } if (requestLayout) { requestLayout(); } mBackgroundSizeChanged = true ; invalidate(true ); invalidateOutline(); }
调用drawable.setCallback(view)建立联系
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 public final void setCallback (@Nullable Callback cb) { mCallback = cb != null ? new WeakReference <>(cb) : null ; } public interface Callback { void invalidateDrawable (@NonNull Drawable who) ; void scheduleDrawable (@NonNull Drawable who, @NonNull Runnable what, long when ) ; void unscheduleDrawable (@NonNull Drawable who, @NonNull Runnable what) ; } public void invalidateSelf () { final Callback callback = getCallback(); if (callback != null ) { callback.invalidateDrawable(this ); } } public void invalidateDrawable (@NonNull Drawable drawable) { if (verifyDrawable(drawable)) { final Rect dirty = drawable.getDirtyBounds(); final int scrollX = mScrollX; final int scrollY = mScrollY; invalidate(dirty.left + scrollX, dirty.top + scrollY, dirty.right + scrollX, dirty.bottom + scrollY); rebuildOutline(); } }
setCallback()将传进去的View实例,通过弱引用包装起来,防止Drawable长时间不释放,导致内存泄漏。最好还是不用drawable的时候,调用setCallback(null)解除引用。
setCallback()主要的是invalidateDrawable()可以在Drawable发生变化的时候,及时回调View.invalidate()进行重绘。
Drawable获取
一般都是通过getResource.getDrawable()根据drawableId获取对应Drawable对象。后来添加了getDrawableForDensity()可以根据缩放比返回对应的Drawable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public Drawable getDrawable (@DrawableRes int id, @Nullable Theme theme) throws NotFoundException { return getDrawableForDensity(id, 0 , theme); } public Drawable getDrawableForDensity (@DrawableRes int id, int density, @Nullable Theme theme) { final TypedValue value = obtainTempTypedValue(); try { final ResourcesImpl impl = mResourcesImpl; impl.getValueForDensity(id, density, value, true ); return impl.loadDrawable(this , value, id, density, theme); } finally { releaseTempTypedValue(value); } }
向下继续调用到ResourceImpl.loadDrawable()
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 55 56 57 Drawable loadDrawable (@NonNull Resources wrapper, @NonNull TypedValue value, int id, int density, @Nullable Resources.Theme theme) throws NotFoundException { final boolean useCache = density == 0 || value.density == mMetrics.densityDpi; final boolean isColorDrawable; final DrawableCache caches; final long key; if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { isColorDrawable = true ; caches = mColorDrawableCache; key = value.data; } else { isColorDrawable = false ; caches = mDrawableCache; key = (((long ) value.assetCookie) << 32 ) | value.data; } if (!mPreloading && useCache) { final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme); if (cachedDrawable != null ) { cachedDrawable.setChangingConfigurations(value.changingConfigurations); return cachedDrawable; } } final Drawable.ConstantState cs; if (isColorDrawable) { cs = sPreloadedColorDrawables.get(key); } else { cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key); } Drawable dr; boolean needsNewDrawableAfterCache = false ; if (cs != null ) { dr = cs.newDrawable(wrapper); } else if (isColorDrawable) { dr = new ColorDrawable (value.data); } else { dr = loadDrawableForCookie(wrapper, value, id, density); } ... cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr); ... return dr; }
Drawable缓存 写入缓存 最后通过cacheDrawable()写入缓存
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 private void cacheDrawable (TypedValue value, boolean isColorDrawable, DrawableCache caches, Resources.Theme theme, boolean usesTheme, long key, Drawable dr) { final Drawable.ConstantState cs = dr.getConstantState(); if (cs == null ) { return ; } if (mPreloading) { final int changingConfigs = cs.getChangingConfigurations(); if (isColorDrawable) { if (verifyPreloadConfig(changingConfigs, 0 , value.resourceId, "drawable" )) { sPreloadedColorDrawables.put(key, cs); } } else { if (verifyPreloadConfig( changingConfigs, ActivityInfo.CONFIG_LAYOUT_DIRECTION, value.resourceId, "drawable" )) { if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0 ) { sPreloadedDrawables[0 ].put(key, cs); sPreloadedDrawables[1 ].put(key, cs); } else { sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs); } } } } else { synchronized (mAccessLock) { caches.put(key, theme, cs, usesTheme); } } }
根据上述源码,实际缓存的不是Drawable,而是Drawable.ConstantState对象
caches指的就是DrawableCache,由这个类负责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 28 29 30 31 32 33 34 35 36 37 38 39 class DrawableCache extends ThemedResourceCache <Drawable.ConstantState> { ... }abstract class ThemeResourceCache <T> { private ArrayMap<ThemeKey, LongSparseArray<WeakReference<T>>> mThemedEntries; private LongSparseArray<WeakReference<T>> mUnthemedEntries; private LongSparseArray<WeakReference<T>> mNullThemedEntries; public void put (long key, @Nullable Theme theme, @NonNull T entry, boolean usesTheme) { if (entry == null ) { return ; } synchronized (this ) { final LongSparseArray<WeakReference<T>> entries; if (!usesTheme) { entries = getUnthemedLocked(true ); } else { entries = getThemedLocked(theme, true ); } if (entries != null ) { entries.put(key, new WeakReference <>(entry)); } } } private LongSparseArray<WeakReference<T>> getUnthemedLocked (boolean create) { if (mUnthemedEntries == null && create) { mUnthemedEntries = new LongSparseArray <>(1 ); } return mUnthemedEntries; } }
写入缓存——实质缓存的是ConstantState
读取缓存 在缓存有效时,即caches.getInstance()不返回null
1 2 3 4 5 6 7 8 9 public Drawable getInstance (long key, Resources resources, Resources.Theme theme) { final Drawable.ConstantState entry = get(key, theme); if (entry != null ) { return entry.newDrawable(resources, theme); } return null ; }
读取缓存——实质从缓存中读取的是ConstantState
Drawable.ConstantState
ConstantState是一个抽象类,具体的实现都交由Drawable的子类实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static abstract class ConstantState { public abstract @NonNull Drawable newDrawable () ; public @NonNull Drawable newDrawable (@Nullable Resources res) { return newDrawable(); } public @NonNull Drawable newDrawable (@Nullable Resources res, @Nullable @SuppressWarnings("unused") Theme theme) { return newDrawable(res); } public abstract @Config int getChangingConfigurations () ; public boolean canApplyTheme () { return false ; } }
ConstantState的具体实现类,都交由Drawable的子类实现,就拿常用的BitmapDrawable示例。一般getResource().getDrawable()获取的也是这个对象。
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 public class BitmapDrawable extends Drawable { private BitmapState mBitmapState; public BitmapDrawable () { init(new BitmapState ((Bitmap) null ), null ); } public BitmapDrawable (Resources res) { init(new BitmapState ((Bitmap) null ), res); } ... final static class BitmapState extends ConstantState { final Paint mPaint; int mSrcDensityOverride = 0 ; int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; boolean mAutoMirrored = false ; @Config int mChangingConfigurations; boolean mRebuildShader; BitmapState(Bitmap bitmap) { mBitmap = bitmap; mPaint = new Paint (DEFAULT_PAINT_FLAGS); } BitmapState(BitmapState bitmapState) { mBitmap = bitmapState.mBitmap; mTint = bitmapState.mTint; mTintMode = bitmapState.mTintMode; mThemeAttrs = bitmapState.mThemeAttrs; mChangingConfigurations = bitmapState.mChangingConfigurations; mGravity = bitmapState.mGravity; mTileModeX = bitmapState.mTileModeX; mTileModeY = bitmapState.mTileModeY; mSrcDensityOverride = bitmapState.mSrcDensityOverride; mTargetDensity = bitmapState.mTargetDensity; mBaseAlpha = bitmapState.mBaseAlpha; mPaint = new Paint (bitmapState.mPaint); mRebuildShader = bitmapState.mRebuildShader; mAutoMirrored = bitmapState.mAutoMirrored; } @Override public boolean canApplyTheme () { return mThemeAttrs != null || mTint != null && mTint.canApplyTheme(); } @Override public Drawable newDrawable () { return new BitmapDrawable (this , null ); } @Override public Drawable newDrawable (Resources res) { return new BitmapDrawable (this , res); } @Override public @Config int getChangingConfigurations () { return mChangingConfigurations | (mTint != null ? mTint.getChangingConfigurations() : 0 ); } } ... private void init (BitmapState state, Resources res) { mBitmapState = state; updateLocalState(res); if (mBitmapState != null && res != null ) { mBitmapState.mTargetDensity = mTargetDensity; } } }
无论是从缓存中获取Drawable还是通过newDrawable()都需要从ConstantState开始创建Drawable对象,可以保证内部资源的一致,以达到资源复用 的目的。浅拷贝
Drawable共享了状态,一些配置的改变实质改变的是ConstantState,就会导致其中一个Drawable状态发生了变化,致使其他的Drawable变为同一状态。
mutate()
使Drawable变得可变,且操作无法还原。一旦调用无法撤销。
主要为了复制一份ConstantState,让newDrawable()之后的Drawable拥有自己的ConstantState,不会受到其他Drawable的干扰 。深拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 public @NonNull Drawable mutate () { return this ; } public Drawable mutate () { if (!mMutated && super .mutate() == this ) { mBitmapState = new BitmapState (mBitmapState); mMutated = true ; } return this ; }
Drawable加载 无法获取缓存时,需要区分以下三种情况:
ConstantState不为null
调用缓存得到的ConstantState.newDrawable()构造Drawable对象
是ColorDrawable类型
判断是否以#开头,是的话直接创建ColorDrawable对象。
没有缓存ConstantState且非ColorDrawable,需要加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private Drawable loadDrawableForCookie (@NonNull Resources wrapper, @NonNull TypedValue value, int id, int density) { ... try { if (file.endsWith(".xml" )) { final XmlResourceParser rp = loadXmlResourceParser( file, id, value.assetCookie, "drawable" ); dr = Drawable.createFromXmlForDensity(wrapper, rp, density, null ); rp.close(); } else { final InputStream is = mAssets.openNonAsset( value.assetCookie, file, AssetManager.ACCESS_STREAMING); AssetInputStream ais = (AssetInputStream) is; dr = decodeImageDrawable(ais, wrapper, value); } } finally { stack.pop(); } ... }
xml加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static Drawable createFromXmlForDensity (@NonNull Resources r, @NonNull XmlPullParser parser, int density, @Nullable Theme theme) throws XmlPullParserException, IOException { AttributeSet attrs = Xml.asAttributeSet(parser); ... Drawable drawable = createFromXmlInnerForDensity(r, parser, attrs, density, theme); return drawable; } static Drawable createFromXmlInnerForDensity (@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density, @Nullable Theme theme) throws XmlPullParserException, IOException { return r.getDrawableInflater().inflateFromXmlForDensity(parser.getName(), parser, attrs, density, theme); }
1 2 3 4 5 6 7 public final DrawableInflater getDrawableInflater () { if (mDrawableInflater == null ) { mDrawableInflater = new DrawableInflater (this , mClassLoader); } return mDrawableInflater; }
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 Drawable inflateFromXmlForDensity (@NonNull String name, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density, @Nullable Theme theme) throws XmlPullParserException, IOException { if (name.equals("drawable" )) { name = attrs.getAttributeValue(null , "class" ); if (name == null ) { throw new InflateException ("<drawable> tag must specify class attribute" ); } } Drawable drawable = inflateFromTag(name); if (drawable == null ) { drawable = inflateFromClass(name); } drawable.setSrcDensityOverride(density); drawable.inflate(mRes, parser, attrs, theme); return 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 ; } } @NonNull private Drawable inflateFromClass (@NonNull String className) { try { Constructor<? extends Drawable > constructor; synchronized (CONSTRUCTOR_MAP) { constructor = CONSTRUCTOR_MAP.get(className); if (constructor == null ) { final Class<? extends Drawable > clazz = mClassLoader.loadClass(className).asSubclass(Drawable.class); constructor = clazz.getConstructor(); CONSTRUCTOR_MAP.put(className, constructor); } } return constructor.newInstance(); } ... }
Drawable.inflateFromXmlForDensity()按照不同格式的xml分为三种加载方式:
<drawable class="CustonmDrawable">...</drawable>
从<drawable>读取class参数,得到CustomDrawable,向下调用到inflateFromClass()
<shape>...</shape>
根据<tag>对应的属性,调用inflateFromTag()新建具体的Drawable对象
<CustomDrawable>...</CustomDrawable>
直接调用inflateFromClass()加载CustomDrawable类
图片文件加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private Drawable decodeImageDrawable (@NonNull AssetInputStream ais, @NonNull Resources wrapper, @NonNull TypedValue value) { ImageDecoder.Source src = new ImageDecoder .AssetInputStreamSource(ais, wrapper, value); try { return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> { decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); }); } catch (IOException ioe) { return null ; } } public static Drawable decodeDrawable (@NonNull Source src, @Nullable OnHeaderDecodedListener listener) throws IOException { Bitmap bitmap = decodeBitmap(src, listener); return new BitmapDrawable (src.getResources(), bitmap); }
图片加载后,转换为BitmapDrawable对象。
总结
Drawable状态 在某些场景下Drawable需要切换不同的显示,例如<selector></selector>,需要根据View不同的状态切换显示。
View的状态 常见的有以下几种:
状态名称
对应属性
含义
pressed
<attr android:state_pressed>
是否处于按下状态,一般通过按压表现
enable
<attr android:state_enabled>
是否可以点击,通过setEnable()控制
focused
<attr android:state_focused>
是否处于聚焦状态,一般由按键操作引起的
selected
<attr android:state_selected>
是否处于选择状态,通过setSelected()控制
checked
<attr android:state_checked>
是否处于选中状态,多用于CheckBox之类可以选择控件
Drawable跟随View状态切换 拿上面调用的setSelected()举例
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 public void setSelected (boolean selected) { if (((mPrivateFlags & PFLAG_SELECTED) != 0 ) != selected) { invalidate(true ); refreshDrawableState(); dispatchSetSelected(selected); } } public void refreshDrawableState () { mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY; drawableStateChanged(); ViewParent parent = mParent; if (parent != null ) { parent.childDrawableStateChanged(this ); } } protected void drawableStateChanged () { final int [] state = getDrawableState(); boolean changed = false ; final Drawable bg = mBackground; if (bg != null && bg.isStateful()) { changed |= bg.setState(state); } ... }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public boolean setState (@NonNull final int [] stateSet) { if (!Arrays.equals(mStateSet, stateSet)) { mStateSet = stateSet; return onStateChange(stateSet); } return false ; } protected boolean onStateChange (int [] state) { return false ; } public boolean isStateful () { return false ; }
onStateChange()交由子类实现。其中支持状态切换的子类,比如ColorDrawable、BitmapDrawable、StateListDrawable
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 protected boolean onStateChange (int [] stateSet) { final boolean changed = super .onStateChange(stateSet); int idx = mStateListState.indexOfStateSet(stateSet); if (idx < 0 ) { idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD); } return selectDrawable(idx) || changed; } int indexOfStateSet (int [] stateSet) { final int [][] stateSets = mStateSets; final int N = getChildCount(); for (int i = 0 ; i < N; i++) { if (StateSet.stateSetMatches(stateSets[i], stateSet)) { return i; } } return -1 ; } public boolean selectDrawable (int index) { ... if (index >= 0 && index < mDrawableContainerState.mNumChildren) { final Drawable d = mDrawableContainerState.getChild(index); mCurrDrawable = d; mCurIndex = index; if (d != null ) { if (mDrawableContainerState.mEnterFadeDuration > 0 ) { mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration; } initializeDrawableForDisplay(d); } } else { mCurrDrawable = null ; mCurIndex = -1 ; } invalidateSelf(); ... }
当View的状态发生改变时,都会调用到refreshDrawableList()更新成对应状态的Drawable对象。
Drawable着色
Android需要做效果切换时,大多数都是UI提供多张效果图,可以在不同的效果进行切换。其实大多数情况下,只是切换图片颜色即可满足要求。
此时涉及Drawable着色。
1 2 3 ColorStateList state = ColorStateList.valueOf(Color.parseColor("#5a8386" )); view.setBackgroundTintList(state); view.setBackgroundTintMode(PorterDuff.Mode.ADD);
主要涉及的是两个方法:
View.setBackgroundTintList():设置着色配置
需要设置setBackgroundTintList()必须设置background才可以生效
View.setBackgroundTintMode():设置叠加模式
系统提供如下叠加模式PorterDuff.Mode
Alpha合成模式
混合模式
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 public void setBackgroundTintList (@Nullable ColorStateList tint) { if (mBackgroundTint == null ) { mBackgroundTint = new TintInfo (); } mBackgroundTint.mTintList = tint; mBackgroundTint.mHasTintList = true ; applyBackgroundTint(); } private void applyBackgroundTint () { if (mBackground != null && mBackgroundTint != null ) { final TintInfo tintInfo = mBackgroundTint; if (tintInfo.mHasTintList || tintInfo.mHasTintMode) { mBackground = mBackground.mutate(); if (tintInfo.mHasTintList) { mBackground.setTintList(tintInfo.mTintList); } if (tintInfo.mHasTintMode) { mBackground.setTintMode(tintInfo.mTintMode); } if (mBackground.isStateful()) { mBackground.setState(getDrawableState()); } } } }
转调到Drawable.setTintList()和Drawable.setTintMode()
1 2 3 4 5 6 public void setTint (@ColorInt int tintColor) { setTintList(ColorStateList.valueOf(tintColor)); } public void setTintList (@Nullable ColorStateList tint) {}
其中setTintList()交由子类实现
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 @Override public void setTintList (ColorStateList tint) { mColorState.mTint = tint; mTintFilter = updateTintFilter(mTintFilter, tint, mColorState.mTintMode); invalidateSelf(); } @Override public void setTintMode (Mode tintMode) { mColorState.mTintMode = tintMode; mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, tintMode); invalidateSelf(); } @Nullable PorterDuffColorFilter updateTintFilter (@Nullable PorterDuffColorFilter tintFilter, @Nullable ColorStateList tint, @Nullable PorterDuff.Mode tintMode) { if (tint == null || tintMode == null ) { return null ; } final int color = tint.getColorForState(getState(), Color.TRANSPARENT); if (tintFilter == null ) { return new PorterDuffColorFilter (color, tintMode); } tintFilter.setColor(color); tintFilter.setMode(tintMode); return tintFilter; } public void draw (Canvas canvas) { final ColorFilter colorFilter = mPaint.getColorFilter(); if ((mColorState.mUseColor >>> 24 ) != 0 || colorFilter != null || mTintFilter != null ) { if (colorFilter == null ) { mPaint.setColorFilter(mTintFilter); } ... } }
View通过setTintList()和setTintMode()设置Drawable,配置完成后生成对应的PorterDuffColorFilter在draw()设置到对应的paint属性。
实质操作的是Paint.setColorFilter()
Drawable动画
基础的就是AnimationDrawable,通过一个接一个的加载一系列的Drawable来创建一个动画,将他们按照顺序播放。
1 2 3 4 5 6 <animation-list xmlns:android ="http://schemas.android.com/apk/res/android" android:oneshot ="true" > <item android:drawable ="@drawable/rocket_thrust1" android:duration ="200" /> <item android:drawable ="@drawable/rocket_thrust2" android:duration ="200" /> <item android:drawable ="@drawable/rocket_thrust3" android:duration ="200" /> </animation-list >
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 public void run () { nextFrame(false ); } private void nextFrame (boolean unschedule) { int nextFrame = mCurFrame + 1 ; final int numFrames = mAnimationState.getChildCount(); final boolean isLastFrame = mAnimationState.mOneShot && nextFrame >= (numFrames - 1 ); if (!mAnimationState.mOneShot && nextFrame >= numFrames) { nextFrame = 0 ; } setFrame(nextFrame, unschedule, !isLastFrame); } private void setFrame (int frame, boolean unschedule, boolean animate) { if (frame >= mAnimationState.getChildCount()) { return ; } mAnimating = animate; mCurFrame = frame; selectDrawable(frame); if (unschedule || animate) { unscheduleSelf(this ); } if (animate) { mCurFrame = frame; mRunning = true ; scheduleSelf(this , SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]); } } public void scheduleSelf (@NonNull Runnable what, long when ) { final Callback callback = getCallback(); if (callback != null ) { callback.scheduleDrawable(this , what, when ); } }
自定义Drawable动画
使用AnimationDrawable支持的都是帧动画类型,需要耗费大量的资源去进行每一帧的渲染,如果是一些简单的动效可以通过自定义Drawable动画去实现。
实现Animatable接口 1 2 3 4 5 6 7 8 public interface Animatable { void start () ; void stop () ; boolean isRunning () ; }
后续还添加了Animateble2接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface Animatable2 extends Animatable { void registerAnimationCallback (@NonNull AnimationCallback callback) ; boolean unregisterAnimationCallback (@NonNull AnimationCallback callback) ; void clearAnimationCallbacks () ; public static abstract class AnimationCallback { public void onAnimationStart (Drawable drawable) {}; public void onAnimationEnd (Drawable drawable) {}; } }
自定义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 28 29 30 31 32 33 34 35 36 37 38 public class CircleAnimDrawable extends Drawable implements Animatable { @Override public void draw (@NonNull Canvas canvas) { } @Override public void setAlpha (int alpha) { } @Override public void setColorFilter (@Nullable ColorFilter colorFilter) { } @Override public int getOpacity () { return PixelFormat.TRANSLUCENT; } @Override public void start () { } @Override public void stop () { } @Override public boolean isRunning () { return false ; } }
现在需要实现一个不断扩散的圆形动画,扩散到一定大小后消失,并开始循环播放。这个时候就可以利用到ValueAnimator实现功能。
实现实例
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 public class CircleAnimDrawable extends Drawable implements Animatable { int width = 0 , height = 0 ; int center = 0 ; float radius = 0f ; float maxRadius = 0f ; Paint mPaint; private ValueAnimator valueAnimator; public CircleAnimDrawable () { mPaint = new Paint (Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.parseColor("#1BA8FB" )); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(5 ); } @Override public void draw (@NonNull Canvas canvas) { canvas.drawCircle(center, center, radius, mPaint); } @Override public void setAlpha (int alpha) { mPaint.setAlpha(alpha); } @Override public void setColorFilter (@Nullable ColorFilter colorFilter) { mPaint.setColorFilter(colorFilter); } @Override public int getOpacity () { return PixelFormat.TRANSLUCENT; } @Override public void start () { if (valueAnimator != null ) { valueAnimator.start(); } } @Override public void stop () { if (valueAnimator != null && valueAnimator.isRunning()) { valueAnimator.end(); } } @Override public boolean isRunning () { return valueAnimator != null && valueAnimator.isRunning(); } @Override protected void onBoundsChange (Rect bounds) { super .onBoundsChange(bounds); center = getBounds().width() >> 1 ; maxRadius = getBounds().width() >> 1 ; valueAnimator = ValueAnimator.ofFloat(0 , 1f ); valueAnimator.setRepeatCount(1 ); valueAnimator.setRepeatMode(ValueAnimator.RESTART); valueAnimator.setDuration(1000 ); valueAnimator.addUpdateListener(new ValueAnimator .AnimatorUpdateListener() { @Override public void onAnimationUpdate (ValueAnimator animation) { float percent = (float ) animation.getAnimatedValue(); radius = maxRadius * percent; mPaint.setAlpha( (255 -(int )(255 *percent))); invalidateSelf(); } }); } @Override public boolean setVisible (boolean visible, boolean restart) { boolean changed = super .setVisible(visible, restart); if (visible) { start(); } else if (changed) { stop(); } return changed; } }
拓展 View、Bitmap与Drawable的区别 Bitmap:位图信息的存储,存储每个像素信息。
View:Android系统的核心,主要有两个作用:draw绘制,测量
Drawable:存储的是对Canvas的一系列操作,但是仅仅支持绘制。Drawable和Bitmap通过BitmapDrawable建立联系,储存的是把Bitmap绘制到Canvas这一操作。
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 public BitmapDrawable (Resources res, Bitmap bitmap) { init(new BitmapState (bitmap), res); } public BitmapDrawable (Resources res, String filepath) { Bitmap bitmap = null ; try (FileInputStream stream = new FileInputStream (filepath)) { bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, stream), (decoder, info, src) -> { decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); }); } catch (Exception e) { } finally { init(new BitmapState (bitmap), res); if (mBitmapState.mBitmap == null ) { android.util.Log.w("BitmapDrawable" , "BitmapDrawable cannot decode " + filepath); } } } public final Bitmap getBitmap () { return mBitmapState.mBitmap; }
通过BitmapDrawable建立起Drawable和Bitmap的关联。
参考链接 PorterDuff.Mode
Paint详解