关于 Android Drawable Resource学习

关于 Android Drawable Resource学习


声明本文参照官方文档Drawable Resource

Drawable是所有图像类的基类,Android中定义了许多XXDrawable,这给开发带来了极大的方便,许多效果可以直接使用drawable来处理,而无需自己定义view。

首先看一下Drawable这个类的层次关系,如下图:

Drawable类继承关系

主要学习常见的Drawable:

1.ShapeDrawable <shape />
2.BitmapDrawable <bitmap />
3.ColorDrawable <color />
4.ClipDrawable <clip />
5.InsetDrawable <inset />
6.ScaleDrawable <scale />
7.RoateDrawable <roate />
8.LevelListDrawable <level-list />
9.AnimaitonDrawable <animation-list />
10.StateListDrawable <selector />
11.LayerDrawable <layer-list />
12.TransitionDrawable <transition />
13.RippleDrawable <ripple />

<h2 id="6">ScaleDrawable</h2>


ScaleDrawable对应的标签是 <scale/>,可以通过设置它的level将指定大小的drawable缩放

概述


这个标签对应的语法(syntax)如下

<?xml version="1.0" encoding="utf-8"?>
<scale
    xmlns:android="http://schemas.android.com/apk/res/android"
    //xmlns:android 这个是定义XML命名空间的,是必须的,且值必须为这个

    android:drawable="@drawable/drawable_resource"
    //android:drawable 这个是来引用一个drawable资源的,是必须的

    android:scaleGravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
                          "fill_vertical" | "center_horizontal" | "fill_horizontal" |
                          "center" | "fill" | "clip_vertical" | "clip_horizontal"]
    //android:scaleGravity 关键字。指定缩放后的gravity的位置。必须是上面可选值中的一个或多个(多个用‘|’分隔)。

    android:scaleHeight="percentage"
    // android:scaleHeight  缩放的高度,以百分比来表示drawable的缩放,比如50%

    android:scaleWidth="percentage"
    // android:scaleWidth  缩放的宽度,以百分比来表示drawable的缩放,比如50%
 />

上面这个标签的语法中的每个属性又是在哪定义的呢?是不是也像我们自己定义一个控件属性类似呢?

那么我们找到这些属性定义:在R.styleable.ScaleDrawable 这个文件中(在Android源码中位置/framework/base/core/res/res/values/attrs.xml ),很显然也是一个自定义控件而已,只不过是系统定义好的,重复的就不在赘述了,如下:

PS 你可以在线查看系统源码

<declare-styleable name="ScaleDrawable">

    <!-- Scale width, expressed as a percentage of the drawable's bound. The value's
         format is XX%. For instance: 100%, 12.5%, etc.-->
    <attr name="scaleWidth" format="string" />

    <!-- Scale height, expressed as a percentage of the drawable's bound. The value's
         format is XX%. For instance: 100%, 12.5%, etc.-->

    <attr name="scaleHeight" format="string" />
    <!-- Specifies where the drawable is positioned after scaling. The default value is
         left. -->

    <attr name="scaleGravity">
        <!-- Push object to the top of its container, not changing its size. -->
        <flag name="top" value="0x30" />
        <!-- Push object to the bottom of its container, not changing its size. -->
        <flag name="bottom" value="0x50" />
        <!-- Push object to the left of its container, not changing its size. -->
        <flag name="left" value="0x03" />
        <!-- Push object to the right of its container, not changing its size. -->
        <flag name="right" value="0x05" />
        <!-- Place object in the vertical center of its container, not changing its size. -->
        <flag name="center_vertical" value="0x10" />
        <!-- Grow the vertical size of the object if needed so it completely fills its container. -->
        <flag name="fill_vertical" value="0x70" />
        <!-- Place object in the horizontal center of its container, not changing its size. -->
        <flag name="center_horizontal" value="0x01" />
        <!-- Grow the horizontal size of the object if needed so it completely fills its container. -->
        <flag name="fill_horizontal" value="0x07" />
        <!-- Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. -->
        <flag name="center" value="0x11" />
        <!-- Grow the horizontal and vertical size of the object if needed so it completely fills its container. -->
        <flag name="fill" value="0x77" />
        <!-- Additional option that can be set to have the top and/or bottom edges of
             the child clipped to its container's bounds.
             The clip will be based on the vertical gravity: a top gravity will clip the bottom
             edge, a bottom gravity will clip the top edge, and neither will clip both edges. -->
        <flag name="clip_vertical" value="0x80" />
        <!-- Additional option that can be set to have the left and/or right edges of
             the child clipped to its container's bounds.
             The clip will be based on the horizontal gravity: a left gravity will clip the right
             edge, a right gravity will clip the left edge, and neither will clip both edges. -->
        <flag name="clip_horizontal" value="0x08" />
        <!-- Push object to the beginning of its container, not changing its size. -->
        <flag name="start" value="0x00800003" />
        <!-- Push object to the end of its container, not changing its size. -->
        <flag name="end" value="0x00800005" />
    </attr>

    <!-- Reference to a drawable resource to draw with the specified scale. -->
    <attr name="drawable" />

    <!-- Use the drawable's intrinsic width and height as minimum size values.
         Useful if the target drawable is a 9-patch or otherwise should not be scaled
         down beyond a minimum size. -->
    <attr name="useIntrinsicSizeAsMinimum" format="boolean" />
    // 这个属性类型是布尔类型,是否使用本身的大小作为最小值,默认是false
</declare-styleable>
下面重点分析一下ScaleDrawable是如何工作的
@Override
public void draw(Canvas canvas) {
    final Drawable d = getDrawable();
    if (d != null && d.getLevel() != 0) {
        d.draw(canvas);
    }
}

这个draw方法,只有level不为0才会绘制drawable。

当调用drawable.setLevel()的方法后,会回调到onLevelChange()

public final boolean setLevel(int level) {
    if (mLevel != level) {
        mLevel = level;
        return onLevelChange(level);
    }
    return false;
}

而在ScaleDrawable中重写了这个方法,到里就一目了然了。调用onBoundsChange方法后又去重绘了,这样就可以更新Drawable大小了

@Override
protected boolean onLevelChange(int level) {
    super.onLevelChange(level);
    onBoundsChange(getBounds());
    invalidateSelf();
    return true;
}

那究竟android:scaleHeight="" android:scaleWidth=""和他自身level是如何影响drawable大小的呢?

 @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);
    }
}
从方法名就可以看出这是用来真正控制缩放效果的,如何控制的呢?

final int iw = min ? d.getIntrinsicWidth() : 0; w -= (int) ((w - iw) * (MAX_LEVEL - level) * mState.mScaleWidth / MAX_LEVEL);
由于min这个属性值通常为false(默认也是false),那么iw一般为零,可以简化为
w -= (int) (w * (10000 - level) * mState.mScaleWidth / 10000);
所以如果level越大,w(drawable)就越大,当level为10000的时候是没有缩放效果的;
如果xml中的缩放比例越大,w(drawable)就越小。

例子


在drawble目录下新建一个xml文件 scale_drawable.xml

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/girl"
        android:scaleGravity="left"
    android:scaleHeight="40%"
    android:scaleWidth="40%"
 />
<!--70%表示将宽度缩小40% 即缩放后为原图 60%大小-->

然后在布局文件中引用这个drawable资源

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.smart.myapplication.DrawableActivity">

    <ImageView
        android:id="@+id/image2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scaleType="centerInside"
        android:src="@drawable/girl" />

    <ImageView
        android:id="@+id/image1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/image2"
        android:layout_marginTop="50dp"
        android:scaleType="centerInside"
        android:src="@drawable/scale_drawable" />

</RelativeLayout>

此时还需要在代码中设置ScaleDrawable的level才会有效果,level默认是0,不显示,将level设置为1即可。

 @Override
 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_drawable);
    ImageView imageView=(ImageView)findViewById(R.id.image1);
    ScaleDrawable scaleDrawable=(ScaleDrawable)imageView.getDrawable();
    scaleDrawable.setLevel(1);  
 }

Demo效果图
为了对比:第一张是原图,第二张是缩放后的效果图

ScaleDrawable效果.png

<h2 id="6">BitmapDrawable </h2>


BitmapDrawable 对应的标签是 <bitmap/>,特殊的是可以通过设置它的平铺模式来变换不同的效果

概述


SYNTAX语法结构:

<?xml version="1.0" encoding="utf-8"?>
<bitmap
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@[package:]drawable/drawable_resource"
    android:antialias=["true" | "false"]
    // 是否开启图片抗锯齿功能,一般开启,设置为true
    android:dither=["true" | "false"]
    //是否开启抖动效果,一般开启后可以让图形显示质量更好(不同分辨率的屏幕),设置为true
    android:filter=["true" | "false"]
    // 是否开启过滤效果,当图片被拉伸或压缩,开启后会显示更好的效果,建议开启
    android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
                      "fill_vertical" | "center_horizontal" | "fill_horizontal" |
                      "center" | "fill" | "clip_vertical" | "clip_horizontal"]
    android:mipMap=["true" | "false"]  
    //这个是图片的一种处理技术
    android:tileMode=["disabled" | "clamp" | "repeat" | "mirror"] />

重点解释 android:tileMode
android:tileMode=["disabled" | "clamp" | "repeat" | "mirror"]
关键字,这个属性表示图片平铺模式,如果这个属性enable,那么gravity属性会被忽略(ignore)。总共有四种属性值:

  1. disabled 表示不用这个平铺属性,也是默认值
  2. repeat 表示图片平铺的效果
  3. mirror 表示镜像投影的效果
  4. clamp 可以翻译为紧紧抓住的意思,其效果是图片四周的像素会扩展到周围区域(紧紧靠紧, 个人理解)

android:mipMap=["true" | "false"]
//这个是图片的一种处理技术,

效果图:

disable模式(也是原素材图).png

那么我们如何将这个素材图,填满整个控件的背景呢,而且还不变形,类似下面的效果


BitmapDrawable-repeat.png
BitmapDrawable-clamp.png
BitmapDrawable-mirror.png

如果上面这个mirror模式效果不够明显,那看一下使用Android logo的效果吧

BitmapDrawable-mirror2.png

以上效果实现非常简单
在drawable目录下新建一个bitmap_drawable.xml

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:antialias="true"
    android:src="@mipmap/pic_bg_01_min" //引用一个图片
    android:dither="true"
    android:filter="true"
    android:gravity="center"
    android:tileMode="clamp"> //分别修改这个模式,即可看到每一个mode的效果
</bitmap>  

在View中直接设置background引用这个bitmap_drawable.xml 即可

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/ android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.smart.myapplication.BitmapDrawableActivity">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/bitmap_drawable"
        android:scaleType="centerInside"/>
</RelativeLayout>
<!--注意要设置imageview的background属性而不是src属性-->

后记:通常会使用这个属性来平铺一个图片作为背景,可以有效防止失真
当然我们还可以直接用代码来完成上面的效果
,例如

 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.pic_bg_01_min);
 BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap);
 drawable.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
 drawable.setDither(true);
 drawable.setAntiAlias(true);
 drawable.setFilterBitmap(true);
 ImageView view = (ImageView) findViewById(R.id.image);
 view.setBackgroundDrawable(drawable);

InsetDrawable


InsetDrawable在xml中对应的标签是 <inset/>

官方这样解释InsetDrawable的应用场景:
This is used when a View needs a background that is smaller than the View's actual bounds.
当一个view所需要的背景图比他自身的实际边界要小的时候,通常用这个InsetDrawable。

概述


SYNTAX语法结构:

<?xml version="1.0" encoding="utf-8"?>
<inset
    xmlns:android="http://schemas.android.com/apk/res/android"
    //  必须的,命名空间,且值必须为这个,不多解释
    android:drawable="@drawable/drawable_resource"
    // 必须的,指定引用的drawable资源
    android:insetTop="dimension"
    android:insetRight="dimension"
    android:insetBottom="dimension"
    android:insetLeft="dimension" />
    // 上面这四个属性值类型是dimension,即表示dimension值或者引用那种@dimen
    // android:insetLeft表示的是drawable距离左边的距离,同理其他几个类似

说到这你可能还不造是什么效果呢?OK,来看个实际问题吧

效果图.png

这个效果是这样的,ListView的点击效果充满整个宽度,而分割线却距离两边都有一个距离,显然不能单纯的使用默认divider设置一个分割线,这个时候该InsetDrawable该登场了!

在drawable目录下定义一个inset_listview_divider.xml文件

<inset xmlns:android="http://schemas.android.com/apk/res/android"
    android:insetLeft="@dimen/theme_padding_17dp"
    android:insetRight="@dimen/theme_padding_17dp" >
    <shape android:shape="rectangle" >
        <solid android:color="@color/info_item_color" />
    </shape>
</inset>    

然后在listvew中引用这个drawable即可

     <ListView
        android:id="@+id/city_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:cacheColorHint="@android:color/transparent"
        android:dividerHeight="0.3dp"
        android:divider="@drawable/inset_listview_divider"
        android:scrollbars="none" />    

mark未完待续


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

推荐阅读更多精彩内容