第六章 Android的Drawable
Drawable表示的是一种可以在Canvas上进行绘制的抽象概念,最常见的图片和颜色都可以是一个Drawable。 本章讲述:Drawable的层次关系、Drawable的分类、自定义Drawable。Drawable的优点:1.使用简单,比View成本低;2、非图片类型的Drawable占用空间较小。3.可以做出一些特殊的UI效果。
(一)Drawable简介
Drawable表示的是图像的概念而又不全是图片,Drawable常常被作为一个View的背景,一般分XML和图片两种,可以用代码来实现,不过这种方式比较复杂。Drawable作为一个抽象类,他衍生出了很多的子类,譬如ShapeDrawable、BitmapDrawable等。Object->Drawable->BitmapDrawable、ShapeDrawable等。
Drawable的内部宽高参数很重要,通过getIntrinsicWidth和getIntrinsicHeight这两个方法可以获取,但并不是所有的Drawable都有内部宽高,比如一张图片所形成的Drawable,为图片的高宽,但是如果你是颜色所形成的,那就自然是没有的。而且要注意的是,Drawable内部宽高不等于它的大小,与View一样大。
(二)Drawable的分类
Drawable的种类繁多,比如我们之前常用的ShapeDrawable,BitmapDrawable,LayerDrawable,StateListDrawable等,使用如下:
2.1.BitmapDrawable
表示一张图片,可直接引用原始图片,也可以通过XML的形式来描述(可展示更多效果)。
2.1.1.代码示例及相关说明
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:antialias="true"
android:dither="true"
android:filter="true"
android:gravity="center"
android:mipMap="true"
android:src="@color/colorPrimaryDark"
android:tileMode="clamp">
<!--1.android:src 资源ID,可以是图片也可以是颜色 2.android:antialias 是否开启图片抗锯齿,开启后图片会变得平滑一点,应当开启-->
<!--3.android:dither 是否开启抖动效果,当图片的像素配置和手机不一致,可让高质量的图片在低分辨率的屏幕上保持好的显示效果,应当开启-->
<!--4.android:filter 是否开启过滤效果,当图片尺寸被拉伸或者压缩时,开启过滤效果会保持比较好的显示效果,所以这个也可开启-->
<!--5.android:gravity 图片小于容器尺寸,可对图片定位,位置方向,上下左右,可以用“|”符号来实现不同选项叠加。-->
<!--6.android:mipMap 纹理映射,比较抽象,默认为false-->
<!--7.android:tileMode 下面细讲-->
</bitmap>
2.1.2. android:gravity和android:tileMode
android:gravity: 图片小于容器尺寸,可对图片定位,位置方向,上下左右,可以用“|”符号来实现不同选项叠加。举例属性如下所示:
android:tileMode: 平铺模式,分为disabled | clamp | repeat | mirror 其中disabled 是关闭平铺模式,这个也是默认值,开启后,gravity属性会无效,先说下其余三个属性的区别,三种都表示平铺模式:1.repeat表示简单的水平和竖直方向上平铺效果;2.mirror表示一种在水平和竖直方向上的镜面投影效果;3.clamp表示四周像素扩散效果。
2.1.3.NinePatchDrawable
自动根据宽/高进行相应的缩放并保证不失真。
2.2.ShapeDrawable
常见Drawable,可以理解为通过颜色构造的图片,它既可以是纯色的图形,也可以具有渐变的图形。
2.2.1.几个属性
1.android:shape 表示图片的形状,有四个选项,line(横线),oval(椭圆),rectangle(矩形),ring(圆环),他的默认值是矩形,而且line(横线)和ring(圆环)都必须通过stroke标签来指定宽高,颜色等信息,否则无法达到预期的效果。
2.size标签:android:width/android:height分别表示的是shape的宽高,是shape的固有大小,但不是最终大小。总的来说,<size>标签设置的宽高就是ShapeDrawable的固有宽/高,但作为View的背景时,并没有宽高这个概念,会适应(拉伸/缩小)view的宽高。
2.2.2.代码示例
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line">
<!--corners表示shape的四个角度,它只是用于矩形的shape,这里的角度是指圆角的程度,用px来表示,他有五个属性:
android:radius设置同样角度,优先级低,其他四个属性分别为最上角、最右角、最下角、最左角的角度 -->
<corners
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"
android:radius="10dp"
android:topLeftRadius="10dp"
android:topRightRadius="10dp" />
<!--gradient与solid标签是互相排斥的,因为solid表示纯色,而他表示渐变,他的属性如下,各种骚渐变-->
<gradient
android:angle="10"
android:centerColor="@color/colorPrimary"
android:centerX="10"
android:centerY="10"
android:endColor="@color/colorPrimary"
android:gradientRadius="10dp"
android:startColor="@color/colorAccent"
android:type="linear"
android:useLevel="true" />
<!--padding表示空白,但是他表示的不是shape的空白,而是包含他view的空白,而且有四个属性,左上右下-->
<padding
android:bottom="10dp"
android:left="10dp"
android:right="10dp"
android:top="10dp" />
<!--size:width/height分别表示的是shape的宽高,对于shape来说并没有宽高这个概念,作为view的背景他会适应view的宽高-->
<size
android:width="100dp"
android:height="100dp" />
<!--solid标签表示纯色填充,通过android:color来表示填充颜色-->
<solid android:color="@color/colorAccent" />
<!--shape的描边,分别表示宽度、颜色、虚线线段的间隔、虚线线段的宽度,注意:如果android:dashGap和android:dashwidth有任何一个为0的话,那么虚线就不能生效了-->
<stroke
android:width="100dp"
android:color="@color/colorPrimaryDark"
android:dashGap="@dimen/activity_horizontal_margin"
android:dashWidth="10dp" />
</shape>
2.3.LayerDrawable
LayerDrawable对应的xml是< layer-list>,他表示一种层次化的Drwable集合,可以理解为图层,通过不同的view达到叠加的效果。
2.3.1.相关说明
一个layer-list包含多个item,每个item是一个drawable,形成叠加的图层效果。常用属性包括android:bottom、android:left、android:right、android:top,分别表示Drawable相对于View上下左右的偏移量,单位是像素。android:drawable可以直接饮用Drawable资源,默认情况,所有Drawable会缩放至View的大小。
2.3.2微信中的文本输入框效果代码
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#0ac39e" />
</shape>
</item>
<item android:bottom="6dp">
<shape android:shape="rectangle">
<solid android:color="#ffffff" />
</shape>
</item>
<item
android:bottom="1dp"
android:left="1dp"
android:right="1dp">
<shape android:shape="rectangle">
<solid android:color="#ffffff" />
</shape>
</item>
</layer-list>
2.4.StateListDrawable
StateListDrawable对应的是< selector>标签,也表示Drawable的集合,每个Drawable对应着View的一种状态,系统会根据view的状态来选择出现的drawable。主要用来设置可单击的View的背景,最常见的是Button。
2.4.1语法如下
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android"
android:constantSize="true"
android:dither="true"
android:variablePadding="true">
<!--1.android:constantSize固有大小是否不随着状态的改变而改变的,false是随着状态的改变而改变,一般默认为false-->
<!--2.android:dither 是否开启抖动,在低质量屏幕上依然有好的效果。-->
<!--3.android:variablePadding padding是否随状态的改变而改变,一般为false-->
<item
android:drawable="@drawable/bitmapdrawable"
android:state_pressed="true"
android:state_focused="true"
android:state_hovered="true"
android:state_selected="true"
android:state_checkable="true"
android:state_checked="true"
android:state_enabled="true"
android:state_activated="true"
android:state_window_focused="true" />
<!--4.item标签标示一个具体的Drawable,android:drawable表示一个具体的id。-->
<!--5.android:state_pressed表示按下没有松开;android:state_hovere表示View已经获取了焦点;android:state_selected表示用户选择了View-->
<!--6.android:state_checked用户选中View,一般用于CheackBox,android:state_enabled表示View处于可用状态-->
</selector>
2.4.2.具体事例
根据View的状态从selector中选择对应的item,每一个对应着一个drawable,系统会按照从上到下来查找。还得要有默认的Item。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">、
<item
android:drawable="@drawable/button_pressed"
android:state_pressed="true" />
<item
android:drawable="@drawable/button_focused"
android:state_focused="true" />
<item
android:drawable="@drawable/button_normal" />
</selector>
2.5.LevelListDrawable
LevelListDrawable对应着<level-list>标签,同样表示一个drawable的集合。集合中的每一个Drawable都有一个等级level,根据不同的等级切换不同的Item,每个item是一个drawable。
代码示例:当它作为View的背景时,可以通过Drawable的setLevel设置不同的等级从而切换不同的Drawable;当它作为ImageView的前景Drawable时,可以通过ImageView的setImageLevel来切换Drawable。等级有范围:0~10000,最小(默认)是0。
<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@color/colorAccent"
android:maxLevel="0" />
<item
android:drawable="@color/colorPrimaryDark"
android:maxLevel="1" />
</level-list>
2.5.LevelListDrawable
LevelListDrawable对应着<level-list>标签,同样表示一个drawable的集合。集合中的每一个Drawable都有一个等级level,根据不同的等级切换不同的Item,每个item是一个drawable。
代码示例:当它作为View的背景时,可以通过Drawable的setLevel设置不同的等级从而切换不同的Drawable;当它作为ImageView的前景Drawable时,可以通过ImageView的setImageLevel来切换Drawable。等级有范围:0~10000,最小(默认)是0。
<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@color/colorAccent"
android:maxLevel="0" />
<item
android:drawable="@color/colorPrimaryDark"
android:maxLevel="1" />
</level-list>
2.6.TransitionDrawable
对应于<transition>标签,实现两个Drawable的淡入淡出效果。
代码示例:
步骤一:新建TransitionDrawable并定义
<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/colorAccent" />
<item android:drawable="@color/colorPrimary" />
</transition>
步骤二:在组件中引用Drawable
<TextView
android:id = "@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:background="@drawable/transitiondrawable"
/>
步骤三:在Activity通过startTransition和resetTransition来操作。实现渐变效果。
tv = (TextView) findViewById(R.id.tv);
TransitionDrawable drawable = (TransitionDrawable) tv.getBackground();
drawable.startTransition(1000);
2.7.InsetDrawable
InsetDrawable对应<inset>标签,可以将其他的Drawable内嵌到自己当中,并且可以在四周留下一定的距离,当一个View希望自己背景比实际区域小的时候就可以用InsetDrawable实现,当然通过LayerDrawable也可以实现该效果。
代码示例:
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetBottom="15dp"
android:insetLeft="15dp"
android:insetRight="15dp"
android:insetTop="15dp">
<shape>
<solid android:color="@color/colorAccent" />
</shape>
</inset>
2.8.ScaleDrawable
ScaleDrawable对应的是<scale>标签,可以指定自己的等级(level)将指定的Drawable缩放到一定的比例。
ScaleDrawable的等级,0为不可见,要想可见,需要不为0,这个在源码中可以看出来,我们看他的draw方法,根据onBoundsChanges源码的方法,ScaleDrawable的级别越大,那么内部的Drawable就看起来越大了,XML中定义的缩放比例越大,那么内部的Drawable看起来就越小。
我们看一个例子,将图片缩小为原大小的30%:
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_launcher"
android:scaleGravity="center"
android:scaleHeight="70%"
android:scaleWidth="70%">
</scale>
光设置drawable的XML不够,还需要将ScaleDrawable的等级设置为大于0小于10000(不要出了这个范围)的值。如下所示:
tv = (TextView) findViewById(R.id.tv);
ScaleDrawable testdrawable = (ScaleDrawable) tv.getBackground();
testdrawable.setLevel(1);
2.9.ClipDrawable
ClipDrawable对应的是<clip>标签,它可以根据自己的等级裁剪另一个Drawable,裁剪的方法可以通过android:clipOrientation(裁剪方向:水平竖直)和android:gravity(通过|组合使用)共同控制。
步骤一:新建ClipDrawable的XML。
<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
android:clipOrientation="vertical"
android:drawable="@color/colorPrimary"
android:gravity="bottom">
</clip>
步骤二:可以设置ImageView,也可以设置为View的背景。TextView不行。
<ImageView
android:id = "@+id/iv"
android:layout_width="100dp"
android:layout_height="100dp"
android:text="Hello World!"
android:src="@drawable/clipdrawable"
/>
步骤三:在代码中设置ClipDrawable的等级。
iv= (ImageView) findViewById(R.id.iv);
ClipDrawable cp = (ClipDrawable) iv.getDrawable();
cp.setLevel(2000);
需要说明的是:由于属性是bottom,因而从上往下进行裁剪的。等级为2000表示裁减了8000,在顶部裁减了80%的区域。
(二)自定义Drawable
Drawable的使用范围很单一,一个是作为ImageView中的图像来显示,还有就是作为View的背景,核心就是draw方法。大多数情况下Drawable是以View的背景形式出现的,可以通过重写Drawable的draw方法来绘制自定义Drwable。通常没有必要去自定义Drawable,在xml中无法使用。
场景:自定义Drawable来绘制一个圆形的Drawable,半径会随着View的变化而变化,可以作为View的通用背景。
代码示例:
public class CustomDrawable extends Drawable {
private Paint paint;
// 参考了ShapeDrawable和BitmapDrawable的源码(源码的重要性)
//主要方法,和View的draw方法相似。在画布上画圆。
@Override
public void draw(@NonNull Canvas canvas) {
final Rect r = getBounds();
float cx = r.exactCenterX();
float cy = r.exactCenterY();
canvas.drawCircle(cx, cy, Math.min(cx, cy), paint);
}
@Override
public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
paint.setAlpha(alpha);
invalidateSelf();
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
paint.setColorFilter(colorFilter);
invalidateSelf();
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}