zoukankan      html  css  js  c++  java
  • 进阶之路 | 奇妙的Drawable之旅

    前言

    本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍:

    我的GIthub博客

    学习清单:

    • Drawable简介
    • Drawable分类
    • 自定义Drawable

    一.为什么要学习Drawable?

    Drawable种类繁多,它们都表示一种图像的概念,但是它们不全是图片。在实际开发中,Drawable经常被用来作为View的背景使用。

    Drawable可以方便我们做出一些特殊的UI效果,这一点在UI相关的开发工作中极为重要。面对UI设计师设计出来的各式各样的按钮点击效果,动态效果,渐变效果,好看是好看,我们程序员往往会咆哮:"你舒服了,我们呢!!"别慌,学好Drawable,你会对各种效果信手拈来,了然于胸,胸有成竹!!

    而且,Drawable在开发中也有自己的优点:

    • 使用简单,成本低于自定义View

    • 非图片类型的Drawable占用空间较小,对于减少APK大小有所裨益

    综上,掌握好Drawable,走遍天下也不怕!(jia de)

    二.核心知识点归纳

    2.1 Drawable简介

    Q1:Drawable类是抽象类,是所有Drawable的基类。继承关系如下:

    Drawable继承关系

    Q2:Drawable使用方式:

    • 创建所需Drawable的根节点的xml,再通过@drawable/xxx引入布局中。(常用)
    • 普通控件(非ImageView)是设置background
    • ImageView是设置src
    • Java代码:new一个所需Drawable并set相关属性,最后加载到布局中。

    Q3:内部宽高了解多少

    • 获取方式:getIntrinsicWidth()getIntrinsicHeight()

    注意

    • 并不是所有Drawable都有内部宽/高
    • 图片所形成的Drawable的内部宽/高就是图片的宽/高。
    • 颜色所形成的Drawable默认情况下没有内部宽/高的概念(除非指定size)。
    • 内部宽高不等于大小,Drawable没有大小概念
    • Drawable被用作background的时候,自动被拉伸到View同等大小;Drawable被用作src的时候,存放原图大小比例,不会被拉伸

    2.2 Drawable种类

    2.2.1 BitmapDrawable

    • 表示一张图片
    • 常用属性:
    bitmap
        |- src="@drawable/res_id"
        |- antialias="[true | false]"
        |- dither="[true | false]"
        |- filter="[true | false]"
        |- tileMode="[disabled | clamp | repeat | mirror]"
        |- gravity="[top | bottom | left | right | center_vertical |
        |            fill_vertical | center_horizontal | fill_horizontal |
        |            center | fill | clip_vertical | clip_horizontal]"
    
    • src:图片的资源id
    • antialias:是否开启图片抗锯齿。开启后会让图片会更加平滑,同时清晰度降低很少,应该开启。
    • dither:是否开启抖动效果。开启后让高质量的图片的在低质量的屏幕上显示不失真,应该开启。
    • filter:是否开启过滤效果。当图片尺寸被拉伸或压缩时,开启后可保持较好的显示效果,应该开启
    • tileMode:平铺模式。开启后gravity会失效;可选值的具体含义:
    可选项 含义
    disable 默认值,关闭平铺模式
    mirror 在水平和垂直方向的镜面投影效果
    repeat 在水平和垂直方向的平铺效果
    clamp 图片四周像素会扩散到其他区域

    具体效果:

    tileMode效果

    • gravity:若位图比容器小,可以设置位图在容器中的相对位置。可选值的具体含义:

    gravity属性

    • 使用方法:以下两种方法效果相同,图见之前截图中所示的mirror情况。

    a.xml:

    //在Drawable文件夹中创建bg_tilemode_mirror.xml
    <?xml version="1.0" encoding="utf-8"?>
    <bitmap xmlns:android="http://schemas.android.com/apk/res/android"
        android:dither="true"
        android:src="@mipmap/ic_launcher"
        android:tileMode="mirror"
        >
    </bitmap>
    
    //在activity_main.xml中设置为View背景
    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/bg_tilemode_mirror"
        />
    

    b.Java代码:

    //在MainActivity创建BitmapDrawable
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
            BitmapDrawable bitDrawable = new BitmapDrawable(bitmap);
            bitDrawable.setDither(true);
            bitDrawable.setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
    
    //加载到mylayout布局
            LinearLayout myLayout = (LinearLayout) findViewById(R.id.mylayout);
            myLayout.setBackgroundDrawable(bitDrawable);
    

    2.2.2 NinePatchDrawable

    • 表示一张.9格式的图片
    • 作用:可自动地根据所需的宽/高进行相应的缩放并保证不失真。
    • 制作方法及原理:可以参考博客:9patch / NinePatch 详解及使用
    • 常用属性:和本文2.2.1 BitmapDrawable一样
    • 使用方法: 不建议用Java代码创建NinePatchDrawable,建议使用XML定义,代码见下。
    //在Drawable文件夹中创建bg_nine_patch.xml
    <?xml version="1.0" encoding="utf-8"?>
    <nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
        android:dither="true"
        android:src="@drawable/box"
        >
    </nine-patch>
    
    //在activity_main.xml中设置为EditText背景
    <EditText
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/bg_nine_patch"
        />
    

    2.2.3 ShapeDrawable

    • 可表示纯色、有渐变效果的基础几何图形(矩形,圆形,线条等)
    • 根节点shape,子节点cornersgradientpaddingsizesolidstroke
    <?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" />
    

    接下来分别解释各个节点下属性含义:

    Q1 shape:图形的形状,可选值有:

    • rectangle(矩形):为默认值。
    • oval(椭圆)
    • line(横线):

    注意:必须通过stroke标签来指定横线的宽度和颜色等信息。

    • ring(圆环):
    • 注意:必须通过stroke标签来指定圆环线的宽度和颜色等信息。
    • 圆环还有额外几个属性,如下图所示:

    ring额外属性

    Q2 corners:表示shape的四个圆角的角度,只适用于矩形

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

    Q3:gradient:渐变效果,与solid纯色填充是互斥的。

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

    渐变的样式

    • padding:与四周空白的距离。
    • size:图形的固有大小,非最终大小

    android:widthandroid:height分别设定shape的宽/高。

    • solid:纯色填充。

    android:color:指定填充的颜色。

    • stroke:描边。属性含义:
    stroke的属性 作用
    width 描边的宽度
    color 描边的颜色
    dashWidth 虚线的宽度
    dashGap 虚线的空隙的间隔

    2.2.4 LayerDrawable

    • 表示一种层次化的Drawable集合,通过将不同的Drawable放置在不同的层上面从而达到一种叠加后的效果。
    • 根节点layer-list,常用属性:
    layer-list
        |- item
        |    |- drawable="@drawable/drawable_id"
        |    |- id="@+id/xxx_id"
        |    |- top="dimension"
        |    |- left="dimension"
        |    |- right="dimension"
        |    |- bottom="dimension"
        |
    

    注意:每组 Drawable 由item节点进行配置,一个layer-list可包含多个item,服从下面item覆盖上面item的原则。

    A.drawable:所引用的位图资源id,如果为空需要有一个Drawable类型的子节点。

    B.id:层id。

    C.left:层相对于容器的左边距。

    D.right:层相对于容器的右边距。

    E.top:层相对于容器的上边距。

    F.bottom:层相对于容器的下边距。

    • 实例:bitmap的简单叠加:
    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item>
            <bitmap
                android:gravity="center"
                android:src="@mipmap/ic_launcher_round"
                 />
        </item>
        <item
            android:left="20dp"
            android:top="30dp">
            <bitmap
                android:gravity="center"
                android:src="@mipmap/ic_launcher_round"
                 />
        </item>
        <item
            android:left="70dp"
            android:top="80dp">
            <bitmap
                android:gravity="center"
                android:src="@mipmap/ic_launcher_round"
                 />
        </item>
    </layer-list>
    

    bitmap的简单叠加

    2.2.5 StateListDrawable

    • 表示一个Drawable的集合,每个Drawable对应着View的一种状态
    • 根节点selector,常用属性:
    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]"
        |
    

    A.selector:

    • constantSize固有大小是否不变。
    • 默认为false,表示固有大小随着状态的改变而改变。

    • 设为true,则表示固有大小是固定值,是内部所有Drawable的固有大小中的最大值

    • dither:是否开启抖动效果。开启后让高质量的图片的比较低质量的屏幕上不失真。默认开启。
    • variblePadding:其padding是否随状态的改变而改变。
    • 默认为false,表示padding是固定值,是其内部所有Drawable的padding中的最大值
    • 为true,则表示padding随着状态的改变而改变。

    B.item:

    • drawable:所引用的位图资源id。
    • 表示各种状态的属性:
    状态 含义
    state_pressed(常用) 按下状态
    state_focused 已经获取了焦点
    state_selected 选择了View
    state_checked 适用于checkBox
    state_enabled 表示可用状态
    • 实例:
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize="false" android:dither="true" android:variablePadding="false">
        <item android:drawable="@drawable/red_bg" android:state_pressed="false" />
        <item android:drawable="@color/black_bg" android:state_pressed="true" />
    </selector>
    

    2.2.6 LevelListDrawable

    • 表示一个Drawable集合,集合中的每个Drawable都有一个等级的概念。通过设置不同的等级来切换具体的Drawable
    • 根节点level-list,常用属性:
    level-list
        |- item
        |    |- drawable="@drawable/drawable_id"
        |    |- maxLevel="integer"
        |    |- minlevel="integer"
    
    • drawable:引用的位图资源id。
    • maxLevel:对应的最大值,取值范围为0~10000,默认为0。(常用)
    • minlevel:对应的最小值,取值范围为0~10000,默认为0。
    • 使用方法:无论是用xml还是代码实现,若作为View背景,都需要在Java代码中调用setLevel()方法;若作为ImageView前景,需要调用setImageLevel()

    • 加载规则:当某item的android:maxLevel 等于 setLevel所设置的数值时就会被加载。若都没有匹配的则都不显示。

    • 实例:

      //在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。

    2.2.7 TransitionDrawable

    • LayerDrawable的子类,实现两层 Drawable之间的淡入淡出效果。
    • 根节点transition,常用属性和LayerDrawable相同,不再赘述。
    transition
        |- item
        |    |- drawable="@drawable/drawable_id"
        |    |- id="@+id/xxx_id"
        |    |- top="dimension"
        |    |- left="dimension"
        |    |- right="dimension"
        |    |- bottom="dimension"
        |
    
    • 使用方法:无论是用xml还是代码实现,若作为View背景,都需要在Java代码中调用startTransition()方法才能启动两层间的切换动画,也可以调用reverseTransition()方法反方向切换。

    • 实例:

    //在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的src
     <ImageView
            android:id="@+id/image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/bg_tran"
            android:src="@drawable/bg_tran"/>
    
    //在MainActivity调用startTransition()
            ImageView imageView = (ImageView) findViewById(R.id.image);
            TransitionDrawable td = (TransitionDrawable) imageView.getDrawable();
    	    TransitionDrawable td2 = (TransitionDrawable) imageView.getBackground();
            td.startTransition(3000);
            td2.startTransition(3000);
    

    运行结果:ImageView的背景从image1缓缓切换到image2。

    2.2.8 InsetDrawable

    • 表示把一个Drawable嵌入到另外一个Drawable的内部,并在四周留一些间距。

    与Drawable的padding属性不同:padding表示的是Drawable的内容与Drawable本身的边距;而InsetDrawable表示的是Drawable与容器之间的边距。

    • 根节点inset,常用属性:
    inset
        |- drawable="@drawable/drawable_id"
        |- visible="[true | false]"
        |- insetTop="dimension"
        |- insetLeft="dimension"
        |- insetRight="dimension"
        |- insetBottom="dimension"
        |
    
    • drawable:所引用的位图资源id。
    • visible:是否留有边距。(经测试,发现设置true/false效果一样....)
    • insetTop:设置距离容器的上边距。其他同理。
    • 适用场景:当控件需要的背景比实际的边框
    • 实例:
    //在drawable文件夹下创建
    <?xml version="1.0" encoding="utf-8"?>
    <inset xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@drawable/image"
        android:insetBottom="40dp"
        android:insetLeft="10dp"
        android:insetRight="30dp"
        android:insetTop="20dp"
        android:visible="true">
    </inset>
    

    InsetDrawable效果

    • 面试题:为一个充满整个屏幕的LinearLayout布局指定背景图,是否可以让背景图不充满屏幕?

    答案:可以使用嵌入(Inset)图像资源来指定图像,然后像使用普通图像资源一样使用嵌入图像资源

    2.2.9 ScaleDrawable

    • 表示将Drawable缩放到一定比例。
    • 根节点scale,常用属性:
    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"
        |
    
    • drawable:所引用的位图资源id。
    • scaleGravity:等同于BitmapDrawable的android:gravity
    • scaleWidth/android:scaleHeight:指定Drawable宽/高的缩放比例,以百分比的形式表示。
    • 使用方法:无论是用xml还是代码实现,若作为View背景,都需要在Java代码中调用setLevel()方法控制Drawable等级。
    • level取值范围为0~10000
    • 默认值为0:表示不可见;1~10000:表示可见
    • 一般level设为1即可
    • 实例:将一张图片缩小为原来的30%,代码为:
    //在drawable文件夹下创建bg_scale.xml
    <?xml version="1.0" encoding="utf-8"?>
    <scale xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@drawable/drawable_test"
        android:scaleGravity="center"
        android:scaleHeight="70%"
        android:scaleWidth="70%"/>
    
    //在activity_main.xml中设置为ImageView背景
     <ImageView
            android:id="@+id/image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/bg_scale"/>
    
    //在MainActivity调用setLevel()
             ImageView imageView = (ImageView) findViewById(R.id.image);
            ScaleDrawable scaleDrawable = (ScaleDrawable) imageView.getDrawable();
            scaleDrawable.setLevel(1);
    

    2.2.10 ClipDrawable

    • 表示裁剪一个Drawable。
    • 根节点clip,常用属性:
    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]"
        |
    
    • drawable:所引用的位图资源id。
    • gravity:表示对齐方式,需要和clipOrientation一起发挥作用。可选值含义:

    ClipDrawable的gravity

    • clipOrientation:表示裁剪方向,可选值有水平和竖直。
    • 使用方法:无论是用xml还是代码实现,若作为View背景,都需要在Java代码中调用setLevel()方法控制可见区大小。
    • level取值范围为0~10000。
    • 0:表示完全裁剪,即不可见;10000:表示不裁剪。
    • level越大可见区越大。
    • 一般level设为1即可

    2.3 自定义Drawable

    • 工作原理的核心是draw():系统调用Drawable的draw()来绘制View的背景或ImageView的图像。
    • 通常没有必要去自定义Drawable,因为无法在XML中使用自定义Drawable,这就降低了其使用范围。
    • 创建自定义Drawable,必须重写其draw()setAlpha()setColorFilter()getOpacity()等方法.以下为自定义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有固有大小时(Drawable是图片),最好重写getIntrinsicWidth()getIntrinsicHeight(),因为它会影响到View的wrap_content布局。

    注意:Drawable的内部大小不等于Drawable的实际大小,后者可通过getBounds()获得,一般它和View的尺寸相同。

    三.知识拓展

    恭喜你!已经看到这里了,相信你已经对Drawable有一定的见解了!本文只是介绍了Drawable中常用的类型,并没有完全列出所有Drawable的类型,而且只是介绍了XML的创建。

    但是,笔者也给好奇心强的读者准备了一些干货(一篇博客),里面详细介绍了Drawable的各种类型,各种创建方法,总的来说还是写得比较不错的。指路:Drawable子类用法总结.

    下面展示下本文还没来得及赘述的Drawable:

    AnimationDrawable

    RippleDrawable

    RoundedBitmapDrawable

    DrawerArrowDrawable


    如果文章对您有一点帮助的话,希望您能点一下赞,您的点赞,是我前进的动力

    本文参考链接:

  • 相关阅读:
    android 本地字符串存取
    2020-07-17:线上一个服务有4个实例突然变得访问很慢,你会从什么地方入手找原因?
    2020-07-16:如何获得一个链表的倒数第n个元素?
    2020-07-15:死锁与活锁的区别,死锁与饥饿的区别?
    2020-07-14:es用过冷热分离吗?假如现在有些数据热变冷,有些数据冷变热,怎么解决?
    2020-07-28:已知sqrt (2)约等于 1.414,要求不用数学库,求sqrt (2)精确到小数点后 10 位。
    2020-07-29:从 innodb 的索引结构分析,为什么索引的 key 长度不能太长?
    2020-07-27:如何设计一个分布式文件系统,如何设计动态扩容和数据定位?
    2020-07-26:如何用 socket 编程实现 ftp 协议?
    2020-07-25:如何实现一个高效的单向链表逆序输出?
  • 原文地址:https://www.cnblogs.com/xcynice/p/qi_miao_de_drawable_zhi_lv.html
Copyright © 2011-2022 走看看