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 <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 //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 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
分为三种加载方式:
图片文件加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 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 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详解