Drawable
是高级UI必须了解的一个环节,在自定义View中,Drawable
可以起到很重要的辅助作用。本文主要讲解的API有:BitmapDrawable
、ShapeDrawable
、GradientDrawable
、LayerDrawable
、StateListDrawable
、LevelListDrawable
、TransitionDrawable
、ColorDrawable
、ScaleDrawable
、InsetDrawable
、ClipDrawable
、PictureDrawable
、RotateDrawable
、RoundedBitmapDrawable
、RippleDrawable
、PaintDrawable
、NinePatchDrawable
、DrawerArrowDrawable
、CircularProgressDrawable
、AdaptiveIconDrawable
、VectorDrawable
、AnimationDrawable
、AnimatedStateListDrawable
、AnimatedVectorDrawable
、AnimatedImageDrawable
。
什么是Drawable?
-
Drawable
是一种可以在Canvas上进行绘制的抽象的概念。Drawable
是一个可以调用Canvas
来进行绘制的上层工具。Drawable.draw(canvas)
可以将Drawable
设置的绘制内容绘制到Canvas
中。 -
Drawable
的内部存储的是绘制规则,这个规则可以是一个具体的Bitmap
,也可以是一个纯粹的颜色
,甚至可以是一个抽象
。Drawable
可以不含有具体的像素信息,只要它含有的信息足以在draw(canvas)
方法中被调用时进行绘制就够了。也就是说,颜色、图片等都可以是一个Drawable
。 -
Drawable
可以通过XML定义,或者通过代码构建。 - Android 中Drawable是一个抽象类,每个具体的Drawable都是其子类。
- 由于
Drawable
存储的只是绘制规则,因此他在draw()
方法被调用前,需要先调用Drawable.setBounds()
来为它设置绘制边界。
Drawable的优点
- 使用简单,比自定义View的成本低
- 非图片类的Drawable所占用空间小,能减小apk大小
Drawable内部宽高的获取
- 一般getIntrinsicWidth/Height 能获取内部宽/高
- 图片Drawable其内部宽高就是图片的宽高
- 颜色Drawable没有内部宽高的概念
- 内部宽高不等同于他的大小,一般Drawable没有大小概念(作为View背景时,会被拉伸至View的大小)
各种Drawable子类实现
【BitmapDrawable】
BitmapDrawable
对应的xml标签是bitmap,表示一张图片。
xml代码如下:
bitmap.xml
其中,属性的意义是:
- android:antialias:表示是否开启抗锯齿功能,一般为true;
- android:dither:表示是否开启抖动,一般为true;
- android:filter:表示是否开启过滤效果,一般为true;
- android:tileMode:表示是否平铺模式 ,disable默认不平铺;repeat表示水平和竖直方向的平铺;mirror表示水平和竖直方向的镜面投影效果;clamp表示四周图像会扩展到周围区域。
将BitmapDrawable
加载到ImageView中
【ShapeDrawable】
和【GradientDrawable】
它们对应的标签是shape,通过颜色构建的图形,其中GradientDrawable
是梯度渐变的对象。
xml代码如下:
其中,属性的意义是:
- shape
根元素,其android:shape
属性定义了这个xml文件定义的形状,可以是retangle(矩形),oval(椭圆),line 和 ring(圆环)。
- corners(角)
表示的是矩形的四个角,只能用在android:shape = "rectangle" 的时候,一般情况下就是用来实现圆角矩形的效果
- stroke(描边)
android:dashWidth
:组成虚线的线段的宽度
android:dashGap="2dp"
:组成虚线的线段之间的间隔
- gradient(渐变)
android:angle
:渐变的角度,默认为0,其值必须是45的倍数
android:centerX
:渐变中心点的横坐标
android:startColor
:渐变的起始颜色,还有中间色和结束色
android:gradientRadius
:渐变半径
android:type
:渐变类别;line(线性渐变)、radial(径向渐变)sweep(扫描渐变)
- solid(填充)
表示纯色填充
- padding
表示包含它的view的空白
- size
Drawable大小
【LayerDrawable】
LayerDrawable
对应的标签是layer-list,表示层次化的Drawable集合。常常用于多张图像组合成一个图像。
xml代码如下:
效果展示:
【StateListDrawable】
StateListDrawable
对应的标签是selector,Drawable集合,其中每个Drawable对应一个状态(如:点击时状态、获取焦点时状态、选中时状态)。
xml代码如下:
效果如下:
添加到ImageView中
【LevelListDrawable】
LevelListDrawable
对应的标签是level-list,Drawable集合,其中每个Drawable对应一个等级。
xml代码如下:
其中,maxLevel属于最大等级,minLevel属于最小等级。
将LevelListDrawable
设置为ImageView的背景。
由于默认等级是0,所以显示的默认图像是maxLevel
为1的图像。
在代码中将Drawable
等级设置为2,如下:
imageview.getDrawable().setLevel(2);
那么,就会展示maxLevel = 2的图像。
【TransitionDrawable】
TransitionDrawable
是LayerDrawable
的子类,它对应的标签是transition,用于实现两个Drawable的切换动画。
xml代码如下:
将TransitionDrawable
设置到ImageView
最后在代码中启动动画
TransitionDrawable drawable=(TransitionDrawable)imageview.getBackground();
drawable.startTransition(1000);
如果将imageview.getBackground
改成imageview.getDrawable
,那么android:background="@drawable/transition_bitmap"
也要改成android:src="@drawable/transition_bitmap"
。
效果展示:
如图所示,默认显示第一张,当执行startTransition(int durationMillis)
方法时,第二层开始显示渐变显示,durationMillis
为渐变显示的时长。
另外,图中,显示第二层的图像之后第一层的图像仍然能看到是因为这两张图像都为透明背景。
【ColorDrawable】
ColorDrawable
对应的标签是color,仅仅表示颜色。
xml代码如下:
将ColorDrawable
设置到ImageView:
【ScaleDrawable】
ScaleDrawable
对应的标签是scale,可将指定的Drawable缩放到一定的比例。
xml代码如下:
其中属性的意义是:
scaleGravity
图像缩放后最终摆放的方位。
scaleHeight
表示Drawable的高的缩放比例,值越大,内部Drawable的高度显示得越小,例如android:scaleHeight=”70%”,那么显示时Drawable的高度只有原来的30%。
scaleWidth
表示Drawable的宽的缩放比例,值越大,内部Drawable的宽显示得越小,例如android:scaleWidth=”70%”,那么显示时Drawable的宽度只有原来的30%。
代码实现:
当设置为imageview的src
属性
ScaleDrawable drawable = (ScaleDrawable) ivShow.getDrawable();
drawable.setLevel(1);
当设置为imageview的background
属性
ScaleDrawable scale= (ScaleDrawable) scaleImage.getBackground();
scale.setLevel(1);
当scaleHeight
和scaleWidth
都为30%时,图片展示的大小为原本大小的70%,如图:
当scaleHeight
和scaleWidth
都为70%时,图片展示的大小为原本大小的30%,如图:
除此之外,通过drawable.setLevel(1)
设置等级,默认等级为0并且不显示图像,当设置为[0,10000]之间的任意数时显示图像。一般将等级设置为1。
我们来看一下设置等级的源码:
public final boolean setLevel(@IntRange(from=0,to=10000) int level) {
if (mLevel != level) {
mLevel = level;
return onLevelChange(level);
}
return false;
}
源码中很明显的告诉我们,这个等级的取值范围为[0,10000],那么这个等级有什么用呢?
假设将一个图像缩小30%,也就是说,从100%缩小到70%,那么可以将这个缩放的过程看成一个由10000张图片组成的动画,当等级设置为1时,就显示缩放到70%后的最后一张,如果设置为5000,那么就显示倒数第5000张。
既然这样,我们可以添加一个进度条来验证个结论,如下:
【InsetDrawable】
InsetDrawable
对应的标签是inset,可以将其他Drawable内嵌到自己当中。
xml代码如下:
它的四个属性比较简单,一张图就可以看懂了,如下:
【ClipDrawable】
ClipDrawable
对应的标签是clip,根据当前等级来裁剪另一个Drawable。
xml代码很简单,如下:
代码实现如下:
final ClipDrawable drawable = (ClipDrawable) imageview.getBackground();
SeekBar seekBar = (SeekBar)findViewById(R.id.seekbar);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int max = seekBar.getMax();
double scale = (double)progress/(double)max;
drawable.setLevel((int) (10000*scale));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
图片展示如下:
【PictureDrawable】
PictureDrawable
无xml表示方法,它的使用方式如下:
// 包装成为PictureDrawable
PictureDrawable drawable = new PictureDrawable(mPicture);
// 设置绘制区域
drawable.setBounds(0,0,250,mPicture.getHeight());
// 绘制
drawable.draw(canvas);
【RotateDrawable】
RotateDrawable
对应的标签是rotate。
xml代码如下:
其中属性的意义是:
fromDegrees:从多少的角度开始旋转
toDegrees:到多少的角度结束旋转
pivotX:旋转的中心在图片X轴的百分比
pivotY:旋转的中心在图片Y轴的百分比
代码实现如下:
final RotateDrawable drawable = (RotateDrawable) imageview.getBackground();
SeekBar seekBar = (SeekBar)findViewById(R.id.seekbar);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int max = seekBar.getMax();
double scale = (double)progress/(double)max;
drawable.setLevel((int) (10000*scale));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
效果展示如下:
【RoundedBitmapDrawable】
RoundedBitmapDrawable
没有对应的标签,不能在xml中体现,它的使用方法如下:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.che);
RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(getResources(), bitmap);
roundedBitmapDrawable.setCornerRadius(100);
imageview.setImageDrawable(roundedBitmapDrawable);
效果如下:
如果需要显示圆形图像,那么代码如下:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.che);
RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(getResources(), bitmap);
roundedBitmapDrawable.setCircular(true);
imageview.setImageDrawable(roundedBitmapDrawable);
效果如下:
这里图像变形了,那是必然的,因为图片做了拉伸,如果不想变形,那么只能先把图片处理成一个正方形图片再使用RoundedBitmapDrawable
生成圆形图片。
【RippleDrawable】
RippleDrawable
对应的标签是,它是体现水波纹动画的Drawable。(又叫触摸反馈动画)RippleDrawable
只在Android 5.0以上的设备有效。
定义一个按钮:
现在的Button
都默认支持MD风格,其效如下:
当将RippleDrawable
设置为背景会是什么效果呢?
RippleDrawable
的xml标签是ripple,xml代码如下:
以上代码是无边框的水波纹动画
,将这个作为背景设置到Button控件上,代码如下:
效果如下:
添加边界后的xml代码如下:
效果如下:
【PaintDrawable】
PaintDrawable
不支持XML实现,只能通过代码去实现,特有的方法是:
void setCornerRadii (float[] radii)
为四个角的每一个指定半径。 对于每个角落,数组包含2个值[X_radius,Y_radius]。 角落的顺序是左上角,右上角,右下角,左下角。
实现圆角矩形,示例代码如下:
PaintDrawable drawable = new PaintDrawable(Color.GREEN);
drawable.setCornerRadii(new float[]{10,10,20,20,30,30,50,50});
imageview.setBackground(drawable);
效果展示:
还有一个特定方法,和前者的参数不一样:
void setCornerRadius (float radius)
指定矩形拐角的半径。
实现圆角矩形,示例代码如下:
PaintDrawable drawable3 = new PaintDrawable(Color.GREEN);
drawable3.setCornerRadius(30);
mTextView.setBackground(drawable3);
效果展示:
【NinePatchDrawable】
有听说过9path图片吗,就是所谓的.9图,在Android中非常适合背景,可以指定图片中某区域进行伸缩。
NinePatchDrawable
为可调整大小的位图,带有可定义的可伸缩区域。
xml代码如下:
- dither:防抖动
- che:任意.9图;
使用方法也比较简单,如下:
当然也可以直接将.9图片资源设置为背景:
【DrawerArrowDrawable】
这个是配合抽屉布局返回键使用的,用法如下:
DrawerArrowDrawable drawerArrowDrawable = new DrawerArrowDrawable(this);
drawerArrowDrawable.setAlpha(1);
drawerArrowDrawable.setSpinEnabled(false);
drawerArrowDrawable.setDirection(DrawerArrowDrawable.ARROW_DIRECTION_LEFT);
drawerArrowDrawable.setColor(Color.BLACK);
mDrawerToggle.setDrawerArrowDrawable(drawerArrowDrawable);
【CircularProgressDrawable】
代码实现如下:
CircularProgressDrawable circularProgressDrawable = new CircularProgressDrawable(this);
// 设置绘制进度弧长
circularProgressDrawable.setStartEndTrim(0,300);
// 设置样式
circularProgressDrawable.setStyle(CircularProgressDrawable.LARGE);
// 设置边界
//circularProgressDrawable.setBounds(0,0,5,5);
// 设置环形的宽度
circularProgressDrawable.setStrokeWidth(10f);
// 设置环形的节点显示(Paint.Cap.ROUND即圆角)
circularProgressDrawable.setStrokeCap(Paint.Cap.ROUND);
// 设置环形的半径(控制环形的尺寸)
circularProgressDrawable.setCenterRadius(50f);
// 启用箭头
circularProgressDrawable.setArrowEnabled(true);
// 设置箭头的尺寸
circularProgressDrawable.setArrowDimensions(20, 20);
// 在箭头的尺寸上缩放倍数, 如果没有设置尺寸则无效
circularProgressDrawable.setArrowScale(2f);
// 判断是否在运行中
circularProgressDrawable.isRunning();
使用如下:
// 运行
circularProgressDrawable.start();
// 停止
circularProgressDrawable.stop();
效果展示:
【AdaptiveIconDrawable】
在Android O(Android 8.0)google推出了Launcher图标的自适应功能。
在3.x的Android Studio中,可以手动设置自适应Launcher图标,步骤如下:
File --> New --> Image Asset
除了手动设置自适应图标之外,还可以使用AdaptiveIconDrawable
来实现。
AdaptiveIconDrawable
的标签是adaptive-icon,xml代码如下:
自适应图标由前景和背景两部分组成。
【VectorDrawable】
VectorDrawable
是矢量图,由于矢量图不会失真,图片较小,所以比较适合做自适应图标,它的标签是vector,xml表示如下:
矢量图由线条组成,实现它的标签是
Android Studio生成适量图的方法如下:
File --> New --> Vector Asset
【AnimationDrawable】
帧动画,它的xml标签是animation-list,xml代码如下:
其中oneshot表示是否只执行一次动画。
使用代码如下:
AnimationDrawable animationDrawable = (AnimationDrawable) imageview.getDrawable();
animationDrawable.start();
效果展示:
【AnimatedStateListDrawable】
AnimatedStateListDrawable
在Android 5.0之后开始支持。其xml标签是animated-selector,xml代码如下:
根据视图的状态执行动画。
【AnimatedVectorDrawable】
AnimatedVectorDrawable
即矢量图动画,矢量图本身不具备动画特性,需要结合属性动画完成矢量图动画。其xml标签是animated-vector,xml代码如下:
可以查看这边文章了解详细Android动画<第十一篇>:矢量图动画。
【AnimatedImageDrawable】
Android 9.0 引入了AnimatedImageDrawable类,用于绘制和显示 GIF 和 WebP 动画图像。 AnimatedImageDrawable 的工作方式与 AnimatedVectorDrawable 的相似之处在于,都是渲染线程驱动 AnimatedImageDrawable 的动画。 渲染线程还使用工作线程进行解码,因此,解码不会干扰渲染线程的其他操作。 这种实现机制允许您的应用在显示动画图像时,无需管理其更新,也不会干扰应用界面线程上的其他事件。
可使用 ImageDecoder 的实例对 AnimatedImageDrawable 进行解码。 代码如下:
Drawable decodedAnimation = ImageDecoder.decodeDrawable(ImageDecoder.createSource(getResources(), R.drawable.demo));
if (decodedAnimation instanceof AnimatedImageDrawable) {
// Prior to start(), the first frame is displayed.
((AnimatedImageDrawable) decodedAnimation).start();
}
[本章完...]