Android学习笔记(十)| Drawable的基本用法


参考书籍:《Android开发艺术探索》 任玉刚
如有错漏,请批评指出!


在Android中,Drawable是一个抽象类,代表的是一种图像的概念。官方 Guide 对它的定义是:

A Drawable is a general abstraction for something that can be drawn.
Drawable 是对可绘制事物的一般抽象。

Drawable 的使用比较简单,比自定义View成本低;并且,非图片类型的Drawable占用内存小,对“apk瘦身”有帮助。因此熟练掌握各种类型的Drawable可以方便我们做一些特殊的UI效果。

Drawable 简介

  • Drawable一般都是通过xml来定义,设置为View的 background 属性值使用。在Android设计中,Drawable是一个抽象类,它是所有Drawable对象的基类。Drawable的层次关系如下:

Drawable 的分类

常见的Drawable如下:

  • BitmapDrawable
  • ShapeDrawable
  • LayerDrawable
  • StateListDrawable
  • LevelListDrawable
  • TransitionDrawable
  • InsetDrawable
  • ScaleDrawable
  • ClipDrawable
1. BitmapDrawable
  • BitmapDrawable 实际就是引用一张图片,不过可以通过属性设置很多效果。
    常用属性如下:

    属性 功能
    src 指定资源文件(只能是 .png 或 .xml)
    antialias 抗锯齿,开启后会让图片变得平滑,会降低图片清晰度(可以忽略,肉眼几乎看不出来),建议开启
    dither 防抖动,会根据手机屏幕像素配置优化图片显示效果,建议开启
    filter 过滤效果,当图片被拉伸或压缩时,可以保持较好的显示效果,建议开启
    gravity drawable在容器中的位置,可选值有top、bottom、start、end、center、center_vertical、center_horizontal、fill(填充满容器,默认值)、fill_vertical(竖直方向填充容器)、fill_horizontal(水平方向填充容器)、clip_vertical(竖直方向裁剪)、clip_horizontal(水平方向裁剪)
    tileMode 平铺模式,可选值有:disabled(关闭平铺模式,默认值, 开启后gravity属性失效)、clamp(像素扩展)、repeat(重复)、mirror(镜面投影)
  • 下面着重看看 tileMode 属性的区别:
  • 示例:

    <?xml version="1.0" encoding="utf-8"?>
    <bitmap
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:src="@drawable/ic_android"
        android:antialias="true"
        android:dither="true"
        android:filter="true"
        android:gravity=""
        android:tileMode="repeat"/>
    
2. ShapeDrawable
  • ShapeDrawable 可以理解为通过颜色来构造的图形,既可以是纯色的图形,也可以是具有渐变效果的图形。
    常用属性如下:

    属性 功能
    shape 图形的形状,可选值有:rectangle(矩形,默认值)、oval(椭圆)、line(横线)、ring(圆环)
    corners 指定shape的四个角的圆角半径
    solid 纯色填充,只有一个属性 color ,指定填充颜色
    gradient 与<solid/>标签相对,表示渐变填充,包含如下属性:type(渐变类型,有linear(线性渐变)、radial(径向渐变)、sweep(扫描线渐变)三种)、centerX(渐变中心点X坐标)、centerY(渐变中心点Y坐标)、startColor(渐变起始颜色)、centerColor(渐变过渡颜色)、endColor(渐变结束颜色)、gradientRadius(渐变半径,类型为径向渐变时有效)、useLevel(当Drawable作为StateListDrawable使用时为true,一般为false)
    stroke 描边,包含如下属性:width(描边宽度)、color(描边颜色)、dashWidth(虚线长度,与dashGap均不为0时描边为虚线)、dashGap(虚线间隔)
    padding 包含这个Drawable的View的padding值
    size ShapeDrawable大小,包含width和height两个属性,不过作为background属性指定给View时会充满View
    innerRadius 类型为 ring 时生效,表示圆环内半径
    thickness 类型为 ring 时生效,表示圆环的宽度,即外半径减去内半径的宽度
    innerRadiusRatio 内半径占整个Drawable宽度的比例,默认为9,与innerRadius同时存在时无效
    thicknessRatio 圆环宽度占整个Drawable的比例,默认为3,与thickness同时存在时无效
    useLevel 为false时圆环才能显示

    详细使用及效果可以参考博客:Android XML shape 标签使用详解

3. LayerDrawable
  • LayerDrawable 对应的 xml 标签是 <layer-list>,它表示一种层次化的 Drawable 集合,通过将不同的 Drawable 放置在不同的层上面从而达到一种叠加后的效果。
    1. 一个 layer-list 中可以包含多个 item,每个 item 表示一个 Drawable。
      item 的常用属性:
      属性 功能
      drawable 资源文件
      top 顶部偏移量(px)
      right / end 右面偏移量
      bottom 底部偏移量
      left / start 左面偏移量
      gravity Drawable在View中的位置
      layer-list 中的 item 从上往下,会依次覆盖上面 item。
    2. 简单示例——输入框
      <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
          <item>
              <shape android:shape="rectangle">
                  <solid android:color="@color/colorPrimary"/>
              </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>
      
4. StateListDrawable
  • StateListDrawable 对应于 <selector> 标签,它也表示 Drawable 集合。每个 item 对应 View 的一种状态,这样系统就会根据 View 的状态来选择合适的 Drawable。
  • StateListDrawable 主要用于设置可单击的 View 的背景,最常见的就是 Button。
    状态 含义
    state_pressed 表示按下状态,比如Button被按下而没有松开的状态
    state_focused 表示View获取了焦点
    state_selected 表示用户选中了View
    state_checked 表示用户选中了View,一般适用于CheckBox 这类在选中和非选中状态之间进行切换的View
    state_enabled 表示View当前处于可用状态
  • 示例:
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_pressed="true" android:drawable="@color/colorPrimary"/>
        <item android:state_pressed="false" android:drawable="@color/colorGray"/>
    </selector>
    
5. LevelListDrawable
  • LevelListDrawable 对应于 <level-list> 标签,同样表示一个 Drawable 集合。每个 item 对应一个等级范围的概念,根据不同等级范围,LevelListDrawable 会切换为对应的 Drawable 效果。
  • 关于 LevelListDrawable的控制,有三个属性:
    属性 含义
    drawable 资源文件
    maxLevel 等级范围最大值
    minLevel 等级范围最小值
    等级范围在:0~10000,默认值为0;
  • 示例:
    <?xml version="1.0" encoding="utf-8"?>
    <level-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:drawable="@color/colorGray"
            android:maxLevel="5"/>
        <item android:drawable="@color/colorTextYellow"
            android:maxLevel="10"/>
        <item android:drawable="@color/colorPrimary"
            android:minLevel="15"/>
    </level-list>
    
  1. 对于一般View,将 drawable 设置为 background,通过LevelListDrawable的setLevle()方法来控制等级。
        Button but = findViewById(R.id.but_levellist);
        LevelListDrawable drawable = (LevelListDrawable) but.getBackground();
        drawable.setLevel(10);
    
  2. 特别地,对于ImageView,将 drawable 设置为 src,通过ImageView的 setImageLevel() 方法控制等级。
        ImageView img = findViewById(R.id.but_levellist);
        img.setImageLevel(10);
    
6. TransitionDrawable
  • TransitionDrawable 对应于 <transition> 标签,用于实现两个Drawable之间的淡入淡出效果。
  • 他的属性和layerDrawable相似,有drawable、top、start / left、bottom、end / right 等。
  • 示例
    <?xml version="1.0" encoding="utf-8"?>
    <transition xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:drawable="@color/colorPrimary"/>
        <item android:drawable="@color/colorTextYellow"/>
    </transition>
    
    同样可以作为一般View的 background 或者 ImageView 的 src,这里以一般View为例:
        View view = findViewById(R.id.view);
        TransitionDrawable drawable = (TransitionDrawable) view.getBackground();
        drawable.startTransition(1000);
    
    TransitionDrawable 有两个方法来控制Drawable,startTransition() 和 reverseDrawable(),即开始淡入淡出效果及其逆过程。
7. InsetDrawable
  • InsetDrawable 对应于 <inset>标签,它可以将其他Drawable内嵌到自己当中。
  • InsetDrawable 有inset(设置四周边距)、insetTop、insetLeft、insetBottom、insetRight、drawable等属性
  • 示例:
    <?xml version="1.0" encoding="utf-8"?>
    <inset ="http://schemas.android.com/apk/res/android"
        android:inset="10dp"
        android:drawable="@color/colorWhite">
        <shape android:shape="rectangle">
            <solid android:color="@color/colorPrimary"/>
            <stroke android:color="@color/colorWeakBlack"
                android:width="1dp"/>
            <corners android:radius="16dp"/>
        </shape>
    </inset>
    

    这是一个类似于卡片的效果,可以作为ListView 的 item 背景,实现卡片式的item布局。
8. ScaleDrawable
  • ScaleDrawable 对应于 <scale> 标签,包含以下属性:
    属性 含义
    drawable 资源文件
    scaleGravity 等同于 shape 中的 gravity
    scaleHeight 高度缩小的比例(如:25%,表示缩小25%,而不是缩小到25%)
    scaleWidth 宽度缩小的比例(同 scaleHeight)
    useIntrinsicSizeAsMinimum 是否以drawable的固有宽高作为最小值(默认false,后面解释)
    level 等级,API24 新增(后面解释)
  • 要理解 ScaleDrawable,我们先来解释几个概念
    1. IntrinsicSize:drawable 的固有宽高,对于图片等资源而言,固有宽高就是实际的图片宽高;需要注意,大多drawable是没有宽高的概念的,比如只设置了 solid 属性的<shape>,它表示一个纯色填充,如果给这个 <shape> 的 <size> 标签设置了宽高属性,那么这个 <shape> 就有了固有宽高了。
    2. level:Drawable 中的这个 level 变量,在不同的Drawable实现类中有不同的含义,在这里用来计算缩放的比例,它的范围是:0~10000,在计算时 (10000 - levle) / 10000 表示缩小的比例。
  • 为了更好地理解,下面来看看源码:
    @Override
    public void draw(Canvas canvas) {
        final Drawable d = getDrawable();
        if (d != null && d.getLevel() != 0) {
            d.draw(canvas);
        }
    }
    
    上面是 ScaleDrawable 的 draw() 方法,可以看到,只有当 level 不为 0 时,才会绘制。而 level 的默认值就是0,因此必须设置 level 值,ScaleDrawable才能显示。
    @Override
    protected void onBoundsChange(Rect bounds) {
        final Drawable d = getDrawable();
        final Rect r = mTmpRect;
        // 对应 xml 中的 useIntrinsicSizeAsMinimum 属性
        final boolean min = mState.mUseIntrinsicSizeAsMin;
        final int level = getLevel();
        // Drawable 最终的显示区域宽度
        int w = bounds.width();
        // 这个 mScaleWidth 就是 xml 中指定的 scaleWidth 属性,也必须是大于 0 的,否则就不会缩放
        if (mState.mScaleWidth > 0) {
            // 在这里 IntrinsicWidth 就起作用了,不过 IntrinsicWidth  大多时候为 0
            // 因此,min 大多时候是 0
            final int iw = min ? d.getIntrinsicWidth() : 0;
            // MAX_LEVEL 就是10000, 这个公式是关键, 从这里可以得出下面的结论
            w -= (int) ((w - iw) * (MAX_LEVEL - level) * mState.mScaleWidth / MAX_LEVEL);
        }
    
        int h = bounds.height();
        if (mState.mScaleHeight > 0) {
            final int ih = min ? d.getIntrinsicHeight() : 0;
            h -= (int) ((h - ih) * (MAX_LEVEL - level) * mState.mScaleHeight / MAX_LEVEL);
        }
    
        final int layoutDirection = getLayoutDirection();
        Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection);
    
        if (w > 0 && h > 0) {
            d.setBounds(r.left, r.top, r.right, r.bottom);
        }
    }
    
    这一段是 onBoundsChange() 方法,在 drawable 绘制时会被回调,上面的代码中注释很详细,下面就给出几个结论:
    1. drawable 的缩放比例由 xml 中的 scaleWidth / scaleHeight 和 level 共同决定。并且,scaleWidth / scaleHeight 越大,drawable越小;level 越大,drawable越大。
    2. 在使用时,为了方便,我们一般将 level 设置为1,这样 (MAX_LEVEL - level) / MAX_LEVEL 就约等于 1,因此,level对缩放的影响几乎没有了。在API 23以下,我们需要使用Java代码设置level,在 API24 以上,则可以在xml 中设置。
  • 示例:
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <View
            android:id="@+id/view"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="@drawable/drawable_scale" />
    
        <View
            android:layout_width="match_parent"
            android:layout_height="0.1dp"
            android:background="@color/colorWeakBlack"/>
    </LinearLayout>
    
    <?xml version="1.0" encoding="utf-8"?>
    <scale xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@color/colorPrimary"
        android:scaleGravity="center"
        android:scaleHeight="30%"
        android:scaleWidth="30%" />
    
    View view = findViewById(R.id.view);
    ScaleDrawable drawable = (ScaleDrawable) view.getBackground();
    drawable.setLevel(1);
    

    上面的示例中,drawable的显示区域就是View的宽高,缩小30%,因此最终显示的大小就是View的70%。
9. ClipDrawable
  • ClipDrawable 对应于 <clip> 标签,他可以对drawable进行裁剪。
  • 它包含三个属性:
    属性 含义
    drawable 资源文件
    gravity 控制裁剪位置
    clipOrientation 裁剪方向(vertical(竖直),orientation(水平))
  • 在 ClipDrawable 中,level 的作用是控制 Drawable 的裁剪百分比,值越大,裁剪区域越小。(0表示不裁剪,10000表示全部裁剪,不显示)
  • 下面通过一个示例来看看:
    <?xml version="1.0" encoding="utf-8"?>
    <clip xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@drawable/ic_image"
        android:gravity="left"
        android:clipOrientation="horizontal"/>
    
    View view = findViewById(R.id.view);
    ClipDrawable drawable = (ClipDrawable) view.getBackground();
    drawable.setLevel(8000);
    

    这种 Drawable 中,gravity 属性的控制效果有很多,可以自行尝试,看看效果。


上一篇:Android学习笔记(九)| Android动画(下)—— 属性动画

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

推荐阅读更多精彩内容