美文网首页Android开发IT@程序员猿媛
Android学习笔记(十)| Drawable的基本用法

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

作者: CCCode1997 | 来源:发表于2019-04-24 16:16 被阅读59次

    参考书籍:《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动画(下)—— 属性动画

    相关文章

      网友评论

        本文标题:Android学习笔记(十)| Drawable的基本用法

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