Android Drawable 詳解

1准脂、Drawable 簡介

Drawable——可簡單理解為可繪制物邪媳,表示一些可以繪制在 Canvas 上的對象捐顷。在日常的工作開發(fā)中,我們?yōu)?UI 配置背景雨效、圖片迅涮、動畫等等界面效果的時候,需要和眾多的 Drawable 打交道徽龟。每種 Drawable 的適用范圍不同叮姑,我們有必要了解每種 Drawable 的特點以及使用方式,才能在工作中得心應(yīng)手顿肺,少走彎路戏溺。

具體的配置、使用方法以及最終的界面效果大家可以在本文的附件里面看到屠尊。Drawable 在 Android 中的繼承關(guān)系如下旷祸,其中,紅框標(biāo)注的幾種 Drawable 是我們在開發(fā)中比較常用的一些:

常用 Drawable

Drawable 中比較重要的方法有以下幾種:

Drawable
    |- createFromPath
    |- createFromResourceStream
    |- createFromStream
    |- createFromXml
    |
    |- inflate   : 從XML中解析屬性讼昆,子類需重寫
    |- setAlpha  : 設(shè)置繪制時的透明度
    |- setBounds : 設(shè)置Canvas為Drawable提供的繪制區(qū)域
    |- setLevel  : 控制Drawable的Level值托享,這個值在ClipDrawable、RotateDrawable浸赫、ScaleDrawable闰围、AnimationDrawable等Drawable中有重要作用;區(qū)間為[0, 10000]
    |- draw(Canvas) : 繪制到Canvas上既峡,子類必須重寫

其中羡榴,比較重要的方法是inflatedrawinflate 方法用于從 XML 中讀取 Drawable 的配置运敢,draw 方法則實現(xiàn)了把一個 Drawable 確切的繪制到一個 Canvas 上面——draw 方法為一個abstract抽象方法校仑,子類必須進行重寫。inflate 方法在Drawable.createFromXmlInner中被調(diào)用:

createFromXmlInner

我們可以看出传惠,在從 XML 中創(chuàng)建一個 Drawable 時迄沫,步驟如下:

  1. 先根據(jù) XML 節(jié)點名稱來決定創(chuàng)造什么類型的 Drawable;然后 new 出相應(yīng)的 Drawable;
  2. 再為該 Drawable 調(diào)用 inflate 方法,讓其把配置加載起來——因為每種 Drawable 會重寫 inflate 方法卦方,所以羊瘩,可以正確加載到各項配置及屬性。XML 的配置我們稍后再講盼砍。

setAlpha方法用于設(shè)置一個 Drawable 的透明度尘吗,setBounds用來指定當(dāng)執(zhí)行繪制時,在 Canvas 上的位置和區(qū)域浇坐。比如我們自定義一個 View摇予,在其onDraw中繪制一個BitmapDrawable,我們設(shè)置了 BitmapDrawable 的 Alpha 和 Bounds吗跋,代碼如下:

Drawable baseDrawable = getResources().getDrawable(R.drawable.base);
baseDrawable.setAlpha(100);
baseDrawable.setBounds(10, 20, 500, 300);
imageContent.setDrawable(baseDrawable);

繪制后的表現(xiàn)如下:

alpha 繪制表現(xiàn)

上圖中侧戴,第一個區(qū)域是正常繪制的,第二個我們?yōu)?Drawable 設(shè)置了Alpha和Bounds跌宛,可以看出酗宋,右邊深藍色的純色部分為整個 Canvas 的大小,設(shè)置了 100 的 Alpha 透明度后疆拘,圖片把后面深藍色的顏色也給透過來了蜕猫,并且 Bounds 決定了 Canvas 上繪制該 Drawable 的區(qū)域大小和位置。

2哎迄、ColorDrawable

接下來我們逐一介紹 Drawable回右,著重介紹幾種常用的 Drawable隆圆。由于在開發(fā)中這些 Drawable 大多在 XML 中進行配置,所以我們結(jié)合 XML 的配置類介紹翔烁。先從ColorDrawable開始渺氧,這個應(yīng)該是最簡單的一種 Drawable 了,它用一個顏色值來表示

color
    |- color="#xxxxxx | @color/color_value"
    |

比如我們的一個 ColorDrawable 的 XML 配置如下蹬屹,以<color>作為根節(jié)點:

<color
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="#0000ff"/>

使用的時候和其他 Drawable 的使用方法類似侣背,可以通過Resource.getDrawable來獲取,或者在 XML 里面配置:

<RelativeLayout
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@drawable/blue_drawable"/>

這個 View 的界面表現(xiàn)如你所想慨默,是一坨藍色:

藍色

Java代碼實現(xiàn):

Resources res = getResources();
ColorDrawable colorDrawable = new ColorDrawable();
colorDrawable.setColor(res.getColor(R.color.skin_black_item));

3贩耐、BitmapDrawable

BitmapDrawable<bitmap>作為根節(jié)點:

bitmap
    |- src="@drawable/res_id"
    |- antialias="[true | false]"
    |- dither="[true | false]"
    |- filter="[true | false]"
    |- tileMode="[disabled | clamp | repeat | mirror]"
    |- gravity="[top | bottom | left | right | center_vertical |
    |            fill_vertical | center_horizontal | fill_horizontal |
    |            center | fill | clip_vertical | clip_horizontal]"
    |

這個比較復(fù)雜一點了,我們逐一介紹各個屬性:

  • src:表示該 BitmapDrawable 引用的位圖厦取,該圖片為 png潮太、jpg 或者 gif;
  • antialias:表示是否開啟抗鋸齒虾攻;
  • dither:表示當(dāng)位圖和屏幕的像素配置不同時消别,是否允許抖動。比如一張位圖的像素為 ARGB_8888 32 位色台谢,而屏幕像素為 RGB_565寻狂;
  • filter:是否允許為位圖進行濾波以獲取平滑的縮放效果;
  • gravity:定義位圖的 gravity朋沮,當(dāng)位圖小于容器時蛇券,該屬性指定了位圖在容器中的停靠位置繪制方式樊拓。
  • tileMode:表示當(dāng)位圖小于容器時纠亚,執(zhí)行“平鋪”模式,并且指定鋪磚的方法筋夏。該屬性覆蓋 gravity 屬性——當(dāng)指定了該屬性后蒂胞,gravity 屬性即使設(shè)置了,也將不起作用条篷。

其中骗随,gravitytileMode這兩個屬性比較有意思,我們著重來進行介紹赴叹。gravity 的默認(rèn)值為fill——亦即在水平和垂直方向均進行縮放鸿染,使得圖片可以填充到整個 View 里面。

比如我們有一張如下的圖片:

car

為了比較好的展現(xiàn)clamp 鉗位模式乞巧,注意這張圖涨椒,我們在右邊緣和下邊緣用了黑白交替的邊線。我們的 XML 配置極其簡單,以<bitmap>作為根節(jié)點:

<bitmap
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/car"
    android:tileMode="repeat"/>

當(dāng)這個 BitmapDrawable 放入一個比它大的容器中時蚕冬,tileMode 就起作用了:

  1. repeat模式:將重復(fù)貼該圖免猾,直到填充完容器:
repeat
  1. clamp模式:鉗位模式,將沿用下邊囤热、右邊邊緣的像素值分水平猎提、垂直兩個方向擴展填充剩余位置:
clamp
  1. mirror模式:鏡像模式,將按水平赢乓、垂直鏡像重復(fù)來填充剩余位置:
mirror
  1. disabled:禁用任何填充方法,將使用整個位圖進行縮放填充石窑。
disabled

我們接著來看 gravity 屬性牌芋,該屬性也比較容易理解:

  1. top:在頂部水平中心繪制;其他類如 left松逊、right躺屁、bottom 和 top 類似;
top

當(dāng)然经宏,我們可以使用“|”來組合犀暑,達到特殊的效果,比如當(dāng) gravity 為bottom|right時烁兰,表現(xiàn)如下:

bottom|right
  1. center_horizontal耐亏、center_vertical將在水平爷辙、垂直兩個方向上居中洗出。當(dāng)單獨使用 top/left/right/bottom 四個值時,默認(rèn)帶了這兩個中的值:比如 top == top|center_horizontal鄙才;

  2. fill_horizontal主之、fill_vertical將在水平择吊、垂直兩個方向上進行縮放填充,默認(rèn)也是帶了center_horizontal或者center_vertical這兩個值的槽奕;

下面是“fill_vertical”的表現(xiàn):

fill_vertical

下面是“fill_vertical|left”的表現(xiàn):

fill_vertical|left
  1. clip_horizontal几睛、clip_vertical將在 Drawable 比容器大時,按水平粤攒、垂直方向進行裁剪所森,下面是 gravity 為“clip_vertical”的情況,可以看出夯接,裁剪了小汽車的首尾:
clip_vertical

在實際的開發(fā)中必峰,我們要活用這些 gravity 的值,可以通過“|”來獲取各種想要的效果钻蹬。

Java代碼實現(xiàn):

Resources res = getResources();
Bitmap bmp = BitmapFactory.decodeResource(res, R.drawable.adt_48);
BitmapDrawable bitmapDrawable = new BitmapDrawable(res, bmp);
bitmapDrawable.setTileModeX(TileMode.MIRROR);
bitmapDrawable.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);

4吼蚁、NinePatchDrawable

4.1、.9.png圖片資源

這是一種比較高端的 Drawable。其實就是九宮貼圖——這種 Drawable 契合了 Android 中的“.9.png”文件肝匆。這種圖片資源的特點在于:

  1. 在一張普通的 png 圖片四周粒蜈,分別向外擴展了一個像素;
  2. 用這些擴展的像素旗国,可以描邊枯怖,描邊用來規(guī)定可縮放區(qū)域內(nèi)容padding區(qū)域
4.1.1能曾、.9.png的擴展區(qū)域

比如我們現(xiàn)在有一張 .9.png 圖片如下:

pic

我們在四周看到了一像素的黑點度硝,這些黑點分別在四周圍成四個邊線。四個圓角處都是透明的寿冕。那么蕊程,左、上兩條邊規(guī)定了當(dāng)按鈕被縮放時的可縮放區(qū)域驼唱。比如下面紅色邊框圈出的矩形內(nèi)的區(qū)域藻茂,就是可縮放區(qū)域,這個區(qū)域外的區(qū)域玫恳,在執(zhí)行縮放時均保留原來的像素比例辨赐。

patch

比如一個按鈕各個角度拉伸,都可以保留圓角的圓潤京办,而不會發(fā)生鋸齒或者糊掉掀序。我們的布局文件如下:

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    
    <Button
        android:layout_width="100dp"
        android:layout_height="40dp"
        android:background="@drawable/btn_normal"/>

    <Button
        android:layout_width="150dp"
        android:layout_height="80dp"
        android:layout_marginLeft="10dp"
        android:background="@drawable/btn_normal"/>
</LinearLayout>

當(dāng) btn_normal 是一個 .9.png 文件時,界面表現(xiàn)如下:

.9

但如果我們的 background 對應(yīng)的圖片如果是一張非 .9.png 的原圖惭婿,那么森枪,界面表現(xiàn)有些糟,明顯看出审孽,圓角部分糊掉了县袱,并且圓角看起來很詭異:

not .9
4.1.2、.9.png 的 padding 區(qū)域

這就是 .9.png 的妙處佑力。這種格式圖片的右式散、下兩個邊緣的像素點,規(guī)定了padding區(qū)域打颤,也就是說暴拄,內(nèi)容的繪制時的 padding:

padding

上面的紅色邊框圈出的矩形區(qū)域規(guī)定了內(nèi)容繪制的區(qū)域。比如我們把上面的圖片用作一個 Button 的 background 時编饺,整個按鈕的文本將明顯偏上:

padding result

通過padding區(qū)域的控制乖篷,我們可以輕松實現(xiàn)一個按鈕按下后文字也相應(yīng)下移幾個像素的點擊效果。

4.1.3透且、.9.png 的制作

制作簡單提一下撕蔼,在 AndroidSDK 的安裝目錄中豁鲤,tools 文件夾下有一個“draw9patch.bat”文件,啟動該 bat鲸沮,就相應(yīng)打開了 .9.png 的制作工具:

draw9patch

我們把一個簡單的png拖入這個窗口琳骡,就可以編輯了。用鼠標(biāo)左鍵在邊緣點擊以點出像素點讼溺,用鼠標(biāo)右鍵刪除像素點楣号。在右邊可以實時預(yù)覽繪制效果:

draw9patch UI

4.2、NinePatchDrawable 的 XML 配置

這種 Drawable 的 XML 節(jié)點表述如下怒坯,以<nine-patch>作為根節(jié)點:

nine-patch
    |- src="@drawable/9_png_resid"
    |- dither="[true | false]"
    |

其中炫狱,dither 屬性,和之前 BitmapDrawable 中將的一樣剔猿,就是像素配置不同時视译,是否允許抖動。src 比較重要艳馒,這個值指向的必須是一個“.9.png”格式的圖片憎亚,否則员寇,底層NinePatchDrawable.inflate方法在解析的時候弄慰,會拋出一個XmlPullParserException異常:

XmlPullParserException

我們可以看出,上圖中bitmap.getNinePatchChunk這個方法蝶锋,獲取 9 宮的各項信息陆爽,如果從一個 Bitmap 對象中得不到這些信息,則表示這個圖片非“.9.png”格式的圖片扳缕,就拋出異常慌闭。
其實,“.9.png”的圖片躯舔,本質(zhì)上是一張普通的 png 圖片驴剔。比如,我們有一張名為“btn_normal.9.png”的圖片粥庄,可以在代碼中這樣使用:

View imageContent = findViewById(R.id.xxx);

Resources res = getResources();
NinePatchDrawable normal = (NinePatchDrawable) res.getDrawable(R.drawable.btn_normal);
imageContent.setBackground(normal);

4.3丧失、NinePatchDrawable 與 .9.png 圖片的映射

那么,Android 是怎樣把這張圖片映射為一個 NinePatchDrawable 的呢惜互?原來布讹,這張圖片開始被當(dāng)作普通的 Bitmap,從 Resources.getDrawable 方法中可以看出端倪:

getDrawable

在 getDrawable 中調(diào)用了loadDrawable训堆,在 loadDrawable 方法中有一個緩存策略描验,我們先不管,直接看加載資源的部分:

loadDrawable

可以看出坑鱼,對 XML 配置類型的 Drawable膘流,使用loadXmlResourceParse加載,然后使用Drawable.createFromXml這個靜態(tài)方法進行創(chuàng)建,得到 Drawable 對象睡扬。對于其他類型的 Drawable盟蚣,先使用openNonAsset得到一個流對象,然后使用Drawable.createFromResourceStream這個靜態(tài)方法進行創(chuàng)建卖怜。Drawable.createFromXml 這個方法最終會調(diào)用Drawable.createFromXmlInner屎开,這個方法我們前面 Drawable 簡介里面已經(jīng)介紹過了。我們著重看 Drawable.createFromResourceStream 這個方法:

getNinePatchChunk

在這個方法中马靠,我們先從流中解析得到一個 Bitmap 對象——這個對象本質(zhì)上和其他所有類型的圖片資源沒任何區(qū)別奄抽。區(qū)別在于接下來調(diào)用的Bitmap.getNinePatchChunkNinePatch.isNinePatchChunk這兩個方法甩鳄,通過這兩個方法的結(jié)合調(diào)用逞度,可以判斷這個 Bitmap 是否是一個合格的“.9.png”圖片。接下來進入drawableFromBitmap

drawableFromBitmap

最后妙啃,根據(jù)九宮信息 np 這個參數(shù)是否為 null档泽,來決定創(chuàng)建什么對象∫靖埃可以看出馆匿,對“.9.png”格式的圖片,最終會創(chuàng)建一個 NinePatchDrawable 對象燥滑,對于其他普通的 png渐北、jpg 等圖片,創(chuàng)建相應(yīng)的 BitmapDrawable 對象铭拧。一切一目了然赃蛛。

Java代碼實現(xiàn):

這部分我們使用draw9patch工具很容易制作,一般不會在代碼中進行創(chuàng)建NinePatchDrawable對象搀菩,也不推薦在代碼中這樣做呕臂。

5、StateListDrawable

這個 Drawable 類型幾乎是我們開發(fā)中最常用的類型了肪跋,為什么呢歧蒋?因為它是根據(jù)一系列的狀態(tài)來控制繪制表現(xiàn)的,這一系列狀態(tài)契合了我們界面控件的各個狀態(tài)澎嚣。界面控件的狀態(tài)一般有:獲取焦點疏尿、失去焦點、普通狀態(tài)易桃、按下狀態(tài)褥琐、可點擊狀態(tài)、不可點擊狀態(tài)晤郑、選中狀態(tài)敌呈、未選中狀態(tài)贸宏、勾選狀態(tài)、未被勾選狀態(tài)磕洪、激活狀態(tài)吭练、未被激活狀態(tài)等等。

StateListDrawable<selector>作為根節(jié)點:

selector
    |- item
    |    |- drawable="@drawable/drawable_id"
    |    |- state_pressed="[true | false]"
    |    |- state_focused="[true | false]"
    |    |- state_selected="[true | false]"
    |    |- state_hovered="[true | false]"
    |    |- state_checked="[true | false]"
    |    |- state_checkable="[true | false]"
    |    |- state_enabled="[true | false]"
    |    |- state_activated="[true | false]"
    |    |- state_window_focused="[true | false]"
    |

一個selector以多個item來組成析显,每個 item 由 0 個或者多個狀態(tài)和一個 drawable 來表示鲫咽,當(dāng)控件的狀態(tài)變化后,將根據(jù)控件當(dāng)前的狀態(tài)谷异,來進行匹配分尸,匹配一個最適合當(dāng)前狀態(tài)的 item,然后用這個 item 的 drawable 來進行繪制歹嘹。
比如箩绍,我們一個普通按鈕的 selector 如下:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_pressed="true"
        android:drawable="@drawable/pressed_btn" />
    
    <item android:drawable="@drawable/normal_btn" />
</selector>

我們定義了一個按鈕的普通狀態(tài)和按下狀態(tài)的 Drawable,使用方法如下:

<Button
    android:layout_width="200dp"
    android:layout_height="60dp"
    android:textColor="#e22"
    android:background="@drawable/flat_button_drawable"
    android:text="Flat Button" />

那么尺上,在普通狀態(tài)和按下狀態(tài)中材蛛,界面表現(xiàn)分別如下:

普通狀態(tài)????

normal

按下狀態(tài)????

pressed

Cool!除了按下狀態(tài)的紅色有點刺眼外怎抛,看起來還不錯卑吭,是吧。其實抽诉,我們可以通過控件狀態(tài)陨簇,來控制普通態(tài)吐绵、按下態(tài)的按鈕文字顏色迹淌。我們新建一個 XML,放入res/color文件夾下己单,比如起名為 btn_text_color.xml:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" android:color="#fff"/>
    <item android:color="#e22"/>
</selector>

我們在Button中的配置如下唉窃,通過設(shè)置android:textColor來控制按鈕的文本顏色:

<Button
    android:layout_width="200dp"
    android:layout_height="60dp"
    android:textColor="@color/btn_text_color"
    android:background="@drawable/flat_button_drawable"
    android:text="Flat Button" />

現(xiàn)在,一個高大上的扁平化的按鈕效果出爐了:

普通狀態(tài)????

normal

按下狀態(tài)????

pressed

在實際操作中纹笼,我們可能要為多種狀態(tài)來進行設(shè)置纹份,可以靈活運用:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_pressed="true"
        android:state_pressed="true"
        android:state_selected="false"
        android:drawable="@drawable/pressed_btn"/>

    <item android:drawable="@drawable/normal_btn"/>
</selector>

這樣,第一個item將只匹配未被禁用且當(dāng)前為按下狀態(tài)且未被選中狀態(tài)廷痘。其他狀態(tài)均使用第二個item蔓涧。

當(dāng)然,不踩幾個坑笋额,怎么能做一名合格的開發(fā)者呢元暴?

注意:

如果有不帶任何狀態(tài)的 item 的話,這個item一定要放在整個 item 列表的最下面兄猩。否則茉盏,所有的狀態(tài)均可優(yōu)先匹配到這個 item鉴未,其他 item 將得不到匹配。因為匹配的時候是一個遍歷操作鸠姨,如果遍歷找到和當(dāng)前狀態(tài)符合的 Drawable铜秆,就直接返回。

Java代碼實現(xiàn):

Resources res = getResources();
StateListDrawable stateListDrawable = new StateListDrawable();
stateListDrawable.addState(
    new int[] {android.R.attr.state_pressed},
    res.getDrawable(R.drawable.blue_drawable));

stateListDrawable.addState(
    new int[] {
        android.R.attr.state_pressed,
        android.R.attr.state_enabled},
    res.getDrawable(R.drawable.bmp_drawable));

stateListDrawable.addState(
    new int[] {},
    res.getDrawable(R.drawable.bkgnd_normal));

6讶迁、ClipDrawable

ClipDrawable允許我們對一個 Drawable 進行剪裁操作连茧,在繪制的時候只繪制剪裁的部分。這里最關(guān)鍵的是Drawable.setLevel方法在起作用巍糯,在為一些控件比如進度條梅屉、音量控制條等設(shè)置 UI 效果的時候,一般會使用 ClipDrawable鳞贷,否則坯汤,你的進度在界面上將得不到刷新。

ClipDrawable以<clip>作為根節(jié)點:

  clip
    |- drawable="@drawable/drawable_id"
    |- clipOrientation="[horizontal | vertical]"
    |- gravity="[ ... ]"
    |

clipOrientation決定了裁剪的方向搀愧,默認(rèn)為horizontal——表示水平方向剪裁惰聂;而 gravity 的取值和之前介紹的類似,結(jié)合 clipOrientation 決定了剪裁發(fā)生的位置——默認(rèn)為left咱筛,就是當(dāng) clipOrientation 為 horizontal 時搓幌,剪裁發(fā)生在 drawable 的右側(cè)。

最主要的繪制我們來看ClipDrawable.draw方法:

ClipDrawable.draw

根據(jù) AndroidSDK 的規(guī)范迅箩,setLevel的 level 值在[0, 10000]這個區(qū)間內(nèi)溉愁。可以看出饲趋,在繪制的時候拐揭,根據(jù) level 值和 gravity 算出要剪裁的區(qū)域,然后在 Canvas 上執(zhí)行 clipRect奕塑,從而達到剪裁效果堂污。

XML的配置也很簡單:

<clip
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/normal_btn"
    android:clipOrientation="vertical"
    android:gravity="top" />

Java代碼實現(xiàn):

Resources res = getResources();
NinePatchDrawable btnNormal = (NinePatchDrawable) res.getDrawable(R.drawable.btn_normal);
ClipDrawable clipDrawable = new ClipDrawable(
    btnNormal, Gravity.TOP, ClipDrawable.VERTICAL);
clipDrawable.setLevel(500);

我們后續(xù)結(jié)合LayerDrawable來看ClipDrawable在進度條等 UI 上的配置方式。

7龄砰、LayerDrawable

LayerDrawable可以將一組 Drawable 按 XML 中定義的順序?qū)盈B起來進行繪制盟猖,并可以設(shè)定每層 Drawable 的 id、位置等等换棚。ProgressBar這個控件的背景切圖式镐,可以通過 LayerDrawable 來進行配置。LayerDrawable 以<layer-list>作為根節(jié)點:

layer-list
    |- item
    |    |- drawable="@drawable/drawable_id"
    |    |- id="@+id/xxx_id"
    |    |- top="dimension"
    |    |- left="dimension"
    |    |- right="dimension"
    |    |- bottom="dimension"
    |

每組 Drawable 由<item>節(jié)點進行配置固蚤,item 中 drawable 表示了這層 Drawale 引用的繪圖資源 ID娘汞,id屬性表示了這層 Drawable 的ID,top颇蜡、left价说、right辆亏、bottom這四個屬性發(fā)布表示與各個方向的間距。比如一個簡單的 LayerDrawable 如下:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@drawable/red_color"
        android:bottom="10dp"
        android:left="10dp"
        android:right="10dp"
        android:top="10dp"/>
    <item
        android:drawable="@drawable/green_color"
        android:bottom="20dp"
        android:left="20dp"
        android:right="20dp"
        android:top="20dp"/>
    <item
        android:drawable="@drawable/blue_color"
        android:bottom="30dp"
        android:left="30dp"
        android:right="30dp"
        android:top="30dp"/>
</layer-list>

那么鳖目,繪制出來的效果如下(其中扮叨,灰色那一層是Activity的背景):

繪制結(jié)果

接下來我們看看LayerDrawable在ProgressBar中的配置:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@android:id/background"
        android:drawable="@drawable/file_background"/>
    
    <item android:id="@android:id/progress">
        <clip android:drawable="@drawable/file_progress"/>
    </item>
    
    <item android:id="@android:id/secondaryProgress">
        <clip android:drawable="@drawable/file_cache_progress"/>
    </item>
</layer-list>

可以看出,我們在配置的時候领迈,分別為每個 item 指定了 id——這些 id 對應(yīng)表示了 ProgressBar 中每種進度狀態(tài):background對應(yīng)整個 ProgressBar 的背景彻磁,progress對應(yīng)當(dāng)前的進度背景,而secondaryProgress對應(yīng) secondaryProgress 的進度背景(一般我們用來做緩沖進度——和優(yōu)酷視頻的緩沖類似)狸捅。另外我們看出衷蜓,結(jié)合使用了 ClipDrawable——因為 ProgressBar 的實現(xiàn)中,正是結(jié)合Drawable.setLevel來進行刷新進度的尘喝,在前面講過磁浇,ClipDrawable 恰好在onDraw繪制中,對 Level 做了相應(yīng)的處理:

setLevel

這里有一個方法:LayerDrawable.findDrawableByLayerId朽褪,這個方法可以獲取 id 對應(yīng)的 Drawable置吓。

Java代碼實現(xiàn):

Resources res = getResources();
LayerDrawable layerDrawable = new LayerDrawable(
        new Drawable[] {
            res.getDrawable(R.drawable.red_color),
            res.getDrawable(R.drawable.green_color),
            res.getDrawable(R.drawable.blue_color)
        });

layerDrawable.setId(0, R.id.action_settings);
layerDrawable.setId(1, R.id.switchBtn);
layerDrawable.setLayerInset(0, 10, 10, 10, 10);
layerDrawable.setLayerInset(1, 20, 20, 20, 20);

8、AnimationDrawable

8.1缔赠、AnimationDrawable 的使用

借助AnimationDrawable衍锚,我們可以輕松實現(xiàn)基于一系列 Drawable 幀的動畫效果。AnimationDrawable 提供了一系列簡單易用的接口來幫助我們:

AnimationDrawable
   |- setOneShot : 設(shè)置動畫是否單次播放嗤堰,默認(rèn)為false戴质,表示不循環(huán)
   |- start : 開始播放動畫,如果已經(jīng)在播放中踢匣,則不起作用
   |- end : 結(jié)束播放
   |

一般我們在 XML 里面進行配置動畫告匠,代碼中手工寫的方式不推薦。AnimationDrawable 以<animation-list>作為根節(jié)點:

animation-list
    |- oneshot="[true | false]"
    |- visible="[true | false]"
    |- item
    |    |- drawable="@drawable/drawable_id"
    |    |- duration="xms"
    |

animation-list 節(jié)點內(nèi)的oneshot屬性表示該動畫是否只播放一次符糊,當(dāng)這個值為 false 的時候凫海,表示循環(huán)播放——這是默認(rèn)值呛凶。其他的一系列動畫效果男娄,均由一組<item>節(jié)點來進行配置,item 中的duration表示這一幀和上一幀的時間間距漾稀,以 ms 為單位模闲。比如我們有一個簡單的動畫配置如下:

<animation-list
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    
    <item
        android:drawable="@drawable/red_color"
        android:duration="500"/>
    
    <item
        android:drawable="@drawable/green_color"
        android:duration="500"/>
    
    <item
        android:drawable="@drawable/blue_color"
        android:duration="500"/>
</animation-list>

我們可以將該動畫效果施加到一個 View 上:

View imageContent = findViewById(R.id.xxx);

AnimationDrawable drawable = (AnimationDrawable) res.getDrawable(R.drawable.animation_drawable);
imageContent.setBackground(drawable);
drawable.start();

這樣,我們在這個 View 上可以看到每隔 500ms 便變換一次顏色的動畫效果崭捍。當(dāng)然尸折,這只是一個 demo,利用 AnimationDrawable殷蛇,我們可以做出更酷的動畫实夹。

我們一般在 XML 里面配置 AnimationDrawable橄浓,通過 Resources.getDrawable 方法來獲取它。雖然我們不推薦在代碼里面手工創(chuàng)建 AnimationDrawable亮航,但萬一哪天你需要它呢荸实?

Java代碼實現(xiàn):

Resources res = getResources();
AnimationDrawable animationDrawable = new AnimationDrawable();
animationDrawable.addFrame(res.getDrawable(R.drawable.red_color), 500);
animationDrawable.addFrame(res.getDrawable(R.drawable.green_color), 500);
animationDrawable.addFrame(res.getDrawable(R.drawable.blue_color), 500);

animationDrawable.setOneShot(false);
imageContent.setBackground(animationDrawable);
animationDrawable.start();

8.2、AnimationDrawable 的原理

我們只是把一個 AnimationDrawable 塞入了一個 View 的 background 中缴淋,那么這些動畫的變換准给,是怎么響應(yīng)到 View 上的呢?原來重抖,這一切都是Drawable.Callback這個回調(diào)在起作用:

Callback

我們通過 Drawable.setCallback 來設(shè)置一個Callback露氮,這個 Callback 中有三個方法:

  • invalidateDrawable:重繪 Drawable;
  • scheduleDrawable:在 when 規(guī)定的 ms 后钟沛,執(zhí)行 what 這個Runnable畔规;(這里可以看出動畫的端倪了)
  • unscheduleDrawable:異步執(zhí)行這個 what;用來結(jié)束動畫等恨统。

View 類實現(xiàn)了 Drawable.Callback 這個接口油讯,在我們調(diào)用View.setBackground方法為 View 設(shè)置背景的時候,會把 View 的 this 塞入 Drawable 中作為 Callback:

Drawable.Callback
setCallback

而在 AnimationDrawable 自己實現(xiàn)了Runnable這個接口延欠,在run方法中陌兑,通過調(diào)用AnimationDrawable.nextFrame方法,提供了動畫幀的切換由捎、終止判斷等操作兔综。

setFrame
scheduleSelf

在這里首先使用selectDrawable把對應(yīng)幀的 Drawable 選為激活的,然后在scheduleSelf中狞玛,通過調(diào)用Drawable.Callback.scheduleDrawable這個 Callback 方法软驰,可以達到動畫幀按時間間隔切換的效果。

9心肪、其他 Drawable 及總結(jié)

基本上我們在工作中最常用的幾類 Drawable 如上所示锭亏。其他的一些 Drawable 有時也會用到,也很有趣硬鞍。比如ShapeDrawable慧瘤、RotateDrawableScaleDrawable以及InsetDrawable固该。這些 Drawable 可以在工作中確實需要用到的時候去參考 SDK 進行學(xué)習(xí)和靈活運用锅减,在這里簡單介紹下這幾種 Drawable 的作用和使用方法,以及一些效果截圖伐坏。

9.1怔匣、ShapeDrawable

通過在 XML 中配置 ShapeDrawable,我們可以輕松繪制矩形桦沉、線段每瞒、圓角矩形金闽、漸變等圖形作為 background 而不需要切圖。ShapeDrawable 以<shape>作為根節(jié)點剿骨;需要熟悉子節(jié)點的有:corners呐矾、gradientpadding懦砂、size蜒犯、solidstroke等荞膘;比如下面是一個簡單的配置及效果展現(xiàn):

<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <corners android:radius="5dp"/>

    <gradient
        android:angle="90"
        android:endColor="#ddd"
        android:startColor="#343434"
        android:type="linear"/>
    
    <stroke
        android:width="2dp"
        android:color="#00f"/>
</shape>

我們通過android:shape指定這個 shape 是一個矩形(rectangle)罚随,用子節(jié)點corners為矩形加上圓角,使之變成一個圓角矩形羽资;再使用gradient子節(jié)點來施加一個漸變效果淘菩,漸變的類型用android:type指定為線性漸變(linear);最后再使用stroke子節(jié)點為整個圖形加上一個 2dp 寬的藍色外邊框屠升。其效果圖如下:

shape
9.2潮改、RotateDrawable

RotateDrawable可以結(jié)合當(dāng)前 Drawable 的 level 值,進行旋轉(zhuǎn)腹暖。level 值每增加一汇在,其旋轉(zhuǎn)角度旋轉(zhuǎn)(toDegrees – fromDegrees) / 10000。比如下圖是一張正常的圖片:

robot

我們通過一個 XML 進行旋轉(zhuǎn)脏答,其中android:fromDegreesandroid:toDegrees確定了旋轉(zhuǎn)的起始角度和終止角度糕殉,android:pivotXandroid:pivotY確定了旋轉(zhuǎn)中心點的位置:

<rotate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/android_robot"
    android:fromDegrees="0"
    android:toDegrees="180"
    android:pivotX="50%"
    android:pivotY="50%"/>

我們在 UI 線程中控制這個 RotateDrawable 的 Level 值殖告,可以獲得一個旋轉(zhuǎn)的動畫效果:

RotateDrawable 例子

我們截取了部分動畫效果的過程阿蝶,如下:

RotateDrawable 示例運行截圖
9.3、ScaleDrawable

ScaleDrawable可以結(jié)合當(dāng)前 Drawable 的 level 值黄绩,進行圖片的縮放羡洁,同樣結(jié)合HandlerTimer,我們可以得到一個簡單的縮放動畫爽丹。

9.4筑煮、InsetDrawable

InsetDrawable可以把一個 drawable 資源嵌入到其他的資源內(nèi)部,并且在四周可以留下邊距习劫。比如我們有時候需要一個左右各留白 15dp 的ListView的分隔線咆瘟,我們可以用 InsetDrawable 來做。為什么不使用切圖的方式來留白呢——注意诽里,我們這里要求是 15dp,而不是 15pixel飞蛹,如果切圖的話谤狡,只能用像素單位留白灸眼,但這導(dǎo)致在不同的設(shè)備上可能用戶看到的留白的間距不統(tǒng)一

<inset
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/seperator_line"
    android:insetLeft="15dp"
    android:insetRight="15dp"/>

我們應(yīng)用到一個 ListView 的分隔符上:

<ListView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:divider="@drawable/inset_drawable"
    android:dividerHeight="1dp"/>

這樣墓懂,我們得到了一個首尾均留白 15dp 的分隔符焰宣,整個界面效果展現(xiàn)如下(灰色背景部分是整個 ListView 的輪廓):

ListView 結(jié)果

當(dāng)然,還有其他諸如TransitionDrawable捕仔、LevelListDrawable匕积、GradientDrawablePictureDrawable榜跌、PaintDrawable沒有詳細介紹闪唆,但這幾種一般不是很常用。經(jīng)過前面一些 Drawable 的簡介钓葫,即時我們在工作中需要用到這幾類 Drawable悄蕾,也可以輕松通過查看文檔等方式來學(xué)習(xí)和使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末础浮,一起剝皮案震驚了整個濱河市帆调,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌豆同,老刑警劉巖番刊,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異影锈,居然都是意外死亡撵枢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門精居,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锄禽,“玉大人,你說我怎么就攤上這事靴姿∥值” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵佛吓,是天一觀的道長宵晚。 經(jīng)常有香客問我,道長维雇,這世上最難降的妖魔是什么淤刃? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮吱型,結(jié)果婚禮上逸贾,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好铝侵,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布灼伤。 她就那樣靜靜地躺著,像睡著了一般咪鲜。 火紅的嫁衣襯著肌膚如雪狐赡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天疟丙,我揣著相機與錄音颖侄,去河邊找鬼。 笑死享郊,一個胖子當(dāng)著我的面吹牛览祖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拂蝎,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼李剖,長吁一口氣:“原來是場噩夢啊……” “哼俺叭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤姻僧,失蹤者是張志新(化名)和其女友劉穎跨细,沒想到半個月后垛耳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涩赢,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年馆里,在試婚紗的時候發(fā)現(xiàn)自己被綠了隘世。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸠踪,死狀恐怖丙者,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情营密,我是刑警寧澤械媒,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站评汰,受9級特大地震影響纷捞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜被去,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一主儡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惨缆,春花似錦糜值、人聲如沸丰捷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓢阴。三九已至畅蹂,卻和暖如春健无,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背液斜。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工累贤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人少漆。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓臼膏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親示损。 傳聞我的和親對象是個殘疾皇子渗磅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容