Android中的Drawable

来源:互联网 发布:算法笔记 pdf 编辑:程序博客网 时间:2024/05/16 07:56

Android的Drawable

Drawable表示的是一种可以在Canvas上进行绘制的抽象的概念。它的种类繁多,使用简单,比自定义View使用的成本低。而且非图片类的Drawable占用空间小,这对APK的瘦身也有作用。

Drawable简介

在Android的设计中,Drawable是一个抽象类,它是所有Drawable对象的,它是所有Drawable对象的基类,每个具体的Drawable都是它的子类,比如ShapeDrawable、BitmapDrawable等。

Drawable中内部宽高这两个参数比较重要,通过getIntrinsicWidthgetIntrinsicHeight这两个方法可以获取到它们。但并不是所有的Drawable都有宽高。比如图片形成的Drawable,它的宽高就是图片的宽高。但是一个颜色形成的Drawable,它内部就没有宽高的概念。另外要注意的是Drawable的内部宽高并不等同于它的大小,一般来说,Drawable是没有大小的概念的,当用作View背景时会被拉伸至View的同等大小。

Drawable的分类

BitmapDrawable

最简单的Drawable,它表示的就是一张图片。直接引用原始的图片即可,也可以通过XML的方式来描述它。

<TextView            android:id="@+id/tv_gravity"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_marginTop="4dp"            android:text="@string/open"            android:textSize="22sp"            android:textColor="@android:color/black"            android:drawableRight="@drawable/btn_card_history_nornaml"            android:antialias= "true"            android:dither="true"            android:filter="true"            android:mipMap="true"            android:tileMode="disabled"            />

下面是它各个属性的意义:

android:src
图片资源id

android:antialias
是否开启图片抗锯齿功能。开启后会让图片变得平滑,同事也会在一定程度上降低图片的清晰度,到那时这个降低的幅度可以忽略。

android:dither
是否开启抖动效果。当图片的像素配置和手机屏幕的像素配置不一致时,开启这个选项可以让高质量的图片在低质量的屏幕上能保持较好的效果。

android:filter
是否开启过滤效果,当图片的尺寸被拉伸或者压缩时,开启过滤效果可以保持较好的显示效果。

android:gravity
当图片小于容器的尺寸时,设置此选项可以对图片进行定位。

android:mipMap
这是一种图像相关的处理技术,也叫纹理映射。默认值为false,日常开发中不常用。

android:tileMode
平铺模式(类似于铺地板砖)。这个选项有如下几个值:

["disabled"|"clamp"|"repeat"|"mirror"]

其中disabled表示关闭平铺模式,也就是默认值,当开启平铺模式后,gravity属性就会被忽略。repeat表示的是简单的水平和竖直方向上的平铺效果;mirror表示一种在水平和竖直方向上的镜面投影效果;而clamp表示的效果是图片的四周的像素会扩展到周围区域。

NinePatchDrawable

它表示一张.9格式的图片,.9图片可以自动根据所需的宽高进行相应的缩放并保证不失真。另外在bitmap标签中也可以使用.9图,即BitmapDrawable也可以代表一个.9图。

在Android Studio开发的时候,.9图必须要放在Drawable目录下,不能放置到mipmap目录下。同时,在Android Studio中.9图必须要四个边都至少有一个点(可伸缩点)或线。这和在Eclipse中是不同的,在Eclipse中,.9图可以只有两边(非对称的两边)有可伸缩点或者线。

ShapeDrawable

通过颜色来构造的图形,既可以是纯色的图形,也可以是具有渐变效果的图形。下面说明一下各个标签的含义:

android:shape
作为根标签,表示图形的形状,有四个属性: rectangle(矩形)、 oval(椭圆)、line(横线)、ring(圆环)。注意:linering必须要通过stroke标签来置顶线的宽度和颜色等信息。

针对ring,有5个特殊的属性:
android:innerRadius
圆环的内半径(优先级高于innerRadiusRatio);

android:thickness
圆环的厚度(优先级高于thicknessRatio);

android:innerRadiusRatio
内半径占整个Drawable宽度的比例,默认值为9(如果为n,那么内半径=宽度/n);

android:thicknessRatio
厚度占整个Drawable宽度的比例,默认为3(如果为n,那么厚度=宽度/n);

android:useLevel
默认为false,如果为true有可能无法达到预期的效果,除非被当做LevelListDrawable来使用;

corners
表示shape的四个角的角度,只在矩形中,单位是px。它有5个属性:
android:radius
为四个角同时设定相同的角度,优先级最低。

android:topLeftRadius
左上角的角度;

android:topRightRadius
右上角的角度;

android:bottomLeftRadius
左下角的角度;

android:bottomRightRadius
右下角的角度;

gradient
它与solid标签是相互排斥的,其中solid表示纯色填充,而gradient则表示渐变效果。gradient有如下属性:
android:angle
渐变的角度,默认为0,其值必须为45的倍数,0表示从左到右,90表示从下到上;

android:centerX
渐变的中心点的横坐标;

android:centerY
渐变的中心店的纵坐标;

android:startColor
渐变的起始色;

android:centerColor
渐变的中间色;

android:endColor
渐变的结束色;

android:gradientRadius
渐变半径,仅当android:type=“radial”时有效;

android:useLevel
一般为false,当Drawable作为StateListDrawable使用时为true;

android:type
渐变的类别,有linear(线性渐变)、radial(径向渐变)、sweep(扫描线渐变)三种,默认为现行渐变;

solid
这个标签表示纯色填充,通过android:color即可指定shape中填充的颜色。

stroke
Shape的描边,有如下几个属性:
android:with
描边的宽度;

android:color
描边的颜色;

android:dashWidth
组成虚线的线段宽度(如果为0,虚线失效);

android:dashGap
组成虚线的线段之间的间隔(如果为0,虚线失效);

padding
表示空白,表示的不是shape的空白,而是包含它的View的空白,有四个属性:
android:left 左;
android:top 上;
android:right 右;
android:bottom下;

size
shape的大小,有两个属性:
android:width 宽;
android:height 高;
但是不代表shape的最终大小,作为View的背景会自适应View的宽高。而在默认情况下,它是没有固定的宽高的,通过getIntrinsicWidth和getIntrinsicHeight方法返回的都会是-1,但是如果通过size来指定宽高信息,那么这个时候的shape就有了固有宽高。

LayerDrawable

LayerDrawable对应的XML标签是layer-list,它表示的是一种层次化的Drawable集合,通过将不同的Drawable放置在不同的层上面从而达到一种叠加的效果。

<?xml version="1.0" encoding="utf-8"?><layer-list xmlns:android="http://schemas.android.com/apk/res/android"    android:shape="oval">  <item      android:drawable="@[package:]drawable/ic_media_play"      android:id="@[+][package:]id/accessibilityActionContextClick"      android:top="@dimen/activity_horizontal_margin"      >    <shape        android:tintMode="add"        android:shape="rectangle"        >      <solid android:color="@color/cardview_dark_background"></solid>    </shape>  </item>  <item>    <shape        android:tintMode="add"        android:shape="rectangle"        >      <solid android:color="@color/cardview_dark_background"></solid>    </shape>  </item></layer-list>

一个layer-list可以包含多个item,每个item表示一个Drawable。Item的属性主要有android:topandroid:left等,分别表示Drawable对于View的上下左右的偏移量,单位是像素。

我们可以通过android:drawable属性来引用一个已有Drawable资源,也可以自定义一个Drawable(如果引用了Drawable,那么shape属性将失效)。

layer-list中所有的Drawable都会被缩放至View的大小,对于bitmap来说,需要使用android:gravity属性才能控制图片的显示效果。

layer-list有层次的概念,下面的item会覆盖上面的item,通过合理的分层,可以实现一些特殊的叠加效果。

StateListDrawable

StateListDrawable对应selector 标签,它也表示Drawable集合,每个Drawable都对应着View的一种状态,这样系统就会根据View的状态来选择合适的Drawable。

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

android:constantSize
StateListDrawable的固有大小是否不随着其状态的改变而改变(true/false默认为false),true表示StateListDrawable的固有大小保持不变,false则会随着状态的改变而改变。

android:dither
是否开启抖动效果(true/false默认为true),开启此选项可以让图片在低质量的屏幕上仍然获得较好的显示效果。

android:variablePadding
表示是否随着其状态的改变而改变(true/false默认为false),true表示随着状态的改变而改变,false表示StateListDrawable的padding是内部所有Drawable的padding的最大值。

item
item标签表示具体的Drawable,其中android:drawable是一个Drawable的资源id,剩下的属性表示View的各种状态:
android:state_pressed
表示按下状态,比如Button被按下仍然没有松开;

android:state_focused
表示View已经获得焦点;

android:state_selected
表示用户选择了View;

android:state_checked
表示用户选中了View,一般适用于CheckBox这类在选中和非选中状态之间进行切换的View;

android:state_enabled
表示View当前处于可用状态;

系统会根据View的当前状态从selector中选择对应的item。系统按照从上到下的顺序查找,直至查找到第一条匹配的item。一般来说,默认的item都应该放在selector的最后一条,并且不附带任何的状态,这样当上面的item都无法匹配View的当前状态时,系统就会选择默认的item。

LevelListDrawable

对应level-list标签,同样表示一个Drawable集合,集合中每个Drawable都有一个等级的概念,根据不同的等级,LevelListDrawable会切换为对应的Drawable

<?xml version="1.0" encoding="utf-8"?><level-list xmlns:android="http://schemas.android.com/apk/res/android">    <item        android:drawable="@android:drawable/dark_header"        android:maxLevel="10"        android:minLevel="2"        ></item></level-list>

在LevelListDrawable中每个item表示一个Drawable,由maxLevel和minLevel来指定对应的等级范围。如果作为View的背景时,可以通过Drawable的setLevel方法来设置不同的等级从而切换具体的Drawable,如果作为ImageView的前景Drawable,还可以用setImageLevel方法来切换Drawable。

Drawable的等级范围是0到10000,默认为0。

TransitionDrawable

对应的是transition标签,用于实现两个Drawable之间的淡入淡出效果。

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

将这个Drawable设置为View的背景,通过startTransition和reverseTransition方法来实现淡入淡出的效果以及它的逆过程。

TextView cancel = (TextView) findViewById(R.id.tv_cancel);        TransitionDrawable background = (TransitionDrawable) cancel.getBackground();        background.startTransition(1000);

InsetDrawable

对应inset标签,它可以将其他Drawable内嵌到自己当中,并且可以给四周留出一定的间距。当一个View希望背景比自己的实际区域小的时候,可以采用InsetDrawable来实现。

<?xml version="1.0" encoding="utf-8"?><inset xmlns:android="http://schemas.android.com/apk/res/android"    android:insetTop="12dp"    android:insetLeft="12dp"    android:insetRight="12dp"    android:insetBottom="12dp"    >    <shape        android:shape="rectangle"        >        <solid android:color="@color/cardview_light_background">        </solid>    </shape></inset>

其中android:insetTop、android:insetLeft等属性是Drawable在View中内凹的大小。

ScaleDrawable

对应scale标签,它可以根据自己的等级将指定的Drawable缩放到一定比例。

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

其中scaleGravity等同于Drawable中的gravity属性,ScaleDrawable除了需要上述的属性之外还需要设定等级,在源码中如果ScaleDrawable的等级为0,那么就显示不出来。所以必须要给ScaleDrawable设置等级在0到10000之间。(10000是源码中的值,虽然可以设置的更高,但是不建议这么做)

ClipDrawable

对应clip标签,它可以根据自己当前的等级来裁剪另一个Drawable,裁剪方向可以通过android:clipOrientationandroid:gravity这两个属性来控制。

android:clipOrientation
表示裁剪的方向,有水平和垂直两个方向。

android:gravity
这个属性比较复杂,需要同android:clipOrientation属性一同使用才有效果,属性值不同效果不同:
top:将内部的Drawable放在容器的顶部,不改变大小,如果为垂直裁剪,那么从底部开始裁剪。

bottom:将内部的Drawable放在容器的底部,不改变大小,如果为垂直裁剪,那么从顶部开始裁剪。

Left:将内部的Drawable放在容器的左边,不改变大小,如果为水平裁剪,那么从右侧开始裁剪(默认)。

right:将内部的Drawable放在容器的右边,不改变大小,如果为水平裁剪,那么从左侧开始裁剪。

center_vertical:使内部的Drawable在容器中垂直居中,不改变它的大小,如果为垂直裁剪,那么从上下同时开始裁剪。

fill_vertical:使内部的Drawable在垂直方向上填充容器,如果为垂直裁剪,那么仅当ClipDrawable的等级为0(0表示ClipDrawable被完全裁剪,不可见)时,才能有裁剪行为。

center_horizontal:使内部的Drawable在容器中水平居中,不改变它的大小,如果为水平裁剪,那么从左右两边同时开始裁剪。

fill_horizontal:使内部的Drawable在水平方向上填充容器,如果为水平裁剪,那么仅当ClipDrawable的等级为0(0表示ClipDrawable被完全裁剪,不可见)时,才能有裁剪行为。

center:使内部的Drawable在容器中水平和竖直方向都居中,不改变它的大小。如果为竖直裁剪,那么从上下同时开始裁剪,如果为水平裁剪,那么从左右同时裁剪。

fill:使内部的Drawable在水平和竖直方向都填充容器。仅当ClipDrawable的等级为0时,才能有裁剪行为。

自定义Drawable

public class MyDrawable extends Drawable {    private Paint mPaint;    public MyDrawable(int color) {        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaint.setColor(color);    }    @Override    public void draw(Canvas canvas) {        Rect bounds = getBounds();        float centerX = bounds.exactCenterX();        float centerY = bounds.exactCenterY();        canvas.drawCircle(centerX,centerY,Math.min(centerX,centerY),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需要重写draw、setAlpha、setColorFilter和getOpacity方法,其中draw是最重要的方法。另外,getIntrinsicWidth和getIntrinsicHeight方法需要注意下,当自定义的Drawable有固定大小时最好重写这两个方法,因为它会影响到View的wrap_content布局。如果没有重写这两个方法,这个时候它的内部大小为-1。需要注意的是,内部大小并不一定是Drawable的实际大小,实际区域大小还是需要通过它的getBounds方法来得到,一般来说它和View的尺寸相同。

0 0