Android Drawable入门

1. 概述

Drawable 是一种媒介,它可以把内容绘制到 Canvas 上。

  • 一种可以在Canvas上进行绘制的抽象的概念
  • 颜色、图片等都可以是一个Drawable
  • Drawable可以通过XML定义,或者通过代码创建
  • Android中Drawable是一个抽象类,每个具体的Drawable都是其子类

Drawable的优点

  • 使用简单,比自定义View成本低
  • 非图片类的Drawable所占空间小,能减小apk大小

Drawable的内部宽/高

  • 通过getIntrinsicWidth/Height方法能获得内部宽/高,注意和setBounds方法搭配使用
  • 图片Drawable其内部宽高就是图片的宽高
  • 颜色Drawable没有内部宽高的概念
  • 内部宽高不等同于它的大小,一般Drawable没有大小概念(作为View背景时,会被拉伸至View的大小)

2. Drawable主要类型及创建

Drawable资源是可绘制到屏幕上的图形的一般概念,可以使用getDrawable(int)之类的API检索该图形。有几种不同类型的Drawable:

类型描述
BitmapDrawable位图图形文件(.png, .jpg或.gif)。
NinePatchDrawable具有可拉伸区域的PNG文件,允许基于内容调整图像大小(.9 PNG)。
LayerDrawable管理其他可绘制项数组的Drawable。这些是按数组顺序绘制的,因此索引最大的元素是在顶部绘制的。
StateListDrawable不同的状态引用不同的位图图形(例如,当按下按钮时使用按下效果的图形)。
LevelListDrawable定义了一个可绘制项,该Drawable管理多个可替换的Drawable,每个可绘制项分配一个最大数值。
TransitionDrawable定义可绘制资源的XML文件,可在两个Drawable之间交叉淡出。
InsetDrawable该XML文件以指定的距离插入另一个Drawable。当视图需要一个比视图实际边界小的背景可绘制项时,这是非常有用的。
ClipDrawable定义一个drawable,根据这个Drawable的当前级别值剪辑另一个drawable。
ScaleDrawable定义可绘制项的XML文件,该文件根据另一个Drawable的当前级别值更改其大小
GradientDrawable定义几何形状(包括颜色和梯度)的XML文件。

注意:颜色资源也可以用作XML中的可绘制项。例如,当创建一个状态列表drawable时,可以为android:drawable属性引用一个颜色资源(android:drawable="@color/green")

Drawable继承关系说明:

  • 直接继承的Drawable的子类则没有child

  • 继承DrawableWrapper的子类只能有一个child

  • 继承DrawableContainer的子类可以有多个child

  • 本身LayerDrawable与DrawableContainer一样,可以有多个child。

android.graphics.drawable.DrawableInflater类负责创建对应的Drawable对象,关键代码如下:

public final class DrawableInflater {
    /**
     * Version of {@link #inflateFromXml(String, XmlPullParser, AttributeSet, Theme)} that accepts
     * an override density.
     */
    @NonNull
    Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
                                      @NonNull AttributeSet attrs, int density, @Nullable Theme theme)
        throws XmlPullParserException, IOException {
        
        // 1. xml形式:定义drawable标签,class属性值为自定义Drawable的完整类路径
        if (name.equals("drawable")) {
            name = attrs.getAttributeValue(null, "class");
            if (name == null) {
                throw new InflateException("<drawable> tag must specify class attribute");
            }
        }

        // 2. 尝试解析系统Drawable的标签名
        Drawable drawable = inflateFromTag(name);
        if (drawable == null) {
            // 3. 反射创建自定义Drawable
            drawable = inflateFromClass(name);
        }
        drawable.setSrcDensityOverride(density);
        // 4. 解析Drawable下的visible属性值
        drawable.inflate(mRes, parser, attrs, theme);
        return drawable;
    }
}

可以看到,主要分为系统的Drawable创建和自定义的Drawable创建,补充一下系统Drawable创建代码:

public final class DrawableInflater {
    // 默认是解析xml下定义的系统Drawable标签名
    private Drawable inflateFromTag(@NonNull String name) {
        switch (name) {
            case "selector":
                return new StateListDrawable();
            case "animated-selector":
                return new AnimatedStateListDrawable();
            case "level-list":
                return new LevelListDrawable();
            case "layer-list":
                return new LayerDrawable();
            case "transition":
                return new TransitionDrawable();
            case "ripple":
                return new RippleDrawable();
            case "adaptive-icon":
                return new AdaptiveIconDrawable();
            case "color":
                return new ColorDrawable();
            case "shape":
                return new GradientDrawable();
            case "vector":
                return new VectorDrawable();
            case "animated-vector":
                return new AnimatedVectorDrawable();
            case "scale":
                return new ScaleDrawable();
            case "clip":
                return new ClipDrawable();
            case "rotate":
                return new RotateDrawable();
            case "animated-rotate":
                return new AnimatedRotateDrawable();
            case "animation-list":
                return new AnimationDrawable();
            case "inset":
                return new InsetDrawable();
            case "bitmap":
                return new BitmapDrawable();
            case "nine-patch":
                return new NinePatchDrawable();
            case "animated-image":
                return new AnimatedImageDrawable();
            default:
                return null;
        }
    }
}

3. Bitmap

位图图像。Android支持三种格式的位图文件:.png(首选)、.jpg(可接受)、.gif(不推荐)。

XML位图是用XML定义的指向位图文件的资源。其效果是原始位图文件的别名。XML可以为位图指定额外的属性,比如抖动和平铺。

  • 位置:res/drawable/filename.xml

  • 类型:BitmapDrawable

  • 继承关系:BitmapDrawable -> Drawable

  • 语法:

    <?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:antialias图片抗锯齿。图片平滑,清晰度降低
android:dither位图抖动。如果位图没有与屏幕相同的像素配置,则启用或禁用位图的抖动(例如:具有RGB 565屏幕的ARGB 8888位图)。
用于高质量图片在低质量屏幕上保存较好的显示效果(不会失真)
android:filter位图筛选。当位图收缩或拉伸用来平滑其外观,使用过滤。
android:mipMap纹理映射。图像处理技术。默认值为false。
android:tileMode定义平铺模式。当启用平铺模式时,将重复位图。启用平铺模式时将忽略android:gravity属性。默认disable。

4. Nine-Patch

NinePatch是一款PNG图像,当视图中的内容超过正常图像边界时,你可以定义Android缩放的可拉伸区域。通常,将这种类型的图像指定为至少将一个维度设置为wrap_content的视图的背景,当视图增长以适应内容时,还将缩放Nine-Patch 的图像以匹配视图的大小。

  • 位置:res/drawable/filename.9.png

  • 类型:NinePatchDrawable

  • 继承关系:NinePatchDrawable -> Drawable

  • 语法:

    <?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的图片类型必须为res/drawable/filename.9.png

5. Layer list

LayerDrawable是一个管理多个drawable的层次容器。列表中的每个drawable会按照顺序依次绘制,即列表中的最后一个drawable会在顶部绘制的。

每个drawable由单个<layer-list>元素中的<item>元素表示。

  • 位置:res/drawable/filename.xml

  • 类型:LayerDrawable

  • 继承关系:LayerDrawable -> 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" />
    </layer-list>
    

top、bottom、left、right为各自方向上的偏移量。其实LayerDrawable好比一个FrameLayout容器,而当中的各个Item按照Z轴方向上排布各个层面

6. State list

StateListDrawable是一个用XML定义的Drawable,它使用几个不同的图像来表示相同的图形,这取决于对象的状态。例如,Button可以存在于几种不同的状态之一(按下、聚焦或都不存在),并且使用状态列表drawable,可以为每种状态提供不同的背景图像。

  • 位置:res/drawable/filename.xml

  • 类型:StateListDrawable

  • 继承关系:StateListDrawable -> DrawableContainer -> 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>
    
属性名称描述
android:constantSize当状态改变时,true值的Drawable内部大小保持不变(大小为所有状态的最大值);反之,大小根据当前状态而变化。默认是false。
android:variablePadding当为true值,Padding值不随当前状态而改变;反之,Padding保持不变(基于所有状态的最大填充)。默认为false
android:state_pressed当处于touched/clicked状态,该Item将会生效
android:state_focused当处于input focus(如用户输入text)时,该Item将会生效
android:state_selected当处于selected状态时,该Item将会生效
android:state_enabled当处于enable状态时,该Item将会生效

注意:Drawable状态列表中会优先匹配第一项,不满足则继续找下一个状态。故全匹配状态应放到最后的位置上,示例如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"
          android:drawable="@drawable/button_pressed" /> <!-- pressed -->
    <item android:state_focused="true"
          android:drawable="@drawable/button_focused" /> <!-- focused -->
    <item android:state_hovered="true"
          android:drawable="@drawable/button_focused" /> <!-- hovered -->
    <item android:drawable="@drawable/button_normal" /> <!-- default -->
</selector>

7. Level list

管理多个可替换的Drawable,每个Drawable分配一个最大数值。使用setLevel()设置drawable的level值将加载level列表中具有android:maxLevel值大于或等于传递给方法的值的drawable资源。

  • 位置:res/drawable/filename.xml

  • 类型:LevelListDrawable

  • 继承关系:LevelListDrawable -> DrawableContainer -> 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-list>
    

动态改变level:

    ImageView imageView = (ImageView) findViewById(R.id.level_list_drawable);
    imageView.getBackground().setLevel(...);

8. Transition drawable

TransitionDrawable是一个可绘制对象,它可以在两个可绘制资源之间交叉淡出。

每个Drawable由单个<transition>元素中的<item>元素表示。不支持超过两个项目。要向前转换,请调用startTransition()。要向后转换,请调用reverseTransition()

  • 类型:TransitionDrawable

  • 继承关系:TransitionDrawable -> LayerDrawable -> 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" />
    </transition>
    

代码调用:

    ImageView imageView = (ImageView) findViewById(R.id.transition);
    TransitionDrawable drawable = (TransitionDrawable) imageView.getBackground();
    if (!isReverse) {
        drawable.startTransition(2000);
        isReverse = true;
    } else {
        drawable.reverseTransition(2000);
        isReverse = false;
    }

9. Inset drawable

将一个Drawable在指定的距离插入其中。当所需视图边界比实际视图的边界小时,就变得很有用了。

  • 类型:InsetDrawable

  • 继承关系:InsetDrawable -> DrawableWrapper -> Drawable

  • 语法:

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

10. Clip drawable

根据该Drawable的当前级别裁剪出所需的Drawable。可以根据级别控制子drawable在宽度和高度上被裁剪的程度,以及控制它在整个容器中的位置的Gravity。最常用来实现进度条之类的东西。

  • 类型:ClipDrawable

  • 继承关系:ClipDrawable -> DrawableWrapper -> Drawable

  • 语法:

    <?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:控制裁剪开始的地方

代码调用例子:

    ImageView imageview = (ImageView) findViewById(R.id.image);
    Drawable drawable = imageview.getBackground();
    if (drawable instanceof CLipDrawable) {
        // 范围区间:from=0, to=10000
        ((ClipDrawable)drawable).setLevel(drawable.getLevel() + 1000);
    }

11. Scale drawable

根据另一个Drawable的当前级别更改其大小。

  • 类型:ScaleDrawable

  • 继承关系:ScaleDrawable -> DrawableWrapper -> 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:scaleGravity:指定开始缩放的位置
    • android:scaleHeight:初始状态时,可用来缩放高度的百分比
    • android:scaleWidth:初始状态时,可用来缩放宽度的百分比

12. Shape drawable

用XML定义的通用形状。

  • 类型:GradientDrawable

  • 继承关系:GradientDrawable -> 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:topLeftRadius="integer"
            android:topRightRadius="integer"
            android:bottomLeftRadius="integer"
            android:bottomRightRadius="integer" />
        <gradient
            android:angle="integer"
            android:centerX="float"
            android:centerY="float"
            android:centerColor="integer"
            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>
    
    • corners标签:圆角
    • android:radius:圆角的半径
    • gradient:渐变色
    • android:angle:渐变色起始角度,必须是能被45整除的值。默认为0度,即Y轴正方向
    • android:useLevel:渐变色的变化是否启用LevelListDrawable的level功能,即调用setLevel方法控制
    • stroke标签:描边,相当于Paint.STROKE
    • stroke标签下的android:width:描边的宽度
    • android:dashWidth:每个分割线的厚度
    • android:dashGap:每个分割线的间隔大小。只有在设置android:dashWidth时才有效。
    • solid标签:填充,相当于Paint.FILL

13. RotateDrawable

根据level值可旋转的Drawable

  • 类型:RotateDrawable

  • 继承关系:RotateDrawable -> DrawableWrapper -> Drawable

  • 语法:

    <?xml version="1.0" encoding="utf-8"?>
    <rotate
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:fromDegrees="0"
        android:toDegrees="180"
        android:pivotX="50%"
        android:pivotY="50%"
        android:visible="true">
    
        <bitmap android:gravity="center"
                android:src="@drawable/sample_1"/>
    </rotate>
    
    • pivotXpivotY控制旋转的中心,我们定义为50%,就是图片的中心位置。
    • fromDegreestoDegrees是选择的起始角度和终结角度。

14. RippleDrawable

水波纹效果的 Drawable

  • 类型:RippleDrawable
  • 继承关系:RippleDrawable -> LayerDrawable -> Drawable
14.1 无边界水波纹效果

RippleDrawable 是继承自 LayerDrawable 的,如果一个 Layer 层都没有的话,就是无边界效果

<?xml version="1.0" encoding="utf-8"?>
<ripple
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="@color/colorAccent">
</ripple>
14.2 有边界水波纹效果

如果加了一个图层作为背景就会有边界效果。

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

    <item android:drawable="@drawable/ripple_shape_drawable"/>
</ripple>

<!--ripple_shape_drawable.xml-->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#b4abab"/>
    <corners android:radius="40dp"/>
</shape>

15. 自定义Drawable

当我们想创建一些自定义绘图时,可以通过扩展Drawable类(或其任何子类)来实现。

主要是实现draw()方法,因为它提供了绘图指令的Canvas对象。

以下是一个自定义Drawable实现画圆形的实例:

public class MyDrawable extends Drawable {
    private final Paint redPaint;

    public MyDrawable() {
        // Set up color and text size
        redPaint = new Paint();
        redPaint.setARGB(255, 255, 0, 0);
    }

    @Override
    public void draw(Canvas canvas) {
        // Get the drawable's bounds
        int width = getBounds().width();
        int height = getBounds().height();
        float radius = Math.min(width, height) / 2;
        // Draw a red circle in the center
        canvas.drawCircle(width/2f, height/2f, radius, redPaint);
    }

    @Override
    public void setAlpha(int alpha) {
        // This method is required
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        // This method is required
    }

    @Override
    public int getOpacity() {
        // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
        return PixelFormat.OPAQUE;
    }
}

在API 24以前,只能在xml文件中使用framework中自带的那些Drawable类,要用自定义的Drawable只能在Java代码中使用。

在API 24+开始可以在xml文件中使用我们自定义的Drawable了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值