Drawable

一、概念

Drawable有很多种,它们都表示一种图像的概念,但是它们又不全是图片,通过颜色也可以构造出各式各样的图像的效果。在实际开发中,Drawable常被用来作为View的背景使用。Drawable一般都是通过XML来定义的,也可以通过代码来创建,用代码创建会稍显复杂。

Drawable的内部宽/高可以通过getIntrinsicWidth和getIntrinsicHeight这两个方法来获取,但是并不是所有的Drawable都有内部宽/高,比如一张图片所形成的Drawable,它的内部宽/高就是图片的宽/高,但是一个颜色所形成的Drawable就没有宽/高的概念。另外需要注意的是,Drawable的内部宽/高不等同于它的实际大小,当用作View的背景时,Drawable会被拉伸至View的同等大小。

二、分类

1.BitmapDrawable

在实际开发中,我们可直接引用原始的图片,也可通过XML的方式来描述它。
通过XML来描述的BitmapDrawable可以设置更多的效果,如下:

<?xml version="1.0" encoding="utf-8"?>
<bitmap
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@[package:]drawable/drawable_resource"
    android:antialias="[true | false]"
    android:dither="[true | false]"
    android:filter="[true | false]"
    android:gravity="[top | bottom | left | right |
     center_vertical | fill_vertical | center_horizontal |
      fill_horizontal | center | fill |
       clip_vertical | clip_horizontal]"
    android:mipMap="[true | false]"
    android:tileMode="[disabled | clamp | repeat | mirror]" />

android:src:图片的资源id。
android:antialias:是否开启图片抗锯齿。开启后会让图片变得平滑,同时也会一定程度上降低图片的清晰度。
android:dither:是否开启抖动效果。开启后可让高质量的图片在低质量的屏幕上能保持较好的显示效果。
android:filter:是否开启过滤效果。当图片尺寸被拉伸或压缩时,开启过滤效果可保持较好的显示效果。
android:gravity:当图片小于容器的尺寸时,设置此属性对图片进行定位。此属性的可选项较多,也可用|来组合使用。
android:mipMap:是否开启纹理映射。
android:tileMode:平铺模式。

2.NinePatchDrawable

表示一张.9格式的图片。.9图片可自动地根据所需的宽/高进行相应的缩放并保证不失真。
在实际使用中可以直接引用.9图片,也可以通过XML来描述它。
通过XML描述如下:

<?xml version="1.0" encoding="utf-8"?>
<nine-patch
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@[package:]drawable/drawable_resource"
    android:dither="[true | false]" />

android:src:图片的资源id。
android:dither:是否开启抖动效果。开启后可让高质量的图片在低质量的屏幕上能保持较好的显示效果。

3.ShapeDrawable

可以理解为通过颜色来构造的图形,既可以是纯色的图形,也可以是具有渐变效果的图形。

<?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" />

android:shape:图形的形状,有四个选项:rectangle(矩形)、oval(椭圆)、line(横线)和ring(圆环),默认是rectangle。另外line和ring必须通过<stroke>标签来指定线的宽度和颜色等信息,否则无法达到预期的显示效果。
<corners>:表示shape的四个圆角的角度,只适用于矩形,属性如下:
android:radius:为四个角同事设定相同的角度。优先级比以下4个属性要低。
android:topLeftRadius:左上角的角度。
android:topRightRadius:右上角的角度。
android:bottomLeftRadius:左下角的角度。
android:bottomRightRadius:右下角的角度。
<solid>:纯色填充,通过android:color指定填充的颜色。
<gradient>:渐变效果,与<solid>纯色填充是互相排斥的,属性如下:
android:angle:渐变的角度。默认为0,其值必须为45的倍数。此角度会影响渐变的方向,0表示从左到右,90表示从下到上。
android:centerX:渐变的中心点的X坐标。
android:centerY:渐变的中心点的Y坐标。
android:startColor:渐变的起始色。
android:centerColor:渐变的中间色。
android:endColor:渐变的结束色。
android:gradient:渐变半径。仅当android:type="radial"时有效。
android:useLevel:一般为false,当Drawable作为StateListDrawable时为true。
android:type:渐变的类别。有linear(线性渐变)、radial(径向渐变)、sweep(扫描线渐变),默认为linear。
<stroke>:描边。
android:width:描边的宽度。
android:color:描边的颜色。
android:dashWidth:虚线的宽度。
android:dashGap:虚线之间的间隔。
<padding>:空白,表示的不是shape的空白,而是包含它的View的空白,有left、top、right、bottom四个属性。
<size>:大小。有两个属性:android:width和android:height,分别表示shape的内部宽/高,但是一般来说它并不是shape最终显示的大小。也就是说,通过<size>标签可以设置ShapeDrawable的内部宽/高,但是作为View的背景时,shape还会被拉伸或者缩小为View的大小。

4.LayerDrawable

对应<layer-list>标签,表示一种层次化的Drawable集合,通过将不同的Drawable放置在不同的层上面从而达到一种叠加后的效果。

<?xml version="1.0" encoding="utf-8"?>
<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android"
    <item
        android:drawable="@[package:]drawable/drawable_resource"
        android:id="@[+][package:]id/resource_name"
        android:top="dimension"
        android:right="dimension"
        android:bottom="dimension"
        android:left="dimension" />
    <!-- 其他item -->
</layer-list>

一个layer-list可包含多个item,每个item表示一个Drawable。可在android:drawable中引用一个现有的Drawable资源,也可在<item>中自定义Drawable。
默认情况下,layer-list中的所有Drawable都会被缩放至View的大小。可设置Drawable相对于View的上下左右偏移量。另外对于bitmap,需要使用其android:gravity来控制图片的显示效果。
layer-list有层次的概念,下面的item会覆盖上面的item。通过合理的分层,可实现一些特殊的叠加效果。

5.StateListDrawable

对应<selector>标签,表示一个Drawable的集合。每个Drawable对应着View的一种状态,系统会根据View的状态来选择合适的Drawable。StateListDrawable主要用于设置可单击的View的背景,最常见的是Button。

<?xml version="1.0" encoding="utf-8"?>
<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="@[package:]drawable/drawable_resource"
        android:state_pressed="[true | false]"
        android:state_focused="[true | false]"
        android:state_hovered="[true | false]"
        android:state_selected="[true | false]"
        android:state_checkable="[true | false]"
        android:state_checked="[true | false]"
        android:state_enabled="[true | false]"
        android:state_activated="[true | false]"
        android:state_window_focused="[true | false]" />
    <!-- 其他item -->
</selector>

android:constantSize:StateListDrawable的内部宽/高是否不随着其状态的改变而改变,因为状态的改变会导致StateListDrawable切换到具体的Drawable,而不同的Drawable具有不同的内部宽/高。默认为false,即随着状态的改变而改变大小。若为true,则表示其内部所有Drawable的内部宽/高的最大值。
android:dither:是否开启抖动效果。开启此选项可以让图片在低质量的屏幕上仍然获得较好的显示效果。此选项默认值为true。
android:variblePadding:StateListDrawable的padding是否随着其状态的改变而改变。默认为false,表示其内部所有Drawable的padding的最大值。若为true,表示会随着状态的改变而改变。
<item>:表示某种状态下的一个具体的Drawable。用android:drawable指定一个现有Drawable的资源id,剩下的属性表示的是View的各种状态。
系统会按照从上到下的顺序查找,直至查找到第一条匹配的item。一般来说,默认的item都应该放在selector的最后一条,并且不附带任何的状态。

6.LevelListDrawable

对应<level-list>标签,表示一个Drawable集合,集合中的每个Drawable都有一个等级的概念。根据不同的等级,LevelListDrawable会切换为对应的Drawable。

<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@[package:]drawable/drawable_resource"
        android:maxLevel="integer"
        android:minLevel="integer" />
    <!-- 其他item -->
</level-list>

7.TransitionDrawable

对应于<transition>标签,用于实现两个Drawable之间的淡入淡出效果。

<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@[package:]drawable/drawable_resource"
        android:id="@[+][package:]id/resource_name"
        android:top="dimension"
        android:right="dimension"
        android:bottom="dimension"
        android:left="dimension" />
    <!-- 另一个item,注意transition只包含两个item -->
</transition>

常将TransitionDrawable作为View的背景,再调用它的startTransition和reverseTransition方法来实现淡入淡出效果以及它的逆过程。代码如下:

ImageView imageview = (ImageView) findViewById(R.id.imageview);
TransitionDrawable drawable = (TransitionDrawable) imageview.getBackground();
drawable.startTransition(1000);

8.InsetDrawable

对应<inset>标签,可将其他Drawable内嵌到自己当中,并可在四周留出一定的间距。当一个View希望自己的背景比自己的实际区域小的时候,可采用InsetDrawable来实现,同时我们知道,通过LayoutDrawable也可以实现这种效果。

<?xml version="1.0" encoding="utf-8"?>
<inset 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@[package:]drawable/drawable_resource"
    android:inset="dimension"
    android:insetTop="dimension"
    android:insetRight="dimension"
    android:insetBottom="dimension"
    android:insetLeft="dimension" />

9.ScaleDrawable

对应<scale>标签,将Drawable缩放到一定比例。

<?xml version="1.0" encoding="utf-8"?>
<scale 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@[package:]drawable/drawable_resource"
    android:scaleGravity="[top | bottom | left | right |
        center_vertical | center_horizontal | center |
        fill_vertical | fill_horizontal | fill |
        clip_vertical | clip_horizontal]"
    android:scaleWidth="percentage"
    android:scaleHeight="percentage" />

缩放比例越大,内部的Drawable就越小;level越大,内部的Drawable就越大。
由于level默认为0,若level为0时,ScaleDrawable不可见。

10.ClipDrawable

对应<clip>标签,用来裁剪另一个Drawable。

<?xml version="1.0" encoding="utf-8"?>
<clip 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@[package:]drawable/drawable_resource"
    android:clipOrientation="[vertical | horizontal]"
    android:gravity="[top | bottom | left | right |
        center_vertical | center_horizontal | center |
        fill_vertical | fill_horizontal | fill |
        clip_vertical | clip_horizontal]" />

android:clipOrientation:表示裁剪方向,可选为水平和竖直。
android:gravity:表示对齐方式,需要和clipOrientation一起发挥作用。
ClipDrawable的裁剪程度由level控制,调用setLevel方法可修改此值。其取值范围为0~10000。0表示完全裁剪,即整个Drawable都不可见;而10000表示不裁剪,即整个Drawable都可见。

三、自定义Drawable

Drawable的工作原理很简单,其核心就是draw方法。系统会调用Drawable的draw方法来绘制View的背景或ImageView的图像,我们可通过重写其draw方法来实现自定义Drawable。通常我们没有必要去自定义Drawable,因为无法在XML中使用自定义Drawable,这就降低了其使用范围。

创建自定义Drawable,必须重写其draw、setAlpha、setColorFilter、getOpacity等方法。以下为自定义Drawable示例:

//CustomDrawable 
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;
    }
}

//MainActivity
CustomDrawable drawable = new CustomDrawable(Color.BLUE);
imageView.setBackgroundDrawable(drawable);

若自定义的Drawable有内部宽/高时,要重写getIntrinsicWidth和getIntrinsicHeight这两个方法,因为它们会影响到View的wrap_content布局。
需要注意的是,Drawable的内部宽/高不等同于Drawable的实际大小。可通过getBounds方法获得Drawable的实际大小,一般与它的View尺寸相同。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容