开发艺术之Drawable

作者: 请叫我林锋 | 来源:发表于2020-01-26 22:06 被阅读0次

一、Drawable 简介

Drawable 是一个抽象类,它是所有 Drawable 对象的基类,每个具体的 Drawable 都是它的子类,比如 ShapeDrawable、BitmapDrawable 等,Drawable 层次关系如图:

Drawable 层次关系.png

如何使用 Drawable:

  • 一般都是通过 XML 来定义
  • 也可以在代码中创建

关于 Drawable 的内部宽/高:

  • 通过 getIntrinsicWidthgetIntrinsicHeight 获取
  • 不是所有的 Drawable 都有内部宽/高,比如一个颜色所形成的 Drawable
  • Drawable 的内部宽/高不等于它的大小,一般来说 Drawable 是没有大小概念的。当用作 View 的背景时,Drawable 会被拉伸至 View 的同等大小

二、Drawable 的分类

1、BitmapDrawable

这是最简单的 Drawable,它表示一张图片。我们可以在代码中引用原始的图片,也可以通过 XML 的方式来描述它,通过 XML 来描述的 BitmapDrawable 可以设置更多的效果:

bitmap
    |- android:src="@drawable/res_id"
    |- 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 | repeat | mirror | clamp]"
  • android:src

    图片的资源 id

  • android:antialias

    是否开启图片抗锯齿功能。开启后图片会变得更加平滑,同时会在一定程度上降低图片的清晰度,但是这个降低的幅度较低可以忽略,应此需要开启

  • android:dither

    是否开启抖动效果。让高质量的图片在低质量的屏幕上还能保持较好的显示效果,应此需要开启

  • android:filter

    是否开启过滤效果。当图片尺寸被拉伸或者压缩时,开启过滤效果可以保持较好的显示效果,应此需要开启

  • android:gravity

    当图片小雨容器的尺寸时,设置此选项可以对图片进行定位。不同选项可以通过 "|"来组合使用

  • android:tileMode

    平铺模式。disable 为关闭平铺模式,也是默认值;repeat 表示水平和竖直方向上的平铺效果;mirror 表示水平和竖直方向上的镜面投影效果;clamp 表示图片四周的像素会扩散到周围区域

使用方法:

  • xml 定义:
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:antialias="true"
    android:dither="true"
    android:filter="true"
    android:src="@drawable/haha" />
  • 代码中定义
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.haha);
        BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bitmap);
        bitmapDrawable.setAntiAlias(true);
        bitmapDrawable.setDither(true);
        ivShow.setImageDrawable(bitmapDrawable);
2、NinePatchDrawable

它表示一张 .9 格式的图片,该格式可以自动根据所需的宽/高进行相应的缩放并且保证不失真

<nine-patch
    |- src="@drawable/9_png_resid"
    |- dither="[true | false]" />

它在 XML 中属性的含义与 BitmapDrawable 中对应属性的含义相同。另外,在 bitmap 标签中也可以使用 .9 图片,即 BitmapDrawable 也可以代表一个 .9 格式图片

3、ShapeDrawable

ShapeDrawable 也是一种很常见的 Drawable,可以理解为通过颜色来构造的图形,语法如下所示:

<?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" />
</shape>
  • android:shape

    表示图形的形状,有四个选项:rectangle(矩形)、oval(椭圆)、line(横线)、ring(圆环),它的默认值是矩形。

    另外 line 和 ring 这两个选项必须要通过 <stroke> 标签来指定线的宽度和颜色。

    最后,针对 ring 这个形状,有 5 个特殊的属性:


    ring 的属性值.png
  • < corners >

    表示 shape 的四个角的角度,它只适用于矩形 shape,用 px 表示,它有如下五个属性:

    • android:radius:为四个角同事设定相同的角度。优先级比以下4个属性要
    • android:topLeftRadius:左上角的角度。
    • android:topRightRadius:右上角的角度。
    • android:bottomLeftRadius:左下角的角度。
    • android:bottomRightRadius:右下角的角度。
  • < gradient >

    它与 <solid> 标签互斥,其中 solid 表示纯色填充,gradient 表示渐变效果,gradient 有如下几个属性:

    • android:angle:渐变的角度。默认为0。值必须为45的倍数。0表示从左到右,90表示从下到上。
    • android:centerX:渐变的中心点的X坐标。
    • android:centerY:渐变的中心点的Y坐标。
    • android:startColor:渐变的起始色。
    • android:centerColor:渐变的中间色。
    • android:endColor:渐变的结束色。
    • android:gradientRadius:渐变半径。仅当android:type="radial"时有效
    • android:useLevel:一般为false,当 Drawable 作 StateListDrawable 时为true
    • android:type:渐变的类别,可选值:linear(线性渐变)、radial(径向渐变)、sweep(扫描式线渐变)
  • < solid >

    纯色填充,通过 android:color 即可指定 shape 中填充的颜色

  • < stroke >

    Shape 的描边,有如下几个属性:

    • android:width:描边的宽度,越大则 shape 的边缘线久看会看起越粗
    • android:color:描边的颜色
    • android:dashWidth:组成虚线的线段的宽度
    • android:dashGap:组成虚线之间的线段之间的间隔,越大则虚线看起来空隙越大

    如果 android:dashWidthandroid:dashGap 有任何一个为 0,则虚线效果将不能生效。

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
    
        <solid android:color="#ff0000" />
        
        <stroke
            android:width="3dp"
            android:color="#00ff00"
            android:dashWidth="2dp"
            android:dashGap="2dp" />
    
    </shape>
    
  • < padding >

    与四周空白的距离

  • < size >

    设置图形的固有大小,非最终大小

3、LayerDrawable

LayerDrawable 对应的 XML 标签是 < 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=""
        android:id=""
        android:bottom=""
        android:left=""
        android:right=""
        android:top="" />
</layer-list>

一个 layer-list 可以包含多个 item,每个 item 表示一个 Drawable。常用属性如下所示:

  • android:bottom、android:left、android:right、android:top。分别代表表示 Drawable 相对于 View 的上下左右的偏移量
  • 可以通过 android:drawable 来直接引用一个已有的 Drawable 资源

实现 bitmap 的简单叠加:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_launcher_foreground" />
    <item
        android:bottom="50dp"
        android:drawable="@drawable/ic_launcher_foreground"
        android:left="50dp" />
    <item
        android:bottom="100dp"
        android:drawable="@drawable/ic_launcher_foreground"
        android:left="100dp" />
    <item
        android:bottom="150dp"
        android:drawable="@drawable/ic_launcher_foreground"
        android:left="150dp" />
</layer-list>

效果图:


layer-list 的应用.jpg
5、StateListDrawable

stateListDrawable 对应于 < slector >标签,它也表示 Drawable 集合,每个 Drawable 都对应着 View 的一种状态,它的语法如下所示:

selector
    |-constantSize="[true | false]"
    |-dither="[true | false]"
    |-variablePadding="[true | false]"
    |- item
    |    |- drawable="@drawable/drawable_id"
    |    |- state_pressed="[true | false]"
    |    |- state_focused="[true | false]"
    |    |- state_selected="[true | false]"
    |    |- state_hovered="[true | false]"
    |    |- state_checked="[true | false]"
    |    |- state_checkable="[true | false]"
    |    |- state_enabled="[true | false]"
    |    |- state_activated="[true | false]"
    |    |- state_window_focused="[true | false]"
    |
  • android:constanSize

    StateListDrawable 的固有大小是否不随着其状态的改变和改变,因为不同状态的 Drawable 的固有大小不同。True 表示 StateListDrawable 的固有大小保持不变,此时它的固有大小是内部所有 Drawable 的固有大小的最大值,false 则会随着状态的改变而改变。此默认值为 false

  • android:dither

    是否开启抖动效果。此选项默认为 ture

  • android:variablePadding

    表示 StateListDrawable 的 padding 是否随着其状态的改变而改变。true 表示会随着状态的改变而改变,false 表示 padding 是内部所有 Drawable 的 padding 的最大值。此选项默认值为 false,并且不建议开启此选项。

< item >标签表示一个具体的 Drawable,其中 android:drawable 是一个已有 Drawable 的资源id,剩下的属性表示 View 的各状态,常见状态如下:

View 的常见状态.png

示例代码如下:

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

系统会按照从上到下的顺下查找,直到查找到第一条匹配的 item。一般来说,默认的 item 都应该放在 selecoor 的最后一条并不附带任何状态。

6、LevelListDrawable

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

level-list
    |- item
    |    |- drawable="@drawable/drawable_id"
    |    |- maxLevel="integer"
    |    |- minlevel="integer"
  • android:maxLevel

    对应最大值,取值范围 0~10000,默认为0

  • android:minlevel

    对应最小值,取值范围 0~10000,默认值为0

//在Drawable文件夹中创建bg_level.xml
<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:maxLevel="1" android:drawable="@drawable/image1" />
    <item android:maxLevel="2" android:drawable="@drawable/image2" />
    <item android:maxLevel="3" android:drawable="@drawable/image3" />
</level-list>

//在activity_main.xml中设置为ImageView背景
 <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/bg_level"/>

//在MainActivity调用setImageLevel()
   ImageView imageView = (ImageView) findViewById(R.id.image);
        imageView.setImageLevel(2);

运行结果:ImageView的背景为image2

7、TransitionDrawable

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

transition
    |- item
    |    |- drawable="@drawable/drawable_id"
    |    |- id="@+id/xxx_id"
    |    |- top="dimension"
    |    |- left="dimension"
    |    |- right="dimension"
    |    |- bottom="dimension"
    |

上面语法中的属性都已经介绍过了,其中 android:top、left、right、bottom 仍然表示 Drawable 四周的偏移量。

  • 使用方法:
//在Drawable文件夹中创建bg_tran.xml
<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/image1"/>
    <item android:drawable="@drawable/image2"/>
</transition>

//在activity_main.xml中设置为ImageView背景
 <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/bg_tran"/>

//在MainActivity调用startTransition()
        ImageView imageView = (ImageView) findViewById(R.id.image);
        TransitionDrawable td = (TransitionDrawable) imageView.getDrawable();
        td.startTransition(3000);

上面通过 startTransitionreverseTransition 方法实现淡入淡出的效果以及它的逆过程。

8、InsetDrawable

对应于 < inset > 标签,它可以将其他 Drawable 内嵌到自己当中,并可以在四周留出一定的间距。语法如下所示:

inset
    |- drawable="@drawable/drawable_id"
    |- visible="[true | false]"
    |- insetTop="dimension"
    |- insetLeft="dimension"
    |- insetRight="dimension"
    |- insetBottom="dimension"
    |
  • android:drawable,代表所引用的资源id
  • android:insetTop ,表示距离容器的上边距。其他同理
  • android:visible,是否留有边距

当一个 View 希望自己的背景比自己的实际区域小时,可以用采用 InsetDrawable 来实现,示例如下:

// 在 drawable 文件夹下创建 inset_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/image"
    android:insetLeft="10dp"
    android:insetTop="20dp"
    android:insetRight="30dp"
    android:insetBottom="40dp"
    android:visible="true" />
    
    // 引用 inset_drawable
    <View
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="@drawable/inset_drawable" />

效果图:


insetDrawable 效果图.jpg
9、ScaleDrawable

对应于 < scale > 标签,它可以根据自己的等级将制定的 Drawable 缩放到一定比例,语法如下:

scale
    |- drawable="@drawable/drawable_id"
    |- scaleGravity="[top | bottom | left | right |
        center_vertical | center_horizontal | center |
        fill_vertical | fill_horizontal | fill |
        clip_vertical | clip_horizontal]"
    |- scaleWidth="percentage"
    |- scaleHeight="percentage"
    |
  • android:drawable,表示引用的资源id
  • android:scaleGravity,等同于 bitmapDrawable 的 android:gravity
  • android:scaleWidth/android:scaleHeight,表示缩放比例,用百分比形式表示

示例:将一张图片缩小为原来的 30%,代码如下:

// 在 drawable 文件夹下创建 inset_drawable.xml
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/image"
    android:scaleWidth="70%"
    android:scaleHeight="70%"
    android:scaleGravity="center" />
    
// 引用 inset_drawable.xml
<ImageView
        android:id="@+id/image"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:src="@drawable/scale_drawable" />
  
// 在代码中设置 ScaleDrawable 的 level
ImageView mImageView = findViewById(R.id.image);
ScaleDrawable drawable = (ScaleDrawable) mImageView.getDrawable();
drawable.setLevel(1);

注意:

  • level 的取值范围为 0~1000,默认值为 0
  • 若 level 为 0,那么 scaleDrawable 是不可见的,所以需要在代码中 通过 setLevel 设置
10、ClipDrawable

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

scale
    |- drawable="@drawable/drawable_id"
    |- gravity="[top | bottom | left | right |
        center_vertical | center_horizontal | center |
        fill_vertical | fill_horizontal | fill |
        clip_vertical | clip_horizontal]"
    |- clipOrientation="[vertical | horizontal]"
    |

其中 clipOrientation 表示裁剪方向,有水平和竖直两个方向。gravity 比较复杂,需要和 clipOrientation 一起才能发挥作用,另外它可通过 “|” 来组合使用

ClipDrawable 的 gravity 属性.png

使用示例:

// 在 drawable 文件夹中创建 clip_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
    android:clipOrientation="vertical"
    android:drawable="@drawable/image"
    android:gravity="bottom" />

    // 使用 clip_drawable.xml 作为图像
    <ImageView
        android:id="@+id/image"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:src="@drawable/clip_drawable" />

        // 在代码中设置等级
        mImageView = findViewById(R.id.image);
        ClipDrawable drawable = (ClipDrawable) mImageView.getDrawable();
        drawable.setLevel(7000);

效果图:


ClipDrawable 效果图.png

对于 ClipDrawable 来说,Drawable 的等级(level)为 0 时表示完全裁剪,即整个 Drawable 都不见了,而等级 10000 表示不裁剪。


三、自定义 Drawable

  • Drawable 的使用范围单一,一个是作为 ImageView 中的图像来显示,另外一个就是作为 View 的背景,大多数情况下 Drawable 都是以 View 的背景这种形式出现的。
  • 可以通过重写 Drawable 的 draw 方法来自定义 Drawable
  • 但是,我们通常没必要去自定义 Drawable,因为自定义的 Drawable 无法复用

如果在某些特殊情况下,我们还是想要自定义 Drawable,也可以,下面给出示例代码:

//自定义Drawable
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;
    }
}

在自定义 Drawable 时注意:

  • draw、setAlpha、setColorFilter、getOpacity 这几个方法都是必须要实现的
  • 当自定义 Drawable 时绘制一张图片时,最好重写 getIntrinsicWidthgetIntrinsicHeight 方法。(上面例子中 Drawable 是颜色填充,所以没有固定大小,就不需要重写)
  • Drawable 的内部大小不等于 Drawable 的实际区域大小,Drawable 的实际区域大小可以通过它的 getBounds 方法来得到,一般来说和它的 View 的尺寸相同

相关文章

网友评论

    本文标题:开发艺术之Drawable

    本文链接:https://www.haomeiwen.com/subject/pjksthtx.html