Android的Drawable

1,931 阅读20分钟

Drawable的种类繁多,进入Drawable类,按ctrl+H快捷键可以在studio的右边看到Drawable的层次关系图。常见的有BitmapDrawable、ShapeDrawable、LayerDrawable以及StateListDrawable等,这里就不一一列举了,下面会分别介绍它们的使用细节。

BitmapDrawable

这几乎是最简单的Drawable了,它表示的就是一张图片。在实际开发中,我们可以直接引用原始的图片即可,但是也可以通过XML的方式来描述它,通过XML来描述的BitmapDrawable可以设置更多的效果,如下所示:

<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/jinzita"
    android:antialias="true"
    android:dither="true"
    android:filter="true"
    android:gravity="center|right"
    android:mipMap="false"
    android:tileMode="repeat">

</bitmap>

下面是它各个属性的含义。

  • android:src这个很简单,就是图片的资源id。
  • android:antialias是否开启图片抗锯齿功能。开启后会让图片变得平滑,同时也会在一定程度上降低图片的清晰度,但是这个降低的幅度较低以至于可以忽略,因此抗锯齿选项应该开启。
  • android:dither是否开启抖动效果。当图片的像素配置和手机屏幕的像素配置不一致时,开启这个选项可以让高质量的图片在低质量的屏幕上还能保持较好的显示效果,比如图片的色彩模式为ARGB8888,但是设备屏幕所支持的色彩模式为RGB555,这个时候开启抖动选项可以让图片显示不会过于失真。在Android中创建的Bitmap一般会选用ARGB8888这个模式,即ARGB四个通道各占8位,在这种色彩模式下,一个像素所占的大小为4个字节,一个像素的位数总和越高,图像也就越逼真。根据分析,抖动效果也应该开启。
  • android:filter是否开启过滤效果。当图片尺寸被拉伸或者压缩时,开启过滤效果可以保持较好的显示效果,因此此选项也应该开启。
  • android:gravity当图片小于容器的尺寸时,设置此选项可以对图片进行定位。这个属性的可选项比较多,不同的选项可以通过“|”来组合使用。
  • android:mipMap这是一种图像相关的处理技术,也叫纹理映射,比较抽象,这里也不对其深究了,默认值为false,在日常开发中此选项不常用。
  • android:tileMode平铺模式。这个选项有如下几个值:["disabled" | "clamp" | "repeat" | "mirror"],其中disable表示关闭平铺模式,这也是默认值,当开启平铺模式后,gravity属性会被忽略。这里主要说一下repeat、mirror和clamp的区别,这三者都表示平铺模式,但是它们的表现却有很大不同。repeat表示的是简单的水平和竖直方向上的平铺效果;mirror表示一种在水平和竖直方向上的镜面投影效果;而clamp表示的效果就更加奇特,图片四周的像素会扩展到周围区域。具体的效果可以自行尝试,注意要用作View的背景才有效果,ImageView直接引用没有效果。

接下来介绍NinePatchDrawable,它表示的是一张.9格式的图片,.9图片可以自动地根据所需的宽/高进行相应的缩放并保证不失真,之所以把它和BitmapDrawable放在一起介绍是因为它们都表示一张图片。和BitmapDrawable一样,在实际使用中直接引用图片即可,但是也可以通过XML来描述.9图,如下所示。

<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/jinzita"
    android:dither="true"
    android:antialias="true"
    android:filter="true">

</nine-patch>

上述XML中的属性的含义和BitmapDrawable中的对应属性的含义是相同的,这里就不再描述了,另外,在实际使用中发现在bitmap标签中也可以使用.9图,即BitmapDrawable也可以代表一个.9格式的图片。

ShapeDrawable

ShapeDrawable是一种很常见的Drawable,可以理解为通过颜色来构造的图形,它既可以是纯色的图形,也可以是具有渐变效果的图形。ShapeDrawable的语法稍显复杂,如下所示。

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle | oval | line | ring">
    <corners
        android:bottomLeftRadius="integer"
        android:bottomRightRadius="integer"
        android:radius="integer"
        android:topLeftRadius="integer"
        android:topRightRadius="integer" />
    <gradient
        android:angle="integer"
        android:centerColor="color"
        android:centerX="integer"
        android:centerY="integer"
        android:endColor="color"
        android:gradientRadius="integer"
        android:startColor="color"
        android:type="linear | radial | sweep"
        android:useLevel="true | false" />
    <padding
        android:bottom="integer"
        android:left="integer"
        android:right="integer"
        android:top="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>

需要注意的是标签创建的Drawable,其实体类实际上是GradientDrawable,下面分别介绍各个属性的含义。

  • android:shape 表示图形的形状,有四个选项:rectangle(矩形)、oval(椭圆)、line(横线)和ring(圆环)。它的默认值是矩形,另外line和ring这两个选项必须要通过标签来指定线的宽度和颜色等信息,否则将无法达到预期的显示效果。针对ring这个形状,有5个特殊的属性:android:innerRadius、android:thickness、android:innerRadiusRatio、android:thicknessRatio和android:useLevel,它们的含义如表6-2所示。
Value Description
android:innerRadius 圆环的内半径,和android:innerRadiusRatio同时存在时,以android:innerRadius为准
android:thickness 圆环的厚度,即外半径减去内半径的大小,和android:thicknessRatio同时存在时,以android:thickness为准
android:innerRadiusRatio 内半径占整个Drawable宽度的比例,默认值为9。如果为n,那么内半径 = 宽度 / n
android:thicknessRatio 厚度占整个Drawable宽度的比例,默认值为3。如果为n,那么厚度 = 宽度 / n
android:useLevel 一般都应该使用false,否则有可能无法达到预期的显示效果,除非它被当作LevelListDrawable来使用
  • <corners>表示shape的四个角的角度。它只适用于矩形shape,这里的角度是指圆角的程度,用px来表示,它有如下5个属性:
  1. android:radius——为四个角同时设定相同的角度,优先级较低,会被其他四个属性覆盖;
  2. android:topLeftRadius——设定最上角的角度;
  3. android:topRightRadius——设定右上角的角度;
  4. android:bottomLeftRadius——设定最下角的角度;
  5. android:bottomRightRadius——设定右下角的角度。
  • <gradient>它与<solid>标签是互相排斥的,其中solid表示纯色填充,而gradient则表示渐变效果,gradient有如下几个属性:
  1. android:angle——渐变的角度,默认为0,其值必须为45的倍数,0表示从左到右,90表示从下到上,具体的效果需要自行体验,总之角度会影响渐变的方向;
  2. · android:centerX——渐变的中心点的横坐标;
  3. android:centerY——渐变的中心点的纵坐标,渐变的中心点会影响渐变的具体效果;
  4. · android:startColor——渐变的起始色;
  5. · android:centerColor——渐变的中间色;
  6. · android:endColo——渐变的结束色;
  7. · android:gradientRadius——渐变半径,仅当android:type= "radial"时有效;
  8. · android:useLevel——一般为false,当Drawable作为StateListDrawable使用时为true;
  9. · android:type——渐变的类别,有linear(线性渐变)、radial(径向渐变)、sweep(扫描线渐变)三种,其中默认值为线性渐变。 下面是一个gradient的例子:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <gradient
        android:angle="0"
        android:centerColor="@color/colorAccent"
        android:centerX="50%p"
        android:centerY="50%p"
        android:endColor="@color/colorPrimaryDark"
        android:gradientRadius="0"
        android:startColor="@color/white"
        android:type="linear"
        android:useLevel="false" />

</shape>
  • <solid>这个标签表示纯色填充,通过android:color即可指定shape中填充的颜色。
  • <stroke> Shape的描边,有如下几个属性:
  1. android:width——描边的宽度,越大则shape的边缘线就会看起来越粗;
  2. · android:color——描边的颜色;
  3. · android:dashWidth——组成虚线的线段的宽度;
  4. · android:dashGap——组成虚线的线段之间的间隔,间隔越大则虚线看起来空隙就越大。注意如果android:dashWidth和android:dashGap有任何一个为0,那么虚线效果将不能生效。
  • <padding>这个表示空白,但是它表示的不是shape的空白,而是包含它的View的空白,有四个属性:android:left、android:top、android:right和android:bottom。
  • <size>shape的大小,有两个属性:android:width和android:height,分别表示shape的宽/高。这个表示的是shape的固有大小,但是一般来说它并不是shape最终显示的大小,这个有点抽象,但是我们要明白,对于shape来说它并没有宽/高的概念,作为View的背景它会自适应View的宽/高。我们知道Drawable的两个方法getIntrinsicWidth和getIntrinsicHeight表示的是Drawable的固有宽/高,对于有些Drawable比如图片来说,它的固有宽/高就是图片的尺寸。而对于shape来说,默认情况下它是没有固有宽/高这个概念的,这个时候getIntrinsicWidth和getIntrinsicHeight会返回-1,但是如果通过<size>标签来指定宽/高信息,那么这个时候shape就有了所谓的固有宽/高。因此,总结来说,<size>标签设置的宽/高就是ShapeDrawable的固有宽/高,但是作为View的背景时,shape还会被拉伸或者缩小为View的大小。

LayerDrawable

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

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/scrollIndicatorDown"
        android:drawable="@drawable/gugong"
        android:bottom="10px"
        android:left="10px"
        android:right="10px"
        android:top="10px">

    </item>
</layer-list>

一个layer-list中可以包含多个item,每个item表示一个Drawable。Item的结构也比较简单,比较常用的属性有android:top、android:bottom、android:left和android:right,它们分别表示Drawable相对于View的上下左右的偏移量,单位为像素。另外,我们可以通过android:drawable属性来直接引用一个已有的Drawable资源,也可以在item中自定义Drawable。默认情况下,layer-list中的所有的Drawable都会被缩放至View的大小,对于bitmap来说,需要使用android:gravity属性才能控制图片的显示效果。Layer-list有层次的概念,下面的item会覆盖上面的item,通过合理的分层,可以实现一些特殊的叠加效果。

下面是一个layer-list具体使用的例子,它实现了微信中的文本输入框的效果。当然它只适用于白色背景上的文本输入框,另外这种效果也可以采用.9图来实现。

<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>

StateListDrawable

StateListDrawable对应于<selector>标签,它也是表示Drawable集合,每个Drawable都对应着View的一种状态,这样系统就会根据View的状态来选择合适的Drawable。StateListDrawable主要用于设置可单击的View的背景,最常见的是Button,这个读者应该不陌生,它的语法如下所示。

<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="@drawable/gugong"
        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"/>
</selector>

针对上面的语法,下面做简单介绍。

  • android:constantSizeStateListDrawable的固有大小是否不随着其状态的改变而改变的,因为状态的改变会导致StateListDrawable切换到具体的Drawable,而不同的Drawable具有不同的固有大小。True表示StateListDrawable的固有大小保持不变,这时它的固有大小是内部所有Drawable的固有大小的最大值,false则会随着状态的改变而改变。此选项默认值为false。
  • android:dither是否开启抖动效果,这个在BitmapDrawable中也有提到,开启此选项可以让图片在低质量的屏幕上仍然获得较好的显示效果。此选项默认值为true。
  • android:variablePaddingStateListDrawable的padding表示是否随着其状态的改变而改变,true表示会随着状态的改变而改变,false表示StateListDrawable的padding是内部所有Drawable的padding的最大值。此选项默认值为false,并且不建议开启此选项。
  • <item>标签表示一个具体的Drawable,它的结构也比较简单,其中android:drawable是一个已有Drawable的资源id,剩下的属性表示的是View的各种状态,每个item表示的都是一种状态下的Drawable信息。View的常见状态如表6-3所示。
状态 含义
android:state_pressed 表示按下状态,比如Button被按下仍没有松开时的状态
android:state_focused 表示View已经获取了焦点
android:state_selected 表示用户选择了View
android:state_checked 表示用户选中了View,一般适用于CheckBox这类在选中和非选中状态之间进行切换的View
android:state_enabled 表示View当前处于可用状态

下面给出具体的例子,如下所示。

<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>

系统会根据View当前的状态从selector中选择对应的item,每个item对应着一个具体的Drawable,系统按照从上到下的顺序查找,直至查找到第一条匹配的item。一般来说,默认的item都应该放在selector的最后一条并且不附带任何的状态,这样当上面的item都无法匹配View的当前状态时,系统就会选择默认的item,因为默认的item不附带状态,所以它可以匹配View的任何状态。

LevelListDrawable

LevelListDrawable对应于<level-list>标签,它同样表示一个Drawable集合,集合中的每个Drawable都有一个等级(level)的概念。根据不同的等级,LevelListDrawable会切换为对应的Drawable,它的语法如下所示。

<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:drawable="@drawable/gugong"
        android:maxLevel="integer"
        android:minLevel="integer" />
</level-list>

上面的语法中,每个item表示一个Drawable,并且有对应的等级范围,由android:min-Level和android:maxLevel来指定,在最小值和最大值之间的等级会对应此item中的Drawable。下面是一个实际的例子,当它作为View的背景时,可以通过Drawable的setLevel方法来设置不同的等级从而切换具体的Drawable。如果它被用来作为ImageView的前景Drawable,那么还可以通过ImageView的setImageLevel方法来切换Drawable。最后,Drawable的等级是有范围的,即0~10000,最小等级是0,这也是默认值,最大等级是10000。

<level-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:drawable="@drawable/status_off"
        android:maxLevel="0" />
    <item
        android:drawable="@drawable/status_on"
        android:maxLevel="1" />
</level-list>

TransitionDrawable

TransitionDrawable对应于<transition>标签,它用于实现两个Drawable之间的淡入淡出效果,它的语法如下所示。

<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/gugong"
        android:id="@+id/scrollIndicatorDown"
        android:top="0dp"
        android:right="0dp"
        android:bottom="0dp"
        android:left="0dp"/>
</transition>

上面语法中的属性前面已经都介绍过了,其中android:top、android:bottom、android:left和android:right仍然表示的是Drawable四周的偏移量,这里就不多介绍了。下面给出一个实际的例子。首先定义TransitionDrawable,如下所示。

<transition xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/icon1" />
    <item android:drawable="@drawable/icon2" />
</transition>

接着将上面的TransitionDrawable设置为View的背景,如下所示。当然也可以在ImageView中直接作为Drawable来使用。


    <TextView
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/transition_drawable_1" />

最后,通过它的startTransition和reverseTransition方法来实现淡入淡出的效果以及它的逆过程,如下所示。

       TextView textView = findViewById(R.id.button);
        TransitionDrawable drawable = (TransitionDrawable) textView.getBackground();
        drawable.startTransition(1000);
//        drawable.reverseTransition(1000);

InsetDrawable

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

<inset xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/icon3"
    android:insetBottom="15dp"
    android:insetLeft="15dp"
    android:insetRight="15dp"
    android:insetTop="15dp">

</inset>

上面的属性都比较好理解,其中android:insetTop、android:insetBottom、android:insetLeft和android:insetRight分别表示顶部、底部、左边和右边内凹的大小。在下面的例子中,inset中的shape距离View的边界为15dp。

<inset xmlns:android="http://schemas.android.com/apk/res/android"
    android:insetLeft="15dp"
    android:insetTop="15dp"
    android:insetRight="15dp"
    android:insetBottom="15dp">

    <shape android:shape="rectangle">
        <solid android:color="#ff0000" />
    </shape>
</inset>

ScaleDrawable

ScaleDrawable对应于<scale>标签,它可以根据自己的等级(level)将指定的Drawable缩放到一定比例,它的语法如下所示。

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/icon1"
    android:scaleGravity="center"
    android:scaleWidth="percentage"
    android:scaleHeight="percentage">

</scale>

在上面的属性中,android:scaleGravity的含义等同于shape中的android:gravity,而android:scaleWidth和android:scaleHeight分别表示对指定Drawable宽和高的缩放比例,以百分比的形式表示,比如25%。

ScaleDrawable有点费解,要理解它,我们首先要明白等级对ScaleDrawable的影响。等级0表示ScaleDrawable不可见,这是默认值,要想ScaleDrawable可见,需要等级不能为0,这一点从源码中可以得出。看一下ScaleDrawable的draw方法即可知道。在下面的例子中,可以近似地将一张图片缩小为原大小的30%,代码如下所示。

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/icon1"
    android:scaleWidth="70%"
    android:scaleHeight="70%"
    android:scaleGravity="center">

</scale>

直接使用上面的drawable资源是不行的,还必须设置ScaleDrawable的等级为大于0且小于等于10000的值,如下所示。

        TextView textView = findViewById(R.id.button);
        ScaleDrawable drawable = (ScaleDrawable) textView.getBackground();
        drawable.setLevel(1);

经过上面的两步可以正确地缩放一个Drawable,如果少了设置等级这一步,由于Drawable的默认等级为0,那么ScaleDrawable将无法显示出来。我们可以武断地将Drawable的等级设置为大于10000的值,比如20000,虽然也能正常工作,但是不推荐这么做,这是因为系统内部约定Drawable等级的范围为0到10000。

ClipDrawable

ClipDrawable对应于<clip>标签,它可以根据自己当前的等级(level)来裁剪另一个Drawable,裁剪方向可以通过android:clipOrientation和android:gravity这两个属性来共同控制,它的语法如下所示。

<clip xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/icon1"
    android:clipOrientation="vertical"
    android:gravity="center">

</clip>

其中clipOrientation表示裁剪方向,有水平和竖直两个方向,gravity比较复杂,需要和clipOrientation一起才能发挥作用,如表6-4所示。

gravity属性
另外gravity的各种选项是可以通过“|”来组合使用的。下面举个例子,我们实现将一张图片从上往下进行裁剪的效果,首先定义ClipDrawable, xml如下:

<clip xmlns:android="http://schemas.android.com/apk/res/android"
    android:clipOrientation="vertical"
    android:drawable="@drawable/icon1"
    android:gravity="bottom">

</clip>

在上面的XML中,因为我们要实现顶部的裁剪效果,所以裁剪方向应该为竖直方向,同时从表6-4可以知道,gravity属性应该选择bottom。有了ClipDrawable如何使用呢?也是很简单的,首先将它设置给ImageView,当然也可以作为普通View的背景,如下所示。


    <ImageView
        android:id="@+id/button"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:src="@drawable/clip_drawable_1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

接着在代码中设置ClipDrawable的等级,如下所示。

        ImageView imageView = findViewById(R.id.button);
        ClipDrawable drawable = (ClipDrawable) imageView.getDrawable();
        drawable.setLevel(8000);

在前面已经提到,Drawable的等级(level)是有范围的,即0~10000,最小等级是0,最大等级是10000,对于ClipDrawable来说,等级0表示完全裁剪,即整个Drawable都不可见了,而等级10000表示不裁剪。在上面的代码中将等级设置为8000表示裁剪了2000,即在顶部裁剪掉20%的区域,被裁剪的区域就相当于不存在了。对于本例来说,等级越大,表示裁剪的区域越小,因此等级10000表示不裁剪,这个时候整个图片都可以完全显示出来;而等级0则表示裁剪全部区域,这个时候整个图片将不可见。另外裁剪效果还受裁剪方向和gravity属性的影响,表6-4中的选项读者可以自行尝试一下,这样就能比较好地理解不同属性对裁剪效果的影响了。

自定义Drawable

Drawable的使用范围很单一,一个是作为ImageView中的图像来显示,另外一个就是作为View的背景,大多数情况下Drawable都是以View的背景这种形式出现的。Drawable的工作原理很简单,其核心就是draw方法。在第5章中,我们分析了View的工作原理,我们知道系统会调用Drawable的draw方法来绘制View的背景,从这一点我们明白,可以通过重写Drawable的draw方法来自定义Drawable。

通常我们没有必要去自定义Drawable,这是因为自定义的Drawable无法在XML中使用,这就降低了自定义Drawable的使用范围。某些特殊情况下我们的确想自定义Drawable,这也是可以的。下面演示一个自定义Drawable的实现过程,我们通过自定义Drawable来绘制一个圆形的Drawable,并且它的半径会随着View的变化而变化,这种Drawable可以作为View的通用背景,代码如下所示。

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 r = getBounds();
        float cx = r.exactCenterX();
        float cy = r.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;
    }
}

在上面的代码中,draw、setAlpha、setColorFilter和getOpacity这几个方法都是必须要实现的,其中draw是最主要的方法,这个方法就和View的draw方法类似,而setAlpha、setColorFilter和getOpacity这三个方法的实现都比较简单,这里不再多说了。在上面的例子中,参考了ShapeDrawable和BitmapDrawable的源码,所以说,源码是一个很好的学习资料,有些技术细节我们不清楚,就可以查看源码中类似功能的实现以及相应的文档,这样就可以更有针对性地解决一些问题。

上面的例子比较简单,但是流程是完整的,读者可以根据自己的需要实现更复杂的自定义Drawable。另外getIntrinsicWidth和getIntrinsicHeight这两个方法需要注意一下,当自定义的Drawable有固有大小时最好重写这两个方法,因为它会影响到View的wrap_content布局,比如自定义Drawable是绘制一张图片,那么这个Drawable的内部大小就可以选用图片的大小。在上面的例子中,自定义的Drawable是由颜色填充的圆形并且没有固定的大小,因此没有重写这两个方法,这个时候它的内部大小为-1,即内部宽度和内部高度都为-1。需要注意的是,内部大小不等于Drawable的实际区域大小,Drawable的实际区域大小可以通过它的getBounds方法来得到,一般来说它和View的尺寸相同。