Drawable

main_content

1. Drawable 简介

Drawable 在 Android 开发中是非常常用的,比如在 XML 中定义color,shape,selector 等。通过 Drawable 的使用,能够帮助我们实现一些比较好的界面效果,同时 Drawable 又相对轻量级,不像自定义 View 那样复杂。使用 Drawable 代替一些图片时,可以有效减少 APK 包的大小,开发成本也行对比较低。

A Drawable is a general abstraction for “something that can be drawn.” Most often you will deal with Drawable as the type of resource retrieved for drawing things to the screen; the Drawable class provides a generic API for dealing with an underlying visual resource that may take a variety of forms. Unlike a View, a Drawable does not have any facility to receive events or otherwise interact with the user.

官方文档给出的描述:Drawable 是一种可绘制事务的抽象,通常情况下我们将 Drawable 作为一种资源类型,来会绘制到屏幕上。 Drawable类提供了用于处理底层可视资源的通用API。与视图不同,Drawable没有任何工具来接收事件或与用户进行任何交互。

实际上,Drawable 就是将我们定义的图形,颜色等绘制到屏幕上,但是不提供事件的处理,自然也就没有交互的功能。

Drawable的内部宽高可以分别通过getIntrinsicWidth()和getIntrinsicHeight()获取,但并不是所有的Drawable都有内部宽高的属性,比如一个颜色形成的Drawable并没有宽高的概念。在大多数情况下,Drawable并没有大小的概念,因为当Drawable作为View的背景图时,Drawable会被拉伸至View的同等大小。

2. Drawable 种类

category

看到 Drawable 的种类比较多,但常用的不多,主要有 ShapeDrawable,BitmapDrawable,LayerDrawable,StateListDrawable 等。

下面就一一介绍一下各个 Drawable 的特点和作用以及简单的用法。

3. 各种 Drawable

3.1 BitmapDrawable

一般情况下,我们可以直接使用原图片。在有些场景下需要做一些特殊的效果,所以可以使用 BitmapDrawable 来对图片做一些处理,相当于对图片做一下包装,如平铺填充、拉伸填充或者保持图片原始大小,也可以在 BitmapDrawable 区域内部使用gravity指定的对齐方式。


<?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:tileMode=["disabled" | "clamp" | "repeat" | "mirror"] />

下面看一下这些属性的含义:

  • android:src

类型:Drawable resource。必需。 引用一个drawable resource.

  • android:antialias

类型:Boolean。是否开启抗锯齿。开启后图片会变得更平滑些,因此一般建议开启,设置为true即可。

  • android:dither

类型:Boolean。是否允许抖动,如果位图与屏幕的像素配置不同时,开启这个选项可以让高质量的图片在低质量的屏幕上保持较好的显示效果(例如:一个位图的像素设置是 ARGB 8888,但屏幕的设置是RGB 565,开启这个选项可以是图片不过于失真)一般建议开启,为true即可。

  • android:filter

类型:Boolean。是否允许对位图进行滤波。当图片被压缩或者拉伸时,使用滤波可以获得平滑的外观效果。一般建议开启,为true即可。

  • android:gravity
当图片小于容器尺寸时,设置此选项可以对图片经典定位,这个属性比较多,不同选项可以使用‘ ’来组合使用。

gravity

  • android:mipMap

纹理映射处理技术,一般也不常用,默认为false

android:tileMode

平铺模式。共有以下几个值

disabled :默认值,表示不使用平铺

clamp :复制边缘色彩

repeat :X、Y 轴进行重复图片显示,也就是我们说要说的平铺

mirror :在水平和垂直方向上使用交替镜像的方式重复图片的绘制

下面看下效果:

tilemode

使用方法:

(1) 直接使用 XML 文件定义 BitmapDrawable,这也是最常用的方法;

<?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:src="@drawable/pic"
android:tileMode="clamp">

</bitmap>

(2) 使用代码方式生成 BitmapDrawable,稍微麻烦点,一般不常用。 实际上我们从BitmapDrawable的源码可以看出,目前Google建议我们创建BitmapDrawable的构造方法有3种

public BitmapDrawable(Resources res, Bitmap bitmap)

public BitmapDrawable(Resources res, String filepath)

public BitmapDrawable(Resources res, java.io.InputStream is)


Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image1);
BitmapDrawable mBitmapDrawable = new BitmapDrawable(getResources()mBitmap);
mBitmapDrawable.setTileModeXY(TileMode.MIRROR, TileMode.MIRROR);//平铺方式
mBitmapDrawable.setAntiAlias(true);//抗锯齿
mBitmapDrawable.setDither(true);//防抖动
//设置到imageView上即可
imageView.setImageDrawable(mBitmapDrawable);

** 注意 **

在使用 android:tileMode 过程中,有另外一个属性和这个表面看上去类似 android:tintMode, 这个是颜色渲染的模式设置,所有的 view 和 Drawable 都有这个选项。它的作用实际上就是用来决定渲染之后显示的效果的,一般只有多个图层时才起作用,如 ImageView 中的 src 和 background 都设置了图片,这时就会有显示的先后和叠加等问题,可以设置这个选项。可使用的选项共有 6 种(add、multiply、screen、src_over、src_in、src_atop,)具体的选项有,可以参看下面来进行设置。

tintmode

1.PorterDuff.Mode.CLEAR
所绘制不会提交到画布上

2.PorterDuff.Mode.SRC
显示上层绘制图片

3.PorterDuff.Mode.DST
显示下层绘制图片

4.PorterDuff.Mode.SRC_OVER
正常绘制显示,上下层绘制叠盖

5.PorterDuff.Mode.DST_OVER
上下层都显示。下层居上显示。

6.PorterDuff.Mode.SRC_IN
取两层绘制交集。显示上层。

7.PorterDuff.Mode.DST_IN
取两层绘制交集。显示下层。

8.PorterDuff.Mode.SRC_OUT
取上层绘制非交集部分。

9.PorterDuff.Mode.DST_OUT
取下层绘制非交集部分。

10.PorterDuff.Mode.SRC_ATOP
取下层非交集部分与上层交集部分

11.PorterDuff.Mode.DST_ATOP
取上层非交集部分与下层交集部分

12.PorterDuff.Mode.XOR
异或:去除两图层交集部分

13.PorterDuff.Mode.DARKEN
取两图层全部区域,交集部分颜色加深

14.PorterDuff.Mode.LIGHTEN
取两图层全部,点亮交集部分颜色

15.PorterDuff.Mode.MULTIPLY
取两图层交集部分叠加后颜色

16.PorterDuff.Mode.SCREEN
取两图层全部区域,交集部分变为透明色

3.2 NinePatchDrawable

NinePatchDrawable 表示的是我们熟悉的.9格式的图片,.9图片可以在保证图片不失真的情况下任意进行缩放,在实际的使用中我们也是通过Xml来实现即可:

<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"   
android:src="drawable/resource"  
android:dither="[true|false]"/> 

  属性和 BitmapDrawable 中属性的含义相同,这里不过多描述。一般情况下不建议代码创建.9图,因为 Android 虽然可以使用 Java 代码创建 NinePatchDrawable,但是极少情况会那么做,这是因为由于Android SDK会在编译工程时对点九图片进行编译,形成特殊格式的图片。使用代码创建 NinePatchDrawable 时只能针对没有编译过的点九图片资源,对于没有编译过的点九图片资源都当做 BitmapDrawable 对待。还有点需要特别注意的是,点九图只能适用于拉伸的情况,对于压缩的情况并不适用,如果需要适配很多分辨率的屏幕时需要把点九图做的小一点。      .9 图使用时规则如下:         .9图片的四条黑边的意义,每条黑边的意义都不一样      顶部:在水平拉伸的时候,保持其他位置不动,只在这个点的区域做无限的延伸   左边:在竖直拉伸的时候,保持其他位置不动,只在这个点的区域做无限的延伸   底部:在水平拉伸的时候,指定图片里的内容显示的区域   右边:在竖直拉伸的时候,指定图片里的内容显示的区域            #### 3.3 ShapeDrawable      ShapeDrawable 对于 XML 的shape标签,在实际开发中我们经常将其作为背景图片使用,因为 ShapeDrawable 可以帮助我们通过颜色来构造图片,也可以构造渐变效果的图片,总之,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:topLeftRadius="integer"   android:topRightRadius="integer"   android:bottomLeftRadius="integer"   android:bottomRightRadius="integer" />   <gradient   android:angle="integer"   android:centerX="integer"   android:centerY="integer"   android:centerColor="integer"   android:endColor="color"   android:gradientRadius="integer"   android:startColor="color"   android:type=["linear" | "radial" | "sweep"]   android:usesLevel=["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>         从代码中我们可以看出Shape的子元素包括 gradient、padding、size、solid、stroke.      android:shape      这个属性表示图像的形状,可以是rectangle(矩形)、oval(椭圆)、line(横线)、ring(圆环)。默认为rectangle。   这里对于ring值还有几个相关的属性:      ting   corners      指定边角的半径,数值越大角越圆,数值越小越趋近于直角,参数为:         <corners   android:radius="integer"   android:topLeftRadius="integer"   android:topRightRadius="integer"   android:bottomLeftRadius="integer"   android:bottomRightRadius="integer" />            gradient      设置颜色渐变与 为互斥标签,因为solid表示纯色填充,而gradient表示渐变填充。      ![gradient1](https://github.com/RalfNick/PicRepository/raw/master/drawable/drawable-%E6%B8%90%E5%8F%98.png)      angle=0和angle=90的区别(都为线性渐变):      ![gradient2](https://github.com/RalfNick/PicRepository/raw/master/drawable/drawable-%E6%B8%90%E5%8F%98%E6%95%88%E6%9E%9C1.png)         linear(线性)为默认值,radial(径内渐变),sweep(扫描渐变)区别如下:      ![gradient3](https://github.com/RalfNick/PicRepository/raw/master/drawable/drawable-%E6%B8%90%E5%8F%98%E6%95%88%E6%9E%9C2.png)      下面画一个渐变的圆环,看一下效果:      ```   <?xml version="1.0" encoding="utf-8"?>   <shape xmlns:android="http://schemas.android.com/apk/res/android"   android:innerRadius="100dp"   android:shape="ring"   android:thickness="40dp"   android:useLevel="false">      <gradient   android:angle="0"   android:centerColor="@color/colorPrimaryDark"   android:endColor="@android:color/white"   android:startColor="@color/colorAccent"   android:type="sweep"   android:useLevel="false" />      </shape>      ```   ![gradient](https://github.com/RalfNick/PicRepository/raw/master/drawable/Drawable-ring2.png)   solid      表示纯色填充,通过android:color设置颜色即可。      stroke      描述边框,属性如下:   ![stroke](https://github.com/RalfNick/PicRepository/raw/master/drawable/Drawable-stroke.png)   有点要明白的是 android:dashWidth 和 android:dashGap 有任意一个为0,则虚线无法预期显示。      同样也来一个纯色的圆环看看效果:      方式一:   ```   <?xml version="1.0" encoding="utf-8"?>   <shape xmlns:android="http://schemas.android.com/apk/res/android"   android:shape="ring"   android:useLevel="false"   android:innerRadius="50dp"   android:thickness="10dp">      <solid android:color="@color/colorPrimaryDark"      </shape>      ```   方式二:      ```   <?xml version="1.0" encoding="utf-8"?>   <shape xmlns:android="http://schemas.android.com/apk/res/android"   android:shape="oval"   android:useLevel="false">      <stroke   android:width="10dp"   android:color="@color/colorPrimaryDark" />      </shape>   ```      虚线圆环:      ```   <?xml version="1.0" encoding="utf-8"?>   <shape xmlns:android="http://schemas.android.com/apk/res/android"   android:shape="oval"   android:useLevel="false">      <stroke   android:width="5dp"   android:color="@color/colorPrimaryDark"   android:dashGap="3dp"   android:dashWidth="3dp" />      </shape>   ```   效果就不展示了。可以自己尝试下。      padding      表示内容或子标签边距,4个属性top、bottom、left、right,需要注意的是这个标签的作用是为内容设置与当前应用此shape的View的边距,而不是设置当前View与父元素的边距。      size      shape 通过这个标签可以设置 shape 的大小:      ```   <size android:width="10dp"   android:height="10dp"/>   ```   注意,给 shape 设置大小,实际中意义并不是很大,为什么这么说呢,因为shape 常用作于 view 的背景,所以 shape 的最终大小是 view 的大小,也就是说 shape的大小是自适应 view 的大小的。 Drawable 有两个方法,getIntrinsicWidth 和 getIntrinsicHeight,通过 这个两个方法能够获取 Drawable 固有的宽和高,对于有图片的 Drawable ,返回的宽高就是 图片的宽和高,对于 shape 来说,默认返回的是 - 1 ,如果我们设置了 size ,就会返回我们设置的值。这样就能够理解了。那么什么时候会用到 这两个方法呢?主要用在哪? 看过 View 的测量方法的源码应该了解到,其中当测量方式为 wrap_content 时,就是通过这两个方法获取背景默认的宽和高。      #### 3.4 LayerDrawable      LayerDrawable xml的标签是,它是将不同的 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 标识一个 Drawable ,主要有 上下左右 几个属性,设置 每个 Drawable 相对于 View 边缘的偏移量,单位为像素。android:drawable 属性可以直接引用已有的一个 Drawable资源,也可以在 item 中自己定义一个 shape,默认情况下, item 的大小是 View 的大小,对于 bitmap 来说,需要使用 android:gravity 才能控制图片的显示效果,分层的结果由于顺序决定,上面的图形会覆盖下面的图形,上下顺序是有绘制的顺序决定的,如,哪个 shape 先画出来,哪个shape 就在下面。下面举一个例子,就能明白了。      ```   <?xml version="1.0" encoding="utf-8"?>                           <item   android:bottom="20dp"   android:end="20dp"   android:start="20dp"   android:top="20dp">            </item>      <item   android:bottom="40dp"   android:end="40dp"   android:start="40dp"   android:top="40dp"   android:gravity="center">            </item>      <item   android:bottom="60dp"   android:end="60dp"   android:start="60dp"   android:top="60dp">            </item>         ```      ![layer](https://github.com/RalfNick/PicRepository/raw/master/drawable/drawable_ring_ring.png)   #### 3.5 StateListDrawable      StateListDrawable 对于 xml 的 selector 标签,这个标签可以说是我们最常用的标签。它也是 Drawable 的集合,每个 Drawable 对应一个 View 的状态,它会根据 View 的状态来选择合适的 Drawable ,从而实现不同的背景效果。如,一个 Button ,设置 StateListDrawable 时,根据点击按下时和松开时,选择合适的 Drawable ,就可以达到给用户点击的感觉。其主要语法如下:      ```   <?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"] />   </selector>   ```   ![state](https://github.com/RalfNick/PicRepository/raw/master/drawable/drawable_state.png)   selector标签的属性含义如下:      android:constantSize        StateListDrawable的固有大小是否随着其状态改变而改变,因为在状态改变后,StateListDrawable会切换到别的Drawable,而不同的Drawable其大小可能不一样。true表示大小不变,这时其固有大小是内容所有Drawable的固有大小的最大值。false则会随着状态改变而改变,默认值为false          android:variablePadding          表示 StateListDrawable的padding是否随状态的改变而改变,默认值为false,一般建议设置为false就行。          android:dither            是否开启抖动效果,开启后可使高质量的图片在低质量的屏幕上仍然有较好的显示效果,一般建议开启,设置为true。                测试例子                ```        <?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="rectangle">                                        </shape>        ```                ```        <?xml version="1.0" encoding="utf-8"?>        <shape xmlns:android="http://schemas.android.com/apk/res/android"        android:shape="rectangle">                                        </shape>                ```                效果:                ![state_gif](https://github.com/RalfNick/PicRepository/raw/master/drawable/state_gif.gif)                #### 3.6 LevelListDrawable                LevelListDrawable对应于标签,也表示一个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="@drawable/drawable_resource"        android:maxLevel="integer"        android:minLevel="integer" />                ```                属性值的说明如下:                ![level](https://github.com/RalfNick/PicRepository/raw/master/drawable/drawable_level.png)                每个 item 表示一个 Drawable,并且有对应的等级范围,通过设定 android:maxLevel 和 android:minLevel 来指定,当指定的值在这个范围内就会对应相应的 Drawable。当然这个两个值也是有范围的,为 0-10000。                应用场景,通过设定不同的 level 来切换 Drawable,实现不同的背景,下面用 ImageView 来测试下效果:                ```        <?xml version="1.0" encoding="utf-8"?>                        <item android:drawable="@drawable/pic"        android:maxLevel="0"/>                <item android:drawable="@drawable/ic_launcher_background"        android:maxLevel="1"/>                        ```                在代码中设置切换的动作:                ```java        final ImageView imageView = findViewById(R.id.imageView2);        imageView.setImageResource(R.drawable.level_list_test);        imageView.setImageLevel(0);        imageView.setOnClickListener(new View.OnClickListener() {        @Override        public void onClick(View view) {        imageView.getDrawable().setLevel(i++ % 2);        }        });                ```        效果就不展示了。自己试试看。                #### 3.7 TransitionDrawable                TransitionDrawable 用于实现两个 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" />                ```        其中 android:top,android:bottom,android:left,android:right分别表示Drawable四周的偏移量。                android:id                资源ID, drawable 资源的唯一标识。使用”@+id/name”方式来给这个item定义一个新的资源ID。可以使用View.findViewById()或者 Activity.findViewById()等方式检索和修改这个item。                下面看一个例子:                ```        <?xml version="1.0" encoding="utf-8"?>                        <item        android:drawable="@drawable/pic1"        android:top="10dp" />                <item        android:drawable="@drawable/pic2"        android:top="10dp" />                                ```                将 TransitionDrawable 作为背景        ```        android:background="@drawable/transition_test"        ```                TransitionDrawable 的第一个参数是渐变开始时的图像,第二个参数是最终要显示的图像。                在代码中获取 TransitionDrawable,并设置持续时间,开始执行淡入淡出的动作                ```java                TextView textView = findViewById(R.id.text_view);        TransitionDrawable drawable = (TransitionDrawable) textView.getBackground();        drawable.startTransition(2000);                ```                #### 3.8 InsetDrawable                InsetDrawable 对应 标签,它可以将其他Drawable内嵌到自己当中,并可以在四周预留出一定的间距。当我们希望 View 的背景比实际区域小时,就可以采用 InsetDrawable 来实现,个人认为这个效果并没有什么特殊之处,因为 layerDrawable 也是可以达到相同的预期效果的。其语法如下:                ```        <?xml version="1.0" encoding="utf-8"?>        <inset        xmlns:android="http://schemas.android.com/apk/res/android"        android:drawable="@drawable/drawable_resource"        android:insetTop="dimension"        android:insetRight="dimension"        android:insetBottom="dimension"        android:insetLeft="dimension" />                ```                这个比较简单,就给出示例布局                ```        <?xml version="1.0" encoding="utf-8"?>        <inset xmlns:android="http://schemas.android.com/apk/res/android"        android:drawable="@drawable/rectangle_r6_pressed"        android:insetTop="20dp"        android:insetBottom="10dp"        android:insetLeft="15dp"        android:insetRight="5dp">                                ```                #### 3.9 ScaleDrawable                ScaleDrawable对应标签,主要基于当前的level,对指定的Drawable进行缩放操作。                ```        <?xml version="1.0" encoding="utf-8"?>        <scale        xmlns:android="http://schemas.android.com/apk/res/android"        android:drawable="@drawable/drawable_resource"        android:scaleGravity=["top" | "bottom" | "left" | "right" | "center_vertical" |        "fill_vertical" | "center_horizontal" | "fill_horizontal" |        "center" | "fill" | "clip_vertical" | "clip_horizontal"]        android:scaleHeight="percentage"        android:scaleWidth="percentage" />                ```                >* android:gravity                  当图片小于容器尺寸时,设置此选项可以对图片经典定位,这个属性比较多,不同选项可以使用‘|’来组合使用。                        ![ScaleDrawable_gravity](https://github.com/RalfNick/PicRepository/raw/master/drawable/drawable_scale.png)                        >* android:scaleHeight                        表示Drawable的高的缩放比例,值越大,内部Drawable的高度显示得越小,例如android:scaleHeight=”70%”,那么显示时Drawable的高度只有原来的30%。                        >* android:scaleWidth                        表示Drawable的宽的缩放比例,值越大,内部Drawable的宽显示得越小,例如android:scaleWidth=”70%”,那么显示时Drawable的宽度只有原来的30%。                        ```            <?xml version="1.0" encoding="utf-8"?>            <scale xmlns:android="http://schemas.android.com/apk/res/android"            android:drawable="@drawable/pic2"            android:scaleGravity="center"            android:scaleHeight="50%"            android:scaleWidth="50%"            android:level="50">                                                ```                        还需要设置level,否则不起作用                        ```java                        ImageView scaleImage= (ImageView) findViewById(R.id.scaleImage);            ScaleDrawable scale= (ScaleDrawable) scaleImage.getBackground();            scale.setLevel(10);                        ```            此外在 API 高于或等于 24 时,可以在 XML 中直接设置 level。                        需要特别注意的是我们如果定义好了 ScaleDrawable,要将其显示出来的话,必须给 ScaleDrawable 设置一个大于 0 小于 10000 的等级(级别越大 Drawable 显示得越大,等级为 10000 时就没有缩放效果了),否则将无法正常显示。我们可以看看其draw函数的源码:                        ```java            @Override            public void draw(Canvas canvas) {            final Drawable d = getDrawable();            if (d != null && d.getLevel() != 0) {            d.draw(canvas);            }            }            ```            也相对比较简单,效果可以自试一下,这里就不展示了。                        #### 3.10 ClipDrawable                        ClipDrawable是通过设置一个Drawable的当前显示比例来裁剪出另一张Drawable,我们可以通过调节这个比例来控制裁剪的宽高,以及裁剪内容占整个View的权重,通过ClipDrawable的setLevel()方法控制显示比例,ClipDrawable的level值范围在[0,10000],level的值越大裁剪的内容越少,当level为10000时则完全显示,而0表示完全裁剪,不可见。需要注意的是在给clip元素中android:drawable属性设置背景图片时,图片不能是9图,因为这涉及到裁剪这张图片,如果设置为九图,裁剪的实际情况会与想要的效果不一样。                        ClipDrawable对应xml的标签,其语法如下:                        ```            <?xml version="1.0" encoding="utf-8"?>            <clip            xmlns:android="http://schemas.android.com/apk/res/android"            android:drawable="@drawable/drawable_resource"            android:clipOrientation=["horizontal" | "vertical"]            android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" |            "fill_vertical" | "center_horizontal" | "fill_horizontal" |            "center" | "fill" | "clip_vertical" | "clip_horizontal"] />                        ```                        其中android:clipOrientation和android:gravity属性共同控制Drawable被裁剪的方向,其中clipOrientation表示裁剪的方向(水平和垂直两种),gravity比较复杂必须和clipOrientation一起才能起作用,同样的我们可以通过“|”来组合使用gravity的属性值。gravity属性值说明如下:                        ![ClipDrawable](https://github.com/RalfNick/PicRepository/raw/master/drawable/drawable_clipdrawable.png)                        举个例子                        ```            <?xml version="1.0" encoding="utf-8"?>            <clip xmlns:android="http://schemas.android.com/apk/res/android"            android:clipOrientation="horizontal"            android:drawable="@drawable/pic2"            android:gravity="center">                                    ```            然后需要在代码中设置 level,通过设置 level 来控制裁剪的比例。                        ```java                        ImageView clipImageView = findViewById(R.id.imageView5);            ClipDrawable clipDrawable = (ClipDrawable) clipImageView.getDrawable();            clipDrawable.setLevel(5000);                        ```                        level 的最大值是 10000,设置为 5000 表示裁剪一半,如果设置为 4000,表示裁剪 60%,保留原来的 40%,裁剪方向为水平裁剪,左右同时裁剪。                                    #### 3.11 ColorDrawable                        ColorDrawable 是最简单的Drawable,它实际上是代表了单色可绘制区域,它包装了一种固定的颜色,当ColorDrawable被绘制到画布的时候会使用颜色填充Paint,在画布上绘制一块单色的区域。 在xml文件中对应标签,它只有一个android:color属性,通过它来决定ColorDrawable的颜色。            xml实现如下:                        ```            <?xml version="1.0" encoding="utf-8"?>            <color xmlns:android="http://schemas.android.com/apk/res/android"            android:color="@color/colorPrimaryDark">                                    ```                        也可以使用代码实现,注意传入的颜色值为16进制的数字:                        ```java            ColorDrawable cd = new ColorDrawable(0xff000000);            ImageView iv = (ImageView)findViewById(...);            iv.setImageDrawable(cd);            ```                        ### 自定义 Drawable                        日常开发中使用自定义 Drawable 的场景不多,多数都是使用 XML 中定义一个 Drawable,已经能够满足多数需求,另外,自定义的 Drawable 不能在 XML 中时候用,这就有了很大的限制。                        在一些特殊场景可能需要用到自定义 Drawable,自定义 Drawable 其实主要重写 draw 方法:                        下面看一个例子                        ```java            public class MyDrawable extends Drawable {                        private Paint mPaint;            private int mPaintColor;                        public MyDrawable(int paintColor) {            mPaintColor = paintColor;            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);            mPaint.setColor(mPaintColor);            }                        @Override            public void draw(@NonNull Canvas canvas) {            Rect r = getBounds();            float centerX = r.exactCenterX();            float centerY = r.exactCenterY();            canvas.drawCircle(centerX,centerY,Math.min(centerX,centerY),mPaint);            }                        @Override            public void setAlpha(int i) {            mPaint.setAlpha(i);            invalidateSelf();            }                        @Override            public void setColorFilter(@Nullable ColorFilter colorFilter) {            mPaint.setColorFilter(colorFilter);            invalidateSelf();            }                        @Override            public int getOpacity() {            return PixelFormat.TRANSLUCENT;            }                        @Override            public int getIntrinsicWidth()            {            return 100;            }                        @Override            public int getIntrinsicHeight()            {            return 100;            }                        }                        ```            主要方法就是 draw 方法,比较简单,就只设置颜色,然后使用画笔画一个圆形,其中 setAlpha、setColorFilter、getOpacity、这几个方法也是必须实现的。            绘制的 Drawable 的大小是随着 View 大小改变的,但是有一点需要注意,就是在 View 的 布局设置为 [wrap_content] 这时如果内部是图片,那么默认的大小就是图片的宽和高,如果 Drawable 的形状是由颜色填充的,那么它的默认 会返回 -1,就像我们的这个例子,此时需要设置一个默认的宽和高,通过重写 getIntrinsicWidth 和 getIntrinsicHeight 即可。如果 View 设置了大小或者 [match_parent],则不会有问题。                        使用时创建 自定义的 Drawable,然后设置到 View 就行了。                        ```java            ImageView imageView6 = findViewById(R.id.imageView6);            MyDrawable myDrawable = new MyDrawable(0xff255779);                        imageView6.setImageDrawable(myDrawable);            ```                        下面换一个样式自定义 Drawable。有些场景,需要使用圆角图或者圆形图,你可能想到使用 Glide 或者 Fresco 加载到 ImageView 中,这完全可以,那还没有其他的思路呢?            我们尝试使用自定义 Drawable 的方式来试试,据说还很高效。主要也是通过重写 draw 方法,使用通过给 Paint 设置 BitmapShader,然后绘制出图片来,绘制前设置矩形圆角。            这里不再介绍 BitmapShader,可以看看鸿洋大神的讲解,[Android BitmapShader 实战 实现圆形、圆角图片](https://blog.csdn.net/lmj623565791/article/details/41967509)                        ```java            public class RoundImageDrawable extends Drawable {                        private Paint mPaint;            private Bitmap mBitmap;                        private RectF rectF;                        public RoundImageDrawable(Bitmap bitmap) {            mBitmap = bitmap;            BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,            Shader.TileMode.CLAMP);            mPaint = new Paint();            mPaint.setAntiAlias(true);            mPaint.setShader(bitmapShader);            }                        @Override            public void setBounds(int left, int top, int right, int bottom) {            super.setBounds(left, top, right, bottom);            rectF = new RectF(left, top, right, bottom);            }                        @Override            public void draw(@NonNull Canvas canvas) {            canvas.drawRoundRect(rectF, 30, 30, mPaint);            }                        @Override            public int getIntrinsicWidth() {            return mBitmap.getWidth();            }                        @Override            public int getIntrinsicHeight() {            return mBitmap.getHeight();            }                        @Override            public void setAlpha(int alpha) {            mPaint.setAlpha(alpha);            }                        @Override            public void setColorFilter(ColorFilter cf) {            mPaint.setColorFilter(cf);            }                        @Override            public int getOpacity() {            return PixelFormat.TRANSLUCENT;            }                        }            ```                        使用圆角 Drawable:                        ```java            ImageView imageView = findViewById(R.id.round_imageView);                        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.pic3);            RoundImageDrawable roundImageDrawable = new RoundImageDrawable(bitmap);            imageView.setImageDrawable(roundImageDrawable);            ```                        对于圆形也是一样的,通过 drawCircle 实现,不再给出代码了。可以自己试试。                        好了,到这里对 Drawable 已经有了一个全面的介绍,其实开发中常用的不会这么多,记住常用的几种就可以了,其他的如果遇到可以查一查。主要还是灵活使用, 通过使用 Drawable 可以代替一些自定义 view,降低开发成本,所以拿到一些需求,看看效果能否用 Drawable 实现,能的话就不用自定义 View;有时 UI 的小伙伴可能没有给出图片,那么我们可以通过使用一些 shape 来完成,让 UI 的妹子刮目相看一下,哈哈!Over ~~                        [练习的代码](https://github.com/RalfNick/AndroidPractice/tree/master/Drawable)                        ### 参考                        [领略千变万化的Android Drawable](https://blog.csdn.net/javazejian/article/details/52247324)                        [Android Drawable 那些不为人知的高效用法](https://blog.csdn.net/lmj623565791/article/details/43752383)                        [android开发艺术探索]                        [google android官网]            

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦