Android Drawable简单总结

前言

最近做项目的时候用到了PrgressBar的进度展示图片,可以使用progressDrawable属性配置它的进度图片,配置XML写了好几遍都没有达到预期效果,Drawable用起来非常简单,实践中还是会遇到不少坑,这里来总结下平时经常使用到的Drawable对象。

ShapeDrawable

ShapeDrawable可以用来定一个基本的几何图形,比如android:shape=[“rectangle” | “oval” | “line” | “ring”]分别代表矩形、椭圆、线段和环形;除了基本形状外还可以在内部定义size 、padding、solid、corners、stroke和gradient几个子标签,如果内部定义了gradient标签,那么实际上生成的是GradientDrawable对象。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <stroke android:color="@color/colorAccent" android:width="1dp" />
</shape>

LayerDrawable

LayerDrawable可以将内部的Drawable对象一层一层地叠加起来,它的实现非常类似于FrameLayout,就是后加入的Drawable展示之前加入Drawable上方,而且内部的每个Drawable都可以在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="@color/colorAccent" />
        </shape>
    </item>

    <item android:gravity="top|center_horizontal"
        android:bottom="2dp"
        android:left="2dp"
        android:right="2dp">
        <shape android:shape="rectangle">
            <solid android:color="#ffffff" />
        </shape>
    </item>

    <item android:gravity="top"
        android:bottom="6dp">
        <shape android:shape="rectangle">
            <solid android:color="#ffffff" />
        </shape>
    </item>
</layer-list>

上面的例子有三个图层,第一层是全部带颜色的色块,第二层则是左右下三个位置都留有2dp间隙的白色色块,这样就实现了一个带左右下三个边框的背景,最后再加一个只有底部留6dp间隙的白色色块,最终就形成了一个常见的TextView背景图。

StateListDrawable

selector标签内部可以包含多个Drawable对象,每个Drawable对象都对应着某种状态,最常见的就是用户按下和松开的按钮背景变色效果,其中按下对应的是state_pressed。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/border_bg" android:state_pressed="true" />
    <item android:drawable="@drawable/rectangle_bg" />
</selector>

// rectangle_bg
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@color/colorAccent" />
</shape>

// border_bg
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <stroke android:color="@color/colorAccent" android:width="1dp" />
</shape>

下面列举一下常用的状态值,开发者可以在Selector中定义各种状态对应展示的Drawable,系统会根据View所处的状态自动切换展示。

状态 意义
enable 是否处于可用状态,比如Button在提交数据未返回时可以设置为disabled,防止用户重复提交,通过setEnable设置
focused 是否处于聚焦状态,通常都是通过硬件上下左右键等才会触发,一般用户不用关心
pressed 是否处于按下状态,通常都是用户在触摸屏上按下可点击View才会触发
selected 是否处于选中状态,这个通常都是开发者通过setSelect这个方法来实现逻辑选中
checked 是否处于签中状态,常用的CheckBox会有这种状态,也就是有没有被勾选

InsetDrawable

inset标签可以为Drawable添加边缘补白,通常为了某些View之间添加间隔需要增加View树的深度,使用InsetDrawable之后会自动在Drawable内部增加间隔,减少了View树深度,提高显示效率。

<inset xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/moon"
    android:insetLeft="10dp"
    android:insetRight="10dp"
    android:insetTop="10dp"
    android:insetBottom="10dp">

</inset>

TransitionDrawable

transition标签能够很自然地切换两个不同的Drawable背景,提升用户体验,查看它内部的实现源码会发现其实是利用了Drawable的setAlpha接口,通过不断的改变第一个图片的alpha值到0同时增加第二个图片的alpha值到255,实现了无缝的背景切换效果。

<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/moon" />
    <item android:drawable="@drawable/pool" />
</transition>

TransitionDrawable transitionDrawable = (TransitionDrawable) transiteImg.getDrawable();
transitionDrawable.startTransition(300);

LevelDrawable

level-list标签能够定义不同level等级下展示的不同图片,下面的里子就是根据等级不同展示白天或者晚上的图片,用户可以不断的修改level值,View内部会自动根据当前设置的level修改展示的图片。

<level-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/moon" android:minLevel="0" android:maxLevel="50" />
    <item android:drawable="@drawable/pool" android:minLevel="51" android:maxLevel="100" />
</level-list>

ClipDrawable

clip标签能够通过level等级决定展示的图片大小,其他的部分都被剪切掉了,它有两个重要的参数android:clipOrientation和android:gravity,对它们设置不同的值就能够实现不同的剪切位置和剪切方式。查看其内部实现源码会发现其实就是在Drawable的onDraw的时候调用了Canvas.clipPath方法剪切掉其他部分。

<clip xmlns:android="http://schemas.android.com/apk/res/android"
    android:clipOrientation="horizontal"
    android:gravity="left">
    <shape android:shape="rectangle">
        <gradient
            android:startColor="#00ffff"
            android:endColor="#ff517e" />
        <corners android:radius="5dp" />
    </shape>
</clip>

clipImg.setImageLevel(level);

ScaleDrawable

scale标签能够缩放包裹在内部的Drawable对象,它也需要根据图片level等级值调整内部Drawable的缩放大小。在使用过程感觉这个缩放功能的特别费解,图片要么就是根本不展示,要么就是展示了根本不会改变大小。

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:scaleWidth="100%">
    <shape
        android:shape="rectangle">
        <gradient
            android:startColor="#00ff00"
            android:endColor="#ff517e" />
        <corners android:radius="5dp" />
    </shape>
</scale>

// 先初始化
scaleImg.getDrawable().setLevel(10000);

// 再设置具体值
scaleImg.getDrawable().setLevel(level);

查看它的onDraw方法会发现它在图片的level等级为0是根本不会做绘制操作,所以即使配置正确了scale标签还需要在代码里为它设置初始的图片level等级。还有就是如果即设置了android:scaleWidth又设置了android:scaleHeight发现即使改变level等级值也不会缩放,这时因为一旦宽高都设置了就会把宽度和高度都随着level变化,然而上面要缩放的Drawable高度太小减去了缩放值之后变成了负数或零,最后一句因为要求宽高都是正数就导致没有实现缩放效果,因而在缩放Drawable时要避免出现计算结果为负值的情况。

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

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

    // 根据level计算应该展示的宽度
    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);
    }

    // 根据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);
    }
}

猜你喜欢

转载自blog.csdn.net/xingzhong128/article/details/80138112