Android开发艺术探索第六章——Android的Drawable

  1. 云栖社区>
  2. 博客>
  3. 正文

Android开发艺术探索第六章——Android的Drawable

刘桂林 2017-02-28 22:06:00 浏览630
展开阅读全文

Android开发艺术探索第六章——Android的Drawable


这本书的涉及面真的很抓重点,这章说的是Drawable,Drawable表示的是一种可以在Canvas上进行绘制的抽象概念,Drawable可不光是指图片,其实他的种类也特别繁多,在实际开发当中,他使用简单,而且对大小,效率都有不错的效果,所以是我们不可或缺的一个知识点,我们接下来就来聊聊Drawable给我们带来了哪些好处!

一.Drawable的简介

在Android中,Drawable可不是单单的图片那么简单,他说直观点可以理解为一种图片的抽象概念,通过颜色也可以定义出各式各样的图片,在实际开发中,Drawable常常被作为一个View的背景,一般分XML和图片两种,当然,我们也可以用图片来实现,不过这种方式就比较复杂了,Drawable作为一个抽象类,他衍生出了很多的子类,我们可以看下他的结构图

这里写图片描述

Drawable的内部宽高参数很重要,通过getIntrinsicWidth和getIntrinsicHeight这两个方法可以获取到他们,但是比并不是所有的Drawable都有内部宽高,比如一张图片所形成的Drawable,他就有,但是如果你是颜色所形成的的,那就自然是没有的,,而且要注意的是,内部宽高不等于他的大小,因为当View是北京,会被拉伸至View的等同大小

二.Drawable的分类

Drawable的种类是比较多的,比如我们之前常用的ShapeDrawable,BitmapDrawable,LayerDrawable,StateListDrawable等,注意的是,StateListDrawable是DrawableContainer的子类,接下来,我们继续看下这些种类繁多的Drawable到底有什么神奇的地方

1.BitmapDrawable

BitmapDrawable是比较简单的一张图片的Drawable,我们在实际开发中,可以直接设置为View的背景,也可以通过XML的形式来描述BitmapDrawable展示更多的效果,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:antialias="true"
    android:dither="true"
    android:filter="true"
    android:gravity="center"
    android:mipMap="true"
    android:src="@color/colorPrimaryDark"
    android:tileMode="clamp">

</bitmap>

下面我们来看下他们的含义都是什么

  • android:src

这个是资源,可以是图片也可以是颜色

  • android:antialias

是否开启图片抗锯齿,开启后图片会变得平滑一点,同时也会在一定程度上降低清晰度,不过这个降低我们完全可以无视,所以这个可以开启

  • android:dither

是否开启抖动效果,当图片的像素配置和手机不一致的时候,开启这个选项可以让高质量的图片在低分辨率的屏幕上保持比较好的显示效果,比如图片的色彩模式ARGB8888,但是设备只支持RGB555,这个时候开启抖动模式可以让图片不会过于失真,在Android中创建的Bitmap一般会使用ARGB8888这个模式,即ARGB四个通道各占8位,在这个色彩下,一个像素所占为4个字节,一个像素的位数综合越高,图片越逼真,抖动也应该开启

  • android:filter

是否开启过滤效果,当图片尺寸被拉伸或者压缩时,开启过滤效果会保持比较好的显示效果,所以这个也可开启

  • android:gravity

这个就不用说,位置方向,上下左右,可以用“|”符号来实现左上,右下等效果,可以具体看下他的属性,如图

这里写图片描述

  • android:mipMap

这是一种图片相关的处理技术,也叫纹理映射,比较抽象,默认为false,不常用

  • android:tileMode

平铺模式,这个选项有几个值: disabled | clamp | repeat | mirror 其中disabled 是关闭平铺模式,这个也是默认值,开启后,gravity属性会无效,先说下其余三个属性的区别,三种都表示平铺模式

  • repeat表示简单的水平和竖直方向上平铺效果
  • mirror表示一种在水平和竖直方向上的镜面投影效果
  • clamp表示四周像素扩散效果

我们来看下实际的效果:

这里写图片描述

接下来说一下NinePatchDrawable,他说白了就是用代码实现的.9图片而已,我们看一下代码

<?xml version="1.0" encoding="utf-8"?>
<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
    android:dither="true"
    android:src="@drawable/ic_launcher">

</nine-patch>

这个就不过多介绍了,基本属性和BitmapDrawable一样,不过现在已经很少有人会用代码去实现了,这样没有图片使用的好

2.ShapeDrawable

ShapeDrawable是一种很常见的Drawable,可以理解为色彩构造的图片,他即是纯色的图形,也可以具有渐变的图形,不过语法要多很多,而且繁杂,我们来看代码的实现:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="line">

    <corners
        android:bottomLeftRadius="10dp"
        android:bottomRightRadius="10dp"
        android:radius="10dp"
        android:topLeftRadius="10dp"
        android:topRightRadius="10dp" />

    <gradient
        android:angle="10"
        android:centerColor="@color/colorPrimary"
        android:centerX="10"
        android:centerY="10"
        android:endColor="@color/colorPrimary"
        android:gradientRadius="10dp"
        android:startColor="@color/colorAccent"
        android:type="linear"
        android:useLevel="true" />

    <padding
        android:bottom="10dp"
        android:left="10dp"
        android:right="10dp"
        android:top="10dp" />

    <size
        android:width="100dp"
        android:height="100dp" />

    <solid android:color="@color/colorAccent" />

    <stroke
        android:width="100dp"
        android:color="@color/colorPrimaryDark"
        android:dashGap="@dimen/activity_horizontal_margin"
        android:dashWidth="10dp" />

</shape>

需要注意的是,shape标签创建的Drawable,实际上是GradientDrawable,我们还是先来分析下他们的属性区别

  • android:shape

表示图片的形状,有四个选项,line(横线),oval(椭圆),rectangle(矩形),ring(圆环),他的默认值是矩形,而且line(横线)和ring(圆环)都必须通过stroke标签来指定宽高,颜色等信息,否则无法达到预期的效果

针对ring的形状,有五个特殊的属性,android:radius,android:tickness,android:innerRadiusRatio,android:ticknessRatio和android:userLevel,我们看图

这里写图片描述

  • < corners>

表示shape的四个角度,它只是用于矩形的shape,这里的角度是指圆角的成都,用px来表示,他有五个属性

  • android:radius:为四个角同时设置相同的角度,优先级较低,会被其他四个覆盖
  • android:topLeftRadius:设置最上角的角度
  • android:topRightRadius:设置右上角的角度
  • android:bottomLeftRadius:设置最下角的角度
  • android:bottomRightRadius:设置右下角的角度

  • < gradient>

他与solid标签是互相排斥的,因为solid表示纯色,而他表示渐变,他的属性如下

  • android:angle:渐变的角度,默认为0,其值必须为45的倍数,0表示从左往右,90表示从上到下,具体的效果自己体验
  • android:centerColor:渐变的中心颜色
  • android:centerX:渐变中心点的横坐标
  • android:centerY:渐变中心点的纵坐标
  • android:endColor:渐变的结束颜色
  • android:gradientRadius:渐变半径,只有当type = radial 时才有效
  • android:startColor:渐变的开始颜色
  • android:type:渐变的类型,
  • android:useLevel:false

  • < solid>

这个标签表示纯色填充,通过android:color来表示填充颜色

  • < stroke>

shape的描边

  • android:width:宽度
  • android:color:颜色
  • android:dashwidth:虚线线段的宽度
  • android:dashGap:组成虚线的线段之间的间隔,间隔越大虚线看起来空隙越大

注意,如果android:dashGap和android:dashwidth有任何一个为0的话,那么虚线就不能生效了,我们来看下效果

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="@android:color/white" />

    <stroke
        android:width="2dp"
        android:color="@color/colorPrimary"
        android:dashGap="2dp"
        android:dashWidth="10dp" />

</shape>

这里写图片描述

  • < pading>

这个表示空白,但是他表示的不是shape的空白,而是包含他view的空白,而且有四个属性,左上右下

  • < size>

shape的大小,有两个属性,width/height ,分别表示的是shape的宽高,也可以理解为shape的股友大小,但是一般来说,他并不是最终的大小,这个有点抽象,但是我们要明白,对于shape来说并没有宽高这个概念,作为view的背景他会适应view的宽高,size标签虽然是设置股友大小,但是还是会被拉伸

3.LayerDrawable

LayerDrawable对应的xml是< layer-list>,他可以理解为图层,通过不同的view达到叠加的效果

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/test1"
        android:bottom="5dp"
        android:drawable="@drawable/ic_launcher"
        android:left="5dp"
        android:right="5dp"
        android:top="5dp" />
    <item
        android:id="@+id/test"
        android:bottom="@dimen/activity_horizontal_margin"
        android:drawable="@drawable/ic_launcher"
        android:left="@dimen/activity_horizontal_margin"
        android:right="@dimen/activity_horizontal_margin"
        android:top="@dimen/activity_horizontal_margin" />

</layer-list>

属性很简单,一个layer-list包含多个item,形成叠加的图层效果,我们可以看一个例子

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item>
        <shape android:shape="rectangle">
            <solid android:color="#0ac39e" />
        </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>标签,他会根据view的状态来选择出现的drawable,语法如下

<?xml version="1.0" encoding="utf-8"?>
<selector 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:constantSize="true" 
    android:dither="true" 
    android:variablePadding="true">

    <item
        android:drawable="@drawable/ic_launcher"
        android:state_pressed="true"
        android:state_focused="true"
        android:state_hovered="true"
        android:state_selected="true"
        android:state_checkable="true"
        android:state_checked="true"
        android:state_enabled="true"
        android:state_activated="true"
        android:state_window_focused="true" />

</selector>

我们来解释一下上面的属性的意思

  • android:constantSize

StateListDrawable的股友大小是不随其状态的改变发生改变的,因为状态的改变会导致他切换不同的drawable,而不同的drawable具有不同的drawable,true表示StateListDrawable的固有大小不变,这时他的固有大小就是内部所有drawable的固有大小的最大值,false则是跟随状态改变,默认false

  • android:dither

是否开启抖动效果,这个在之前就已经提过,开启此选项可以让图片在低质量的屏幕上显示较好的效果,默认为true

  • android:variablePadding

StateListDrawable的pading是跟随其状态发生改变的而改变,fasle为最大值,跟constantSize类似,不建议开启,默认false

< item>标签标示的是一个具体的Drawable,他的结构也比较简单,其中drawable标示已经资源的id,剩下的就是各种状态了,我们可以看下下面的图

这里写图片描述

下面给出具体的例子

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

    <item 
        android:drawable="@color/colorAccent" 
        android:state_pressed="true" />

    <item 
        android:drawable="@color/colorPrimary" 
        android:state_focused="true" />

    <item 
        android:drawable="@color/colorAccent" />

</selector>

系统会根据View的状态从selector中选择对应的item,每一个对应着一个drawable,系统会按照从上到下来查找,当然,还有默认的drawable

5.LevenlListDrawable

LevenlListDrawable对应着标签,同样表示一个drawable的集合。集合中的每一个Drawable都有一个等级,根据不同的等级切换不同的Item

<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:drawable="@drawable/ic_launcher"
        android:maxLevel="10"
        android:minLevel="1" />

</level-list>

上面的语法中,每一个item代表一个drawable,并且对应着等级范围,由min和max来决定,我们来看一个实际的例子,,当他作为view的背景时,可以通过setLevenl来设置不同的drawable,等级的范围是0-10000

<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@color/colorAccent"
        android:maxLevel="0" />
    <item
        android:drawable="@color/colorAccent"
        android:maxLevel="1" />
</level-list>

6.TransitionDrawable

TransitionDrawable对应的是用于实现两个Drawable的淡入淡出

<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:bottom="@dimen/activity_horizontal_margin"
        android:drawable="@color/colorAccent"
        android:left="@dimen/activity_horizontal_margin"
        android:right="@dimen/activity_horizontal_margin"
        android:top="@dimen/activity_horizontal_margin" />

</transition>

上面的语法都已经结束过了,我们给出一个实例的例子

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

接着将上面的设置为View的背景,最后通过startTransition和resetTransition来操作

TransitionDrawable rawable = (TransitionDrawable) iv.getBackground();
drawable.startTransition(1000);

7.InsetDrawable

InsetDrawable对应的是,他可以将其他的Drawable内嵌到自己当中,并且可以在四周留下一定的距离,当一个View想他的背景比实际的距离小的时候就可以用,我们先来看下他的语法

<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_launcher"
    android:insetBottom="@dimen/activity_horizontal_margin"
    android:insetLeft="@dimen/activity_horizontal_margin"
    android:insetRight="@dimen/activity_horizontal_margin"
    android:insetTop="@dimen/activity_horizontal_margin">

</inset>

上面的属性都比较好理解,其中方向就不用说了,我们来看一个实例的例子再说

<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
    android:insetBottom="15dp"
    android:insetLeft="15dp"
    android:insetRight="15dp"
    android:insetTop="15dp">

    <shape>
        <solid android:color="@color/colorAccent" />
    </shape>

</inset>

8.ScaleDrawable

ScaleDrawable对应的是,他可以指定自己的等级将指定的Drawable缩放带一定的比例,语法如下

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_launcher"
    android:scaleGravity="center"
    android:scaleHeight="50%"
    android:scaleWidth="50%">

</scale>

在上门的属性中,scaleGravity的含义等同于shape中的gravity,而这个宽高就是缩放比例,以百分比的形式

ScaleDrawable有点费解,要理解他,我们就要先理解他的等级,0为不可见,要想可见,需要不为0,这个在源码中可以看出来,我们看他的draw方法

    @Override
    public void draw(Canvas canvas) {
        final Drawable d = getDrawable();
        if (d != null && d.getLevel() != 0) {
            d.draw(canvas);
        }
    }

很显然,为0都不会绘制,我们再看一下他的onBoundsChange

    @Override
    protected void onBoundsChange(Rect bounds) {
        final Drawable d = getDrawable();
        final Rect r = mTmpRect;
        final boolean min = mState.mUseIntrinsicSizeAsMin;
        final int level = getLevel();

        int w = bounds.width();
        if (mState.mScaleWidth > 0) {
            final int iw = min ? d.getIntrinsicWidth() : 0;
            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方法中,我们可以看出他的等级和大小,这里拿宽度来说

final int iw = min ? d.getIntrinsicWidth() : 0;
w -= (int) ((w - iw) * (MAX_LEVEL - level) * mState.mScaleWidth / MAX_LEVEL);

由于iw一般为0,所以上面的代码其实可以再简化一下 w -=(int)(w*(10000-level)*mState.mScaleWidth /10000),可以发现,级别越大,规律越大,那么内部的Drawable就看起来越大了,我们看一个例子,缩小30%

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_launcher"
    android:scaleGravity="center"
    android:scaleHeight="30%"
    android:scaleWidth="30%">

</scale>

首先使用上面drawable资源是不行的,还需要设置一下等级

scaleDrawable = (ScaleDrawable) iv.getBackground();
scaleDrawable.setLevel(1);

这个数值小于1000就好

9.ClipDrawable

ClipDrawable对应的是,他可以根据自己的等级裁剪另一个Drawable,裁剪的方法可以通过android:clipOrientation,先看下语法

<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
    android:clipOrientation="vertical"
    android:drawable="@drawable/ic_launcher"
    android:gravity="center">

</clip>

其中clipOrientation标示裁剪方向,有水平和竖直,gravity比较复杂,需要和clipOrientation才有作用,我们来看下这张图表

下面我们举个例子来实现一张图片从上往下进行裁剪的效果,那么我们定义一个ClipDrawable

<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
    android:clipOrientation="vertical"
    android:drawable="@drawable/ic_launcher"
    android:gravity="bottom">

</clip>

从上面的XML中,因为我们要实现顶部的裁剪效果,所以裁剪的方向是竖直,gravity属性是bottom,那么这个如何使用,设置给ImageView就好了,在代码中

clipDrawable = (ClipDrawable) iv.getBackground();
clipDrawable.setLevel(5000);

三.自定义Drawable

Drawbaled的使用范围很单一,一个座位ImageView在图像的演示,还有就是View的背景,核心就是draw方法

通常我们没有必要去自定义的,因为你无法再xml中使用,下面我们来实现一个过程

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 f = getBounds();
        float cx = f.exactCenterX();
        float cy = f.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;
    }
}

在上面的代码中,重写了需要实现的几个方法,其中draw是主要的方法,这个方法和View的draw方法类似。而其他三个就比较简单了,这个参靠下shapDrawable的实现就好了,所以说,看源码是很有必要的

这就是本章节的内容了

有兴趣就加群吧:555974449

PPT下载:PPT

Sample下载:Sample

网友评论

登录后评论
0/500
评论
刘桂林
+ 关注