《Android开发艺术探索》之理解Drawable(七)

231 阅读7分钟

                                                                       第六章  Android的Drawable
Drawable表示的是一种可以在Canvas上进行绘制的抽象概念,最常见的图片和颜色都可以是一个Drawable。 本章讲述:Drawable的层次关系、Drawable的分类、自定义Drawable。Drawable的优点:1.使用简单,比View成本低;2、非图片类型的Drawable占用空间较小。3.可以做出一些特殊的UI效果。
(一)Drawable简介
Drawable表示的是图像的概念而又不全是图片,Drawable常常被作为一个View的背景,一般分XML和图片两种,可以用代码来实现,不过这种方式比较复杂。Drawable作为一个抽象类,他衍生出了很多的子类,譬如ShapeDrawable、BitmapDrawable等。Object->Drawable->BitmapDrawable、ShapeDrawable等。
Drawable的内部宽高参数很重要,通过getIntrinsicWidth和getIntrinsicHeight这两个方法可以获取,但并不是所有的Drawable都有内部宽高,比如一张图片所形成的Drawable,为图片的高宽,但是如果你是颜色所形成的,那就自然是没有的。而且要注意的是,Drawable内部宽高不等于它的大小,与View一样大。
(二)Drawable的分类
Drawable的种类繁多,比如我们之前常用的ShapeDrawable,BitmapDrawable,LayerDrawable,StateListDrawable等,使用如下:
2.1.BitmapDrawable
表示一张图片,可直接引用原始图片,也可以通过XML的形式来描述(可展示更多效果)。
2.1.1.代码示例及相关说明

<?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">
<!--1.android:src 资源ID,可以是图片也可以是颜色  2.android:antialias 是否开启图片抗锯齿,开启后图片会变得平滑一点,应当开启-->
<!--3.android:dither 是否开启抖动效果,当图片的像素配置和手机不一致,可让高质量的图片在低分辨率的屏幕上保持好的显示效果,应当开启-->
<!--4.android:filter 是否开启过滤效果,当图片尺寸被拉伸或者压缩时,开启过滤效果会保持比较好的显示效果,所以这个也可开启-->
<!--5.android:gravity 图片小于容器尺寸,可对图片定位,位置方向,上下左右,可以用“|”符号来实现不同选项叠加。-->
<!--6.android:mipMap 纹理映射,比较抽象,默认为false-->
<!--7.android:tileMode 下面细讲-->
</bitmap>

2.1.2. android:gravity和android:tileMode
android:gravity: 图片小于容器尺寸,可对图片定位,位置方向,上下左右,可以用“|”符号来实现不同选项叠加。举例属性如下所示:

                                 
android:tileMode: 平铺模式,分为disabled | clamp | repeat | mirror 其中disabled 是关闭平铺模式,这个也是默认值,开启后,gravity属性会无效,先说下其余三个属性的区别,三种都表示平铺模式:1.repeat表示简单的水平和竖直方向上平铺效果;2.mirror表示一种在水平和竖直方向上的镜面投影效果;3.clamp表示四周像素扩散效果。

             

2.1.3.NinePatchDrawable
自动根据宽/高进行相应的缩放并保证不失真。
2.2.ShapeDrawable
常见Drawable,可以理解为通过颜色构造的图片,它既可以是纯色的图形,也可以具有渐变的图形。
2.2.1.几个属性
1.android:shape 表示图片的形状,有四个选项,line(横线),oval(椭圆),rectangle(矩形),ring(圆环),他的默认值是矩形,而且line(横线)和ring(圆环)都必须通过stroke标签来指定宽高,颜色等信息,否则无法达到预期的效果。
2.size标签:android:width/android:height分别表示的是shape的宽高,是shape的固有大小,但不是最终大小。总的来说,<size>标签设置的宽高就是ShapeDrawable的固有宽/高,但作为View的背景时,并没有宽高这个概念,会适应(拉伸/缩小)view的宽高。
2.2.2.代码示例

<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="line">
    <!--corners表示shape的四个角度,它只是用于矩形的shape,这里的角度是指圆角的程度,用px来表示,他有五个属性:
      android:radius设置同样角度,优先级低,其他四个属性分别为最上角、最右角、最下角、最左角的角度 -->
    <corners
        android:bottomLeftRadius="10dp"
        android:bottomRightRadius="10dp"
        android:radius="10dp"
        android:topLeftRadius="10dp"
        android:topRightRadius="10dp" />
    <!--gradient与solid标签是互相排斥的,因为solid表示纯色,而他表示渐变,他的属性如下,各种骚渐变-->
    <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表示空白,但是他表示的不是shape的空白,而是包含他view的空白,而且有四个属性,左上右下-->
    <padding
        android:bottom="10dp"
        android:left="10dp"
        android:right="10dp"
        android:top="10dp" />
    <!--size:width/height分别表示的是shape的宽高,对于shape来说并没有宽高这个概念,作为view的背景他会适应view的宽高-->
    <size
        android:width="100dp"
        android:height="100dp" />
    <!--solid标签表示纯色填充,通过android:color来表示填充颜色-->
    <solid android:color="@color/colorAccent" />
    <!--shape的描边,分别表示宽度、颜色、虚线线段的间隔、虚线线段的宽度,注意:如果android:dashGap和android:dashwidth有任何一个为0的话,那么虚线就不能生效了-->
    <stroke
        android:width="100dp"
        android:color="@color/colorPrimaryDark"
        android:dashGap="@dimen/activity_horizontal_margin"
        android:dashWidth="10dp" />
</shape>

2.3.LayerDrawable
LayerDrawable对应的xml是< layer-list>,他表示一种层次化的Drwable集合,可以理解为图层,通过不同的view达到叠加的效果。
2.3.1.相关说明
一个layer-list包含多个item,每个item是一个drawable,形成叠加的图层效果。常用属性包括android:bottom、android:left、android:right、android:top,分别表示Drawable相对于View上下左右的偏移量,单位是像素。android:drawable可以直接饮用Drawable资源,默认情况,所有Drawable会缩放至View的大小。
2.3.2微信中的文本输入框效果代码

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

2.4.StateListDrawable
StateListDrawable对应的是< selector>标签,也表示Drawable的集合,每个Drawable对应着View的一种状态,系统会根据view的状态来选择出现的drawable。主要用来设置可单击的View的背景,最常见的是Button。
2.4.1语法如下

<?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">
    <!--1.android:constantSize固有大小是否不随着状态的改变而改变的,false是随着状态的改变而改变,一般默认为false-->
    <!--2.android:dither 是否开启抖动,在低质量屏幕上依然有好的效果。-->
    <!--3.android:variablePadding padding是否随状态的改变而改变,一般为false-->
    <item
        android:drawable="@drawable/bitmapdrawable"
        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" />
    <!--4.item标签标示一个具体的Drawable,android:drawable表示一个具体的id。-->
    <!--5.android:state_pressed表示按下没有松开;android:state_hovere表示View已经获取了焦点;android:state_selected表示用户选择了View-->
    <!--6.android:state_checked用户选中View,一般用于CheackBox,android:state_enabled表示View处于可用状态-->
</selector>

2.4.2.具体事例
根据View的状态从selector中选择对应的item,每一个对应着一个drawable,系统会按照从上到下来查找。还得要有默认的Item。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item 
        android:drawable="@drawable/button_pressed" 
        android:state_pressed="true" />
    <item 
        android:drawable="@drawable/button_focused" 
        android:state_focused="true" />
    <item 
        android:drawable="@drawable/button_normal" />
</selector>
2.5.LevelListDrawable
LevelListDrawable对应着<level-list>标签,同样表示一个drawable的集合。集合中的每一个Drawable都有一个等级level,根据不同的等级切换不同的Item,每个item是一个drawable。
代码示例:当它作为View的背景时,可以通过Drawable的setLevel设置不同的等级从而切换不同的Drawable;当它作为ImageView的前景Drawable时,可以通过ImageView的setImageLevel来切换Drawable。等级有范围: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/colorAccent"
        android:maxLevel="0" />
     <item
        android:drawable="@color/colorPrimaryDark"
        android:maxLevel="1" />
</level-list>

2.5.LevelListDrawable
LevelListDrawable对应着<level-list>标签,同样表示一个drawable的集合。集合中的每一个Drawable都有一个等级level,根据不同的等级切换不同的Item,每个item是一个drawable。
代码示例:当它作为View的背景时,可以通过Drawable的setLevel设置不同的等级从而切换不同的Drawable;当它作为ImageView的前景Drawable时,可以通过ImageView的setImageLevel来切换Drawable。等级有范围: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/colorAccent"
        android:maxLevel="0" />
     <item
        android:drawable="@color/colorPrimaryDark"
        android:maxLevel="1" />
</level-list>

2.6.TransitionDrawable
对应于<transition>标签,实现两个Drawable的淡入淡出效果。
代码示例:

步骤一:新建TransitionDrawable并定义

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

        步骤二:在组件中引用Drawable

    <TextView
        android:id = "@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:background="@drawable/transitiondrawable"
        />

          步骤三:在Activity通过startTransition和resetTransition来操作。实现渐变效果。

     tv = (TextView) findViewById(R.id.tv);
     TransitionDrawable drawable = (TransitionDrawable) tv.getBackground();
     drawable.startTransition(1000);

2.7.InsetDrawable
InsetDrawable对应<inset>标签,可以将其他的Drawable内嵌到自己当中,并且可以在四周留下一定的距离,当一个View希望自己背景比实际区域小的时候就可以用InsetDrawable实现,当然通过LayerDrawable也可以实现该效果。
代码示例:

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

2.8.ScaleDrawable
ScaleDrawable对应的是<scale>标签,可以指定自己的等级(level)将指定的Drawable缩放到一定的比例。
ScaleDrawable的等级,0为不可见,要想可见,需要不为0,这个在源码中可以看出来,我们看他的draw方法,根据onBoundsChanges源码的方法,ScaleDrawable的级别越大,那么内部的Drawable就看起来越大了,XML中定义的缩放比例越大,那么内部的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="70%"
    android:scaleWidth="70%">
</scale>

    光设置drawable的XML不够,还需要将ScaleDrawable的等级设置为大于0小于10000(不要出了这个范围)的值。如下所示:

tv = (TextView) findViewById(R.id.tv);
ScaleDrawable testdrawable = (ScaleDrawable) tv.getBackground();
testdrawable.setLevel(1);

2.9.ClipDrawable
ClipDrawable对应的是<clip>标签,它可以根据自己的等级裁剪另一个Drawable,裁剪的方法可以通过android:clipOrientation(裁剪方向:水平竖直)和android:gravity(通过|组合使用)共同控制。

步骤一:新建ClipDrawable的XML。

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

     步骤二:可以设置ImageView,也可以设置为View的背景。TextView不行。

  <ImageView
        android:id = "@+id/iv"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:text="Hello World!"
        android:src="@drawable/clipdrawable"
        />

     步骤三:在代码中设置ClipDrawable的等级。

        iv= (ImageView) findViewById(R.id.iv);
        ClipDrawable cp = (ClipDrawable) iv.getDrawable();
        cp.setLevel(2000);

       需要说明的是:由于属性是bottom,因而从上往下进行裁剪的。等级为2000表示裁减了8000,在顶部裁减了80%的区域。      

           
(二)自定义Drawable
Drawable的使用范围很单一,一个是作为ImageView中的图像来显示,还有就是作为View的背景,核心就是draw方法。大多数情况下Drawable是以View的背景形式出现的,可以通过重写Drawable的draw方法来绘制自定义Drwable。通常没有必要去自定义Drawable,在xml中无法使用。
场景:自定义Drawable来绘制一个圆形的Drawable,半径会随着View的变化而变化,可以作为View的通用背景。
代码示例:

public class CustomDrawable extends Drawable {
    private Paint paint;
//    参考了ShapeDrawable和BitmapDrawable的源码(源码的重要性)
    //主要方法,和View的draw方法相似。在画布上画圆。
    @Override
    public void draw(@NonNull Canvas canvas) {
        final Rect r = getBounds();
        float cx = r.exactCenterX();
        float cy = r.exactCenterY();
        canvas.drawCircle(cx, cy, Math.min(cx, cy), paint);
    }

    @Override
    public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
        paint.setAlpha(alpha);
        invalidateSelf();
    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
        paint.setColorFilter(colorFilter);
        invalidateSelf();
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }
}