【Android Drawable】二、BitmapDrawable、NinePatchDrawable、GradientDrawable、StateListDrawable

一、BitmapDrawable

Bitmap 是一种位图图像,Android 系统支持三种格式的位图图像,.png (preferred)(支持最好),.jpg (acceptable), .gif (discouraged)(支持最差)。

在构建应用的时候,Bitmap文件可能会被appt工具压缩自动优化为无损图像。例如,一个真彩色PNG,不需要超过256的颜色可以被转换成一个8位PNG和调色板。这将导致一个图像质量相同,但这需要更少的内存。所以要意识到,在drawable目录中图像的二进制文件在构建程序时可以改变。如果你打算读一个图像作为字节流并将它转换成一个位图,把你的图片放在在res /raw/文件夹里,在那里他们不会被优化。

属性介绍
    android:src=""
    android:alpha=""
    android:antialias=""
    android:autoMirrored=""
    android:dither=""
    android:filter=""
    android:gravity=""
    android:mipMap=""
    android:tileMode=""
    android:tileModeX=""
    android:tileModeY=""
    android:tint=""
    android:tintMode=""
  • android:antialias 抗锯齿

开启抗锯齿,图像会变的=得更平滑,但是会降低清晰度,一般开启;

  • android:dither 防抖动

让高质量的图片的比较低质量的屏幕上不失真,得到比较好的显示效果。
比如图片的色彩模式是 ARGB8888,但是手机设备的支持RGB555的色彩模式,那么开启这么就可以有效减少失真现象。
(Android中我们创建的Bitmap一般会选择ARGB888模式,ARGB每个通道各占8位,8位1个字节,一个像素4个字节,一个像素的位数总和越高,图片越逼真)

  • android:filter 过滤效果

在图片图片被拉伸或者压缩的时候开启过滤效果可以显示更加好的效果。

  • android:mipmap

纹理映射

  • android:gravity 图片位置
    可选项
  • top|bottom|left|right|center|center_vertical|center_horizontal|cneter 保持图片原来大小
  • fill_vertical|fill_horizontal|fill 拉伸图片填充容器,BitmapDrawable 作为背景时会自动拉伸为 View 宽高,所以默认的 gravity 属性值为 fill
  • clip_vertical | clip_horizontal 附加项,表示裁剪图片
  • android:tileMode 平铺模式

默认为 disabled,不为 disabled 时,gravity 属性会被忽略

  • disabled 关闭平铺模式
  • clamp 拉伸像素
  • repeat 水平和垂直方向平铺
  • mirror 镜像平铺
BitmapDrawable 内部宽高
    @Override
    public int getIntrinsicWidth() {
        return mBitmapWidth;
    }

    @Override
    public int getIntrinsicHeight() {
        return mBitmapHeight;
    }

可以看到 BitmapDrawable 返回的宽高就是 Bitmap 的宽高,Bitmap 的宽高是在 computeBitmapSize() 方法中赋值的:

private void computeBitmapSize() {
        final Bitmap bitmap = mBitmapState.mBitmap;
        if (bitmap != null) {
            mBitmapWidth = bitmap.getScaledWidth(mTargetDensity);
            mBitmapHeight = bitmap.getScaledHeight(mTargetDensity);
        } else {
            mBitmapWidth = mBitmapHeight = -1;
        }
    }

如果 mBitmapState 中的成员变量 mBitmap 不为 null 的话,把 mTargetDensity 作为参数调用 Bitmap 的 getScaledXXX 方法。
这里的 mTargetDensity 是 Drawable 的目标密度,在构造方法中会进行赋值,也可以通过 setTargetDensity 方法赋值,默认为设备的屏幕像素密度。
Bitmap 也有一个成员变量 mDensity 是自身的像素密度,在 getScaledXXX 方法中会根据 mTargetDensity / mDensity 对 Bitamp 的宽高尺寸进行缩放。

public int getScaledHeight(int targetDensity) {
        return scaleFromDensity(getHeight(), mDensity, targetDensity);
    }

    /**
     * @hide
     */
    static public int scaleFromDensity(int size, int sdensity, int tdensity) {
        if (sdensity == DENSITY_NONE || tdensity == DENSITY_NONE || sdensity == tdensity) {
            return size;
        }

        // Scale by tdensity / sdensity, rounding up.
        return ((size * tdensity) + (sdensity >> 1)) / sdensity;
    }
Bitmap 和 Drawable 的转换
  1. Bitmap --> Drawable:
    new BitampDrawable(bitmap)

二、NinePatchDrawable

.9格式的图片。BitmapDrawable 会根据 View 的大小进行拉伸,而.9图片可自动地根据所需的宽/高进行相应的缩放并保证不失真。
同样可以通过 xml 文件来描述,属性同 bitmap

三、ShapeDrawable & GradientDrawable

属性
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle|oval|line|ring"
    android:innerRadius=""
    android:innerRadiusRatio=""
    android:thickness=""
    android:thicknessRatio=""
    android:useLevel="">
    <corners android:radius=""
        android:topLeftRadius=""
        android:topRightRadius=""
        android:bottomLeftRadius=""
        android:bottomRightRadius=""/>
    <gradient
        android:type="linear|radio|sweep"
        android:startColor=""
        android:centerColor=""
        android:endColor=""
        android:centerX=""
        android:centerY=""
        android:angle=""
        android:gradientRadius=""
        android:useLevel=""/>
    <padding android:top=""
        android:bottom=""
        android:left=""
        android:right=""/>
    <size
        android:width=""
        android:height=""/>
    <solid
        android:color=""/>
    <stroke
        android:color=""
        android:width=""
        android:dashWidth=""
        android:dashGap=""/>
</shape>
  • android:shape 默认为 rectangle,line 和 ring 必须通过标签<stroke> 来指定宽度和颜色;
    当 shape 为 ring 时,有五个特殊属性:
  • android:innerRadius 圆环内半径,会覆盖 innerRadiusRatio;
  • android:innerRadiusRatio 圆环内半径所占比率,默认为9;
  • android:thickness 圆环厚度,会覆盖 thicknessRaio;
  • android:thicknessRatio 圆环厚度所占比率,默认为3;
  • android:useLevel 常为false,除非它被当做是LevelListDrawable;
  • <corners>:表示shape的四个圆角的角度,只适用于矩形。
  • <gradient>:渐变效果,与 <solid> 的纯色填充相互排斥。
  • android:type:渐变的类别。有linear(线性渐变)、radial(径向渐变)、sweep(扫描线渐变),默认为linear。
  • android:centerX:渐变的中心点的X坐标。
  • android:centerY:渐变的中心点的Y坐标。
  • android:gradientRadius :渐变半径。仅当android:type="radial"时有效。
  • android:angle:渐变角度,默认为0,必须为45的倍数,0表示从左到右,90表示从上到下,仅在 linear 时有效
  • <stroke>:描边。
  • android:width:描边的宽度。
  • android:color:描边的颜色。
  • android:dashWidth:虚线的宽度。
  • android:dashGap:虚线之间的间隔。
  • <solid> :填充纯色
  • <padding> :内边距
  • <size>:大小。其android:width和android:height分别设定shape的宽/高。注意,这个表示的是shape的固有大小,但并不是其最终大小。
用代码创建

通过 inflate 方法解析 xml 文件中的属性,也可以使用构造方法来创建 ShapeDrawable 对象:

public ShapeDrawable()
public ShapeDrawable(Shape s)
private ShapeDrawable(ShapeState state, Resources res)

ShapeDrawable 有三个构造方法,第一个没有参数,第二个传入一个 Shape 对象,最终都是调用了第三个,但是第三个构造方法是 private 修饰的,第三个方法的第一个参数是一个 ShapeState 对象,ShapeState 类继承自 Drawable 类的静态内部类 ConstantState ,ShapeState 封装了当前 Drawable 的重要属性, ShapeState是Drawable 自己的保存状态量和数据的重要对象.。在该方法中给 ShapeDrawable 的成员变量 mShapeState 赋值,并初始化 PorterDuffColorFilter 类型成员变量 mTintFilter。
但是其实 <shape> 标签定义的不是 ShapeDrawable 而是 GradientDrawable。
Drawable 类也有三个构造方法:

public GradientDrawable()
public GradientDrawable(Orientation orientation, @ColorInt int[] colors)
private GradientDrawable(@NonNull GradientState state, @Nullable Resources res)

最终都是调用了 private 的两个参数的构造方法,第一个参数是 ConstantState 的子类 GradientState,第二个参数还是 Resources 对象。
GradientState 的构造方法有两个:

public GradientState(Orientation orientation, int[] gradientColors) {
            mOrientation = orientation;
            setGradientColors(gradientColors);
 }
public GradientState(@NonNull GradientState orig, @Nullable Resources res)

也就是说,在创建 GradientState 时,必须要确定渐变的方向和渐变颜色:
Orientation 是一个内部枚举类:

public enum Orientation {
        TOP_BOTTOM, //从上到下 angle = 90
        TR_BL,//从右上到左下   angle = 135
        RIGHT_LEFT,//从右向左 angle = 180
        BR_TL,//从右下到左上  angle = 225
        BOTTOM_TOP,//从下到上 angle = 270
        BL_TR,//从左下到右上  angle = 315
        LEFT_RIGHT,//从左到右   angle = 0
        TL_BR,//从左上到右下 angle = 45
    }
public void setGradientColors(@Nullable int[] colors) {
            mGradientColors = colors;
            mSolidColors = null;
            computeOpacity();
        }

<solid> 标签和 <gradient> 标签相互冲突,以及 <stroke> 标签定义的属性分别存储在以下三个成员变量中

        public ColorStateList mSolidColors; 
        public ColorStateList mStrokeColors;
        public @ColorInt int[] mGradientColors;

GradientDrawable 类对外提供了一系列方法设置相关属性:

  • <shape>
    setShape(@Shape int shape),参数为 GradientDrawable 内部定义的 int 类型常量
  • <corners>
    setCornerRadius(float radius)
    setCornerRadii(@Nullable float[] radii)
  • <gradient>
    setGradientType(@GradientType int gradient) 参数也为静态常量
    setGradientCenter(float x, float y)
    setGradientRadius(float gradientRadius)
  • <solid>
    setColor(@ColorInt int argb)
    setColor(@Nullable ColorStateList colorStateList)
  • <stroke>
    setStroke(int width, @ColorInt int color)
    setStroke(int width, ColorStateList colorStateList)
    setStroke(int width, @ColorInt int color, float dashWidth, float dashGap)
    setStroke(
    int width, ColorStateList colorStateList, float dashWidth, float dashGap)
  • <size>
    setSize(int width, int height)

四、StateListDrawable

下面罗列了 selector 的所有 state,android:drawable 属性引用drawable 资源,也可以在 <item> 标签中定义其它 drawable 。
android:contantSize 属性为 true 时,StateListDrawable 固有大小保持不变,是内部所有 Drawable 的固有大小的最大值。false 则会随着状态的改变而改变;
android:variablePadding 属性为 true 时表示 padding 随着状态改变而改变,为 false 表示 padding 为内部所有 drawable 的 padding 最大值保持不变;

<selector xmlns:android="http://schemas.android.com/apk/res/android"
    android:constantSize=""
    android:variablePadding="">
    <item android:state_accelerated=""
        android:state_active=""
        android:state_activated=""
        android:state_checkable=""
        android:state_checked=""
        android:state_drag_can_accept=""
        android:state_drag_hovered=""
        android:state_enabled=""
        android:state_first=""
        android:state_focused=""
        android:state_hovered=""
        android:state_last=""
        android:state_middle=""
        android:state_pressed=""
        android:state_selected=""
        android:state_single=""
        android:state_window_focused=""
        android:drawable="">
          <color android:color=""/>
          <bitmap android:src=""/>
          <nine-patch android:src=""/>
          <clip android:drawable="" />
          <inset android:drawable="" />
          <scale android:drawable=""/>
          <ripple android:color=""/>
          <rotate android:drawable=""/>
          <transition />
          <layer-list></layer-list>
          <level-list></level-list>
          <shape ></shape>
          <selector></selector>
         <animation-list></animation-list>
         <animated-rotate />
         <animated-selector android:fromId="" android:toId=""/>
    </item>
</selector>

StateListDrawable 类有三个构造方法:

public StateListDrawable() 
private StateListDrawable(StateListState state, Resources res)
StateListDrawable(@Nullable StateListState state)

构造方法传入了一个 StateListState 参数,这个 StateListState 不是继承自 Drawable.ConstantState 类,而是继承自 DrawableContainer.DrawableContainerState 类,它的构造方法是 StateListState(StateListState orig, StateListDrawable owner, Resources res)
构造方法内部都是创建一个 StateListState 对象,然后调用了 setConstantState(DrawableContainerState state) 方法给 成员变量 mStateListState 赋值。
StateListState 内部有一个二维数组 mStateSets 保存着所有的状态和 Drawable。给 StateListDrawable 添加 item 就是通过调用 addState 方法,state 是放在一个 int 数组里面的。

public void addState(int[] stateSet, Drawable drawable) {
        if (drawable != null) {
            mStateListState.addStateSet(stateSet, drawable);
            // in case the new state matches our current state...
            onStateChange(getState());
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容