一篇文章介紹完 Drawable

main_content

1. Drawable 簡介

Drawable 在 Android 開發(fā)中是非常常用的,比如在 XML 中定義color,shape,selector 等。通過 Drawable 的使用,能夠幫助我們實現(xiàn)一些比較好的界面效果,同時 Drawable 又相對輕量級,不像自定義 View 那樣復(fù)雜。使用 Drawable 代替一些圖片時,可以有效減少 APK 包的大小,開發(fā)成本也行對比較低。

A Drawable is a general abstraction for "something that can be drawn." Most often you will deal with Drawable as the type of resource retrieved for drawing
things to the screen; the Drawable class provides a generic API for dealing with an underlying visual resource that may take a variety of forms.
Unlike a View, a Drawable does not have any facility to receive events or otherwise interact with the user.

官方文檔給出的描述:Drawable 是一種可繪制事務(wù)的抽象,通常情況下我們將 Drawable 作為一種資源類型,來會繪制到屏幕上。
Drawable類提供了用于處理底層可視資源的通用API。與視圖不同,Drawable沒有任何工具來接收事件或與用戶進(jìn)行任何交互。

實際上,Drawable 就是將我們定義的圖形,顏色等繪制到屏幕上,但是不提供事件的處理,自然也就沒有交互的功能。

Drawable的內(nèi)部寬高可以分別通過getIntrinsicWidth()和getIntrinsicHeight()獲取,但并不是所有的Drawable都有內(nèi)部寬高的屬性,比如一個顏色形成的Drawable并沒有寬高的概念。在大多數(shù)情況下,Drawable并沒有大小的概念,因為當(dāng)Drawable作為View的背景圖時,Drawable會被拉伸至View的同等大小。

2. Drawable 種類

drawable-分類.png

看到 Drawable 的種類比較多,但常用的不多,主要有 ShapeDrawable,BitmapDrawable,LayerDrawable,StateListDrawable 等。

下面就一一介紹一下各個 Drawable 的特點和作用以及簡單的用法。

3. 各種 Drawable

3.1 BitmapDrawable

一般情況下,我們可以直接使用原圖片。在有些場景下需要做一些特殊的效果,所以可以使用 BitmapDrawable 來對圖片做一些處理,相當(dāng)于對圖片做一下包裝,如平鋪填充、拉伸填充或者保持圖片原始大小,也可以在 BitmapDrawable 區(qū)域內(nèi)部使用gravity指定的對齊方式。


    <?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"]
        android:dither=["true" | "false"]
        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:tileMode=["disabled" | "clamp" | "repeat" | "mirror"] />

下面看一下這些屬性的含義:

  • android:src

類型:Drawable resource。必需。 引用一個drawable resource.

  • android:antialias

類型:Boolean。是否開啟抗鋸齒。開啟后圖片會變得更平滑些,因此一般建議開啟,設(shè)置為true即可。

  • android:dither

類型:Boolean。是否允許抖動,如果位圖與屏幕的像素配置不同時,開啟這個選項可以讓高質(zhì)量的圖片在低質(zhì)量的屏幕上保持較好的顯示效果(例如:一個位圖的像素設(shè)置是 ARGB 8888,但屏幕的設(shè)置是RGB 565,開啟這個選項可以是圖片不過于失真)一般建議開啟,為true即可。

  • android:filter

類型:Boolean。是否允許對位圖進(jìn)行濾波。當(dāng)圖片被壓縮或者拉伸時,使用濾波可以獲得平滑的外觀效果。一般建議開啟,為true即可。

  • android:gravity

當(dāng)圖片小于容器尺寸時,設(shè)置此選項可以對圖片經(jīng)典定位,這個屬性比較多,不同選項可以使用‘|’來組合使用。

gravity
  • android:mipMap

紋理映射處理技術(shù),一般也不常用,默認(rèn)為false

android:tileMode

平鋪模式。共有以下幾個值

disabled :默認(rèn)值,表示不使用平鋪

clamp :復(fù)制邊緣色彩

repeat :X、Y 軸進(jìn)行重復(fù)圖片顯示,也就是我們說要說的平鋪

mirror :在水平和垂直方向上使用交替鏡像的方式重復(fù)圖片的繪制

下面看下效果:

tilemode

使用方法:

(1) 直接使用 XML 文件定義 BitmapDrawable,這也是最常用的方法;

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:antialias="true"
    android:dither="true"
    android:filter="true"
    android:gravity="center"
    android:src="@drawable/pic"
    android:tileMode="clamp">

</bitmap>

(2) 使用代碼方式生成 BitmapDrawable,稍微麻煩點,一般不常用。
實際上我們從BitmapDrawable的源碼可以看出,目前Google建議我們創(chuàng)建BitmapDrawable的構(gòu)造方法有3種

public BitmapDrawable(Resources res, Bitmap bitmap)

public BitmapDrawable(Resources res, String filepath)

public BitmapDrawable(Resources res, java.io.InputStream is)


Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image1);
BitmapDrawable mBitmapDrawable = new BitmapDrawable(getResources(),mBitmap);
mBitmapDrawable.setTileModeXY(TileMode.MIRROR, TileMode.MIRROR);//平鋪方式
mBitmapDrawable.setAntiAlias(true);//抗鋸齒
mBitmapDrawable.setDither(true);//防抖動
//設(shè)置到imageView上即可
imageView.setImageDrawable(mBitmapDrawable);

** 注意 **

在使用 android:tileMode 過程中,有另外一個屬性和這個表面看上去類似 android:tintMode, 這個是顏色渲染的模式設(shè)置,所有的 view 和 Drawable 都有這個選項。它的作用實際上就是用來決定渲染之后顯示的效果的,一般只有多個圖層時才起作用,如 ImageView 中的 src 和 background 都設(shè)置了圖片,這時就會有顯示的先后和疊加等問題,可以設(shè)置這個選項??墒褂玫倪x項共有 6 種(add、multiply、screen、src_over、src_in、src_atop,)具體的選項有,可以參看下面來進(jìn)行設(shè)置。

tintmode
1.PorterDuff.Mode.CLEAR
  所繪制不會提交到畫布上
  
2.PorterDuff.Mode.SRC
   顯示上層繪制圖片

3.PorterDuff.Mode.DST
  顯示下層繪制圖片

4.PorterDuff.Mode.SRC_OVER
  正常繪制顯示,上下層繪制疊蓋
  
5.PorterDuff.Mode.DST_OVER
  上下層都顯示。下層居上顯示。
  
6.PorterDuff.Mode.SRC_IN
   取兩層繪制交集。顯示上層。
   
7.PorterDuff.Mode.DST_IN
  取兩層繪制交集。顯示下層。
  
8.PorterDuff.Mode.SRC_OUT
 取上層繪制非交集部分。
 
9.PorterDuff.Mode.DST_OUT
 取下層繪制非交集部分。
 
10.PorterDuff.Mode.SRC_ATOP
 取下層非交集部分與上層交集部分
 
11.PorterDuff.Mode.DST_ATOP
 取上層非交集部分與下層交集部分
 
12.PorterDuff.Mode.XOR
  異或:去除兩圖層交集部分
  
13.PorterDuff.Mode.DARKEN
  取兩圖層全部區(qū)域,交集部分顏色加深
  
14.PorterDuff.Mode.LIGHTEN
  取兩圖層全部,點亮交集部分顏色
  
15.PorterDuff.Mode.MULTIPLY
  取兩圖層交集部分疊加后顏色
  
16.PorterDuff.Mode.SCREEN
  取兩圖層全部區(qū)域,交集部分變?yōu)橥该魃?

3.2 NinePatchDrawable

NinePatchDrawable 表示的是我們熟悉的.9格式的圖片,.9圖片可以在保證圖片不失真的情況下任意進(jìn)行縮放,在實際的使用中我們也是通過Xml來實現(xiàn)即可:

<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"   
    android:src="drawable/resource"  
    android:dither="[true|false]"/> 
    

屬性和 BitmapDrawable 中屬性的含義相同,這里不過多描述。一般情況下不建議代碼創(chuàng)建.9圖,因為 Android 雖然可以使用 Java 代碼創(chuàng)建 NinePatchDrawable,但是極少情況會那么做,這是因為由于Android SDK會在編譯工程時對點九圖片進(jìn)行編譯,形成特殊格式的圖片。使用代碼創(chuàng)建 NinePatchDrawable 時只能針對沒有編譯過的點九圖片資源,對于沒有編譯過的點九圖片資源都當(dāng)做 BitmapDrawable 對待。還有點需要特別注意的是,點九圖只能適用于拉伸的情況,對于壓縮的情況并不適用,如果需要適配很多分辨率的屏幕時需要把點九圖做的小一點。
??
??.9 圖使用時規(guī)則如下:

.9圖片的四條黑邊的意義,每條黑邊的意義都不一樣

頂部:在水平拉伸的時候,保持其他位置不動,只在這個點的區(qū)域做無限的延伸
左邊:在豎直拉伸的時候,保持其他位置不動,只在這個點的區(qū)域做無限的延伸
底部:在水平拉伸的時候,指定圖片里的內(nèi)容顯示的區(qū)域
右邊:在豎直拉伸的時候,指定圖片里的內(nèi)容顯示的區(qū)域

3.3 ShapeDrawable

ShapeDrawable 對于 XML 的shape標(biāo)簽,在實際開發(fā)中我們經(jīng)常將其作為背景圖片使用,因為 ShapeDrawable 可以幫助我們通過顏色來構(gòu)造圖片,也可以構(gòu)造漸變效果的圖片,總之,ShapeDrawable 足矣滿足我們大部分特殊需求下面我們說說其使用方法:

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape=["rectangle" | "oval" | "line" | "ring"] >
    <corners
        android:radius="integer"
        android:topLeftRadius="integer"
        android:topRightRadius="integer"
        android:bottomLeftRadius="integer"
        android:bottomRightRadius="integer" />
    <gradient
        android:angle="integer"
        android:centerX="integer"
        android:centerY="integer"
        android:centerColor="integer"
        android:endColor="color"
        android:gradientRadius="integer"
        android:startColor="color"
        android:type=["linear" | "radial" | "sweep"]
        android:usesLevel=["true" | "false"] />
    <padding
        android:left="integer"
        android:top="integer"
        android:right="integer"
        android:bottom="integer" />
    <size
        android:width="integer"
        android:height="integer" />
    <solid
        android:color="color" />
    <stroke
        android:width="integer"
        android:color="color"
        android:dashWidth="integer"
        android:dashGap="integer" />
</shape>

從代碼中我們可以看出Shape的子元素包括、<gradient>、<padding>、<size>、<solid>、<stroke>.
??
??android:shape
??這個屬性表示圖像的形狀,可以是rectangle(矩形)、oval(橢圓)、line(橫線)、ring(圓環(huán))。默認(rèn)為rectangle。
這里對于ring值還有幾個相關(guān)的屬性:

ting

<corners>

指定邊角的半徑,數(shù)值越大角越圓,數(shù)值越小越趨近于直角,參數(shù)為:

    <corners
    android:radius="integer"
    android:topLeftRadius="integer"
    android:topRightRadius="integer"
    android:bottomLeftRadius="integer"
    android:bottomRightRadius="integer" />

<gradient>
??設(shè)置顏色漸變與<solid>為互斥標(biāo)簽,因為solid表示純色填充,而gradient表示漸變填充。

drawable-漸變.png

angle=0和angle=90的區(qū)別(都為線性漸變):
??


drawable-漸變效果1.png

linear(線性)為默認(rèn)值,radial(徑內(nèi)漸變),sweep(掃描漸變)區(qū)別如下:

drawable-漸變效果2.png

下面畫一個漸變的圓環(huán),看一下效果:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:innerRadius="100dp"
    android:shape="ring"
    android:thickness="40dp"
    android:useLevel="false">

    <gradient
        android:angle="0"
        android:centerColor="@color/colorPrimaryDark"
        android:endColor="@android:color/white"
        android:startColor="@color/colorAccent"
        android:type="sweep"
        android:useLevel="false" />

</shape>

gradient

<solid>
??表示純色填充,通過android:color設(shè)置顏色即可。
??
<stroke>
描述邊框,屬性如下:

stroke

有點要明白的是 android:dashWidth 和 android:dashGap 有任意一個為0,則虛線無法預(yù)期顯示。

同樣也來一個純色的圓環(huán)看看效果:

方式一:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="ring"
    android:useLevel="false"
    android:innerRadius="50dp"
    android:thickness="10dp">

    <solid android:color="@color/colorPrimaryDark"

</shape>

方式二:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval"
    android:useLevel="false">

    <stroke
        android:width="10dp"
        android:color="@color/colorPrimaryDark" />

</shape>

虛線圓環(huán):

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval"
    android:useLevel="false">

    <stroke
        android:width="5dp"
        android:color="@color/colorPrimaryDark"
        android:dashGap="3dp"
        android:dashWidth="3dp" />

</shape>

效果就不展示了??梢宰约簢L試下。

<padding>

表示內(nèi)容或子標(biāo)簽邊距,4個屬性top、bottom、left、right,需要注意的是這個標(biāo)簽的作用是為內(nèi)容設(shè)置與當(dāng)前應(yīng)用此shape的View的邊距,而不是設(shè)置當(dāng)前View與父元素的邊距。

<size>

shape 通過這個標(biāo)簽可以設(shè)置 shape 的大?。?/p>

<size android:width="10dp"
        android:height="10dp"/>

注意,給 shape 設(shè)置大小,實際中意義并不是很大,為什么這么說呢,因為shape 常用作于 view 的背景,所以 shape 的最終大小是 view 的大小,也就是說 shape的大小是自適應(yīng) view 的大小的。 Drawable 有兩個方法,getIntrinsicWidth 和 getIntrinsicHeight,通過 這個兩個方法能夠獲取 Drawable 固有的寬和高,對于有圖片的 Drawable ,返回的寬高就是 圖片的寬和高,對于 shape 來說,默認(rèn)返回的是 - 1 ,如果我們設(shè)置了 size ,就會返回我們設(shè)置的值。這樣就能夠理解了。那么什么時候會用到 這兩個方法呢?主要用在哪? 看過 View 的測量方法的源碼應(yīng)該了解到,其中當(dāng)測量方式為 wrap_content 時,就是通過這兩個方法獲取背景默認(rèn)的寬和高。

3.4 LayerDrawable

LayerDrawable xml的標(biāo)簽是<layer-list>,它是將不同的 Drawable 放在不同的層次上,達(dá)到一種疊加的效果,主要語法如下:

<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android" >
<item
    android:drawable="@[package:]drawable/drawable_resource"
    android:id="@[+][package:]id/resource_name"
    android:top="dimension"
    android:right="dimension"
    android:bottom="dimension"
    android:left="dimension" />
</layer-list>

語法中一個 item 標(biāo)識一個 Drawable ,主要有 上下左右 幾個屬性,設(shè)置 每個 Drawable 相對于 View 邊緣的偏移量,單位為像素。android:drawable 屬性可以直接引用已有的一個 Drawable資源,也可以在 item 中自己定義一個 shape,默認(rèn)情況下, item 的大小是 View 的大小,對于 bitmap 來說,需要使用 android:gravity 才能控制圖片的顯示效果,分層的結(jié)果由于順序決定,上面的圖形會覆蓋下面的圖形,上下順序是有繪制的順序決定的,如,哪個 shape 先畫出來,哪個shape 就在下面。下面舉一個例子,就能明白了。

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item>
        <shape android:shape="oval">
            <solid android:color="@color/colorPrimaryDark" />
        </shape>
    </item>

    <item
        android:bottom="20dp"
        android:end="20dp"
        android:start="20dp"
        android:top="20dp">
        <shape android:shape="oval">
            <solid android:color="@color/colorAccent" />
        </shape>
    </item>

    <item
        android:bottom="40dp"
        android:end="40dp"
        android:start="40dp"
        android:top="40dp"
        android:gravity="center">
        <shape android:shape="oval">
            <solid android:color="@android:color/holo_green_light" />
        </shape>
    </item>

    <item
        android:bottom="60dp"
        android:end="60dp"
        android:start="60dp"
        android:top="60dp">
        <shape android:shape="oval">
            <solid android:color="@android:color/holo_orange_dark" />
        </shape>
    </item>

</layer-list>
layer

3.5 StateListDrawable

StateListDrawable 對于 xml 的 <selector> 標(biāo)簽,這個標(biāo)簽可以說是我們最常用的標(biāo)簽。它也是 Drawable 的集合,每個 Drawable 對應(yīng)一個 View 的狀態(tài),它會根據(jù) View 的狀態(tài)來選擇合適的 Drawable ,從而實現(xiàn)不同的背景效果。如,一個 Button ,設(shè)置 StateListDrawable 時,根據(jù)點擊按下時和松開時,選擇合適的 Drawable ,就可以達(dá)到給用戶點擊的感覺。其主要語法如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
    android:constantSize=["true" | "false"]
    android:dither=["true" | "false"]
    android:variablePadding=["true" | "false"] >
    <item
        android:drawable="@[package:]drawable/drawable_resource"
        android:state_pressed=["true" | "false"]
        android:state_focused=["true" | "false"]
        android:state_hovered=["true" | "false"]
        android:state_selected=["true" | "false"]
        android:state_checkable=["true" | "false"]
        android:state_checked=["true" | "false"]
        android:state_enabled=["true" | "false"]
        android:state_activated=["true" | "false"]
        android:state_window_focused=["true" | "false"] />
</selector>

state

selector標(biāo)簽的屬性含義如下:

android:constantSize
??StateListDrawable的固有大小是否隨著其狀態(tài)改變而改變,因為在狀態(tài)改變后,StateListDrawable會切換到別的Drawable,而不同的Drawable其大小可能不一樣。true表示大小不變,這時其固有大小是內(nèi)容所有Drawable的固有大小的最大值。false則會隨著狀態(tài)改變而改變,默認(rèn)值為false

android:variablePadding
??表示 StateListDrawable的padding是否隨狀態(tài)的改變而改變,默認(rèn)值為false,一般建議設(shè)置為false就行。

android:dither
??是否開啟抖動效果,開啟后可使高質(zhì)量的圖片在低質(zhì)量的屏幕上仍然有較好的顯示效果,一般建議開啟,設(shè)置為true。
??
測試?yán)?/p>

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/rectangle_r6_pressed" android:state_pressed="true" />

    <item android:drawable="@drawable/rectangle_r6_normal" android:state_pressed="false" />
</selector>

兩個圓角矩形

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="@color/colorAccent"/>
    <corners android:radius="6dp"/>

</shape>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="@android:color/darker_gray"/>
    <corners android:radius="6dp"/>

</shape>

效果:

state_gif

3.6 LevelListDrawable

LevelListDrawable對應(yīng)于<level-list>標(biāo)簽,也表示一個Drawable的集合,但集合中的每個Drawable都一個等級。根據(jù)不同等級,LevelListDrawable會切換到相應(yīng)的Drawable。語法如下:

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

屬性值的說明如下:

level

每個 item 表示一個 Drawable,并且有對應(yīng)的等級范圍,通過設(shè)定 android:maxLevel 和 android:minLevel 來指定,當(dāng)指定的值在這個范圍內(nèi)就會對應(yīng)相應(yīng)的 Drawable。當(dāng)然這個兩個值也是有范圍的,為 0-10000。

應(yīng)用場景,通過設(shè)定不同的 level 來切換 Drawable,實現(xiàn)不同的背景,下面用 ImageView 來測試下效果:

<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/pic"
        android:maxLevel="0"/>

    <item android:drawable="@drawable/ic_launcher_background"
        android:maxLevel="1"/>
</level-list>

在代碼中設(shè)置切換的動作:

    final ImageView imageView = findViewById(R.id.imageView2);
    imageView.setImageResource(R.drawable.level_list_test);
    imageView.setImageLevel(0);
    imageView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            imageView.getDrawable().setLevel(i++ % 2);
        }
    });

效果就不展示了。自己試試看。

3.7 TransitionDrawable

TransitionDrawable 用于實現(xiàn)兩個 Drawable 之間的淡入淡出的效果,它對應(yīng)的是 <transition>標(biāo)簽;其語法如下:

<?xml version="1.0" encoding="utf-8"?>
<transition
xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:drawable="@[package:]drawable/drawable_resource"
        android:id="@[+][package:]id/resource_name"
        android:top="dimension"
        android:right="dimension"
        android:bottom="dimension"
        android:left="dimension" />
</transition>

其中 android:top,android:bottom,android:left,android:right分別表示Drawable四周的偏移量。

android:id

資源ID, drawable 資源的唯一標(biāo)識。使用”@+id/name”方式來給這個item定義一個新的資源ID??梢允褂肰iew.findViewById()或者 Activity.findViewById()等方式檢索和修改這個item。

下面看一個例子:

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

    <item
        android:drawable="@drawable/pic1"
        android:top="10dp" />

    <item
        android:drawable="@drawable/pic2"
        android:top="10dp" />

</transition>

將 TransitionDrawable 作為背景

android:background="@drawable/transition_test"

TransitionDrawable 的第一個參數(shù)是漸變開始時的圖像,第二個參數(shù)是最終要顯示的圖像。

在代碼中獲取 TransitionDrawable,并設(shè)置持續(xù)時間,開始執(zhí)行淡入淡出的動作


    TextView textView = findViewById(R.id.text_view);
    TransitionDrawable drawable = (TransitionDrawable) textView.getBackground();
    drawable.startTransition(2000);

3.8 InsetDrawable

InsetDrawable 對應(yīng) <inset> 標(biāo)簽,它可以將其他Drawable內(nèi)嵌到自己當(dāng)中,并可以在四周預(yù)留出一定的間距。當(dāng)我們希望 View 的背景比實際區(qū)域小時,就可以采用 InsetDrawable 來實現(xiàn),個人認(rèn)為這個效果并沒有什么特殊之處,因為 layerDrawable 也是可以達(dá)到相同的預(yù)期效果的。其語法如下:

    <?xml version="1.0" encoding="utf-8"?>
    <inset
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@drawable/drawable_resource"
        android:insetTop="dimension"
        android:insetRight="dimension"
        android:insetBottom="dimension"
        android:insetLeft="dimension" />

這個比較簡單,就給出示例布局

    <?xml version="1.0" encoding="utf-8"?>
    <inset xmlns:android="http://schemas.android.com/apk/res/android"
        android:drawable="@drawable/rectangle_r6_pressed"
        android:insetTop="20dp"
        android:insetBottom="10dp"
        android:insetLeft="15dp"
        android:insetRight="5dp">
    
    </inset>  

3.9 ScaleDrawable

ScaleDrawable對應(yīng)<scale>標(biāo)簽,主要基于當(dāng)前的level,對指定的Drawable進(jìn)行縮放操作。

<?xml version="1.0" encoding="utf-8"?>
<scale
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/drawable_resource"
    android:scaleGravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
                          "fill_vertical" | "center_horizontal" | "fill_horizontal" |
                          "center" | "fill" | "clip_vertical" | "clip_horizontal"]
    android:scaleHeight="percentage"
    android:scaleWidth="percentage" />

  • android:gravity

當(dāng)圖片小于容器尺寸時,設(shè)置此選項可以對圖片經(jīng)典定位,這個屬性比較多,不同選項可以使用‘|’來組合使用。
??


ScaleDrawable_gravity
  • android:scaleHeight

表示Drawable的高的縮放比例,值越大,內(nèi)部Drawable的高度顯示得越小,例如android:scaleHeight=”70%”,那么顯示時Drawable的高度只有原來的30%。

  • android:scaleWidth

表示Drawable的寬的縮放比例,值越大,內(nèi)部Drawable的寬顯示得越小,例如android:scaleWidth=”70%”,那么顯示時Drawable的寬度只有原來的30%。

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/pic2"
    android:scaleGravity="center"
    android:scaleHeight="50%"
    android:scaleWidth="50%"
    android:level="50">
    <!--API 需要大于等 24-->

</scale>

還需要設(shè)置level,否則不起作用


ImageView scaleImage= (ImageView) findViewById(R.id.scaleImage);
ScaleDrawable scale= (ScaleDrawable) scaleImage.getBackground();
scale.setLevel(10);

此外在 API 高于或等于 24 時,可以在 XML 中直接設(shè)置 level。

需要特別注意的是我們?nèi)绻x好了 ScaleDrawable,要將其顯示出來的話,必須給 ScaleDrawable 設(shè)置一個大于 0 小于 10000 的等級(級別越大 Drawable 顯示得越大,等級為 10000 時就沒有縮放效果了),否則將無法正常顯示。我們可以看看其draw函數(shù)的源碼:

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

也相對比較簡單,效果可以自試一下,這里就不展示了。

3.10 ClipDrawable

ClipDrawable是通過設(shè)置一個Drawable的當(dāng)前顯示比例來裁剪出另一張Drawable,我們可以通過調(diào)節(jié)這個比例來控制裁剪的寬高,以及裁剪內(nèi)容占整個View的權(quán)重,通過ClipDrawable的setLevel()方法控制顯示比例,ClipDrawable的level值范圍在[0,10000],level的值越大裁剪的內(nèi)容越少,當(dāng)level為10000時則完全顯示,而0表示完全裁剪,不可見。需要注意的是在給clip元素中android:drawable屬性設(shè)置背景圖片時,圖片不能是9圖,因為這涉及到裁剪這張圖片,如果設(shè)置為九圖,裁剪的實際情況會與想要的效果不一樣。

ClipDrawable對應(yīng)xml的<clip>標(biāo)簽,其語法如下:

<?xml version="1.0" encoding="utf-8"?>
 <clip
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:drawable="@drawable/drawable_resource"
 android:clipOrientation=["horizontal" | "vertical"]
 android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" |
                  "fill_vertical" | "center_horizontal" | "fill_horizontal" |
                  "center" | "fill" | "clip_vertical" | "clip_horizontal"] />

其中android:clipOrientation和android:gravity屬性共同控制Drawable被裁剪的方向,其中clipOrientation表示裁剪的方向(水平和垂直兩種),gravity比較復(fù)雜必須和clipOrientation一起才能起作用,同樣的我們可以通過“|”來組合使用gravity的屬性值。gravity屬性值說明如下:

ClipDrawable

舉個例子

<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
    android:clipOrientation="horizontal"
    android:drawable="@drawable/pic2"
    android:gravity="center">

</clip>

然后需要在代碼中設(shè)置 level,通過設(shè)置 level 來控制裁剪的比例。


    ImageView clipImageView = findViewById(R.id.imageView5);
    ClipDrawable clipDrawable = (ClipDrawable) clipImageView.getDrawable();
    clipDrawable.setLevel(5000);
    

level 的最大值是 10000,設(shè)置為 5000 表示裁剪一半,如果設(shè)置為 4000,表示裁剪 60%,保留原來的 40%,裁剪方向為水平裁剪,左右同時裁剪。

3.11 ColorDrawable

ColorDrawable 是最簡單的Drawable,它實際上是代表了單色可繪制區(qū)域,它包裝了一種固定的顏色,當(dāng)ColorDrawable被繪制到畫布的時候會使用顏色填充Paint,在畫布上繪制一塊單色的區(qū)域。 在xml文件中對應(yīng)<color>標(biāo)簽,它只有一個android:color屬性,通過它來決定ColorDrawable的顏色。
xml實現(xiàn)如下:

<?xml version="1.0" encoding="utf-8"?>
<color xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="@color/colorPrimaryDark">
</color>
  

也可以使用代碼實現(xiàn),注意傳入的顏色值為16進(jìn)制的數(shù)字:

    ColorDrawable cd = new ColorDrawable(0xff000000);
    ImageView iv = (ImageView)findViewById(...);
    iv.setImageDrawable(cd);

自定義 Drawable

日常開發(fā)中使用自定義 Drawable 的場景不多,多數(shù)都是使用 XML 中定義一個 Drawable,已經(jīng)能夠滿足多數(shù)需求,另外,自定義的 Drawable 不能在 XML 中時候用,這就有了很大的限制。

在一些特殊場景可能需要用到自定義 Drawable,自定義 Drawable 其實主要重寫 draw 方法:

下面看一個例子

public class MyDrawable extends Drawable {

    private Paint mPaint;
    private int mPaintColor;

    public MyDrawable(int paintColor) {
        mPaintColor = paintColor;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(mPaintColor);
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        Rect r = getBounds();
        float centerX = r.exactCenterX();
        float centerY = r.exactCenterY();
        canvas.drawCircle(centerX,centerY,Math.min(centerX,centerY),mPaint);
    }

    @Override
    public void setAlpha(int i) {
        mPaint.setAlpha(i);
        invalidateSelf();
    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
        mPaint.setColorFilter(colorFilter);
        invalidateSelf();
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    @Override
    public int getIntrinsicWidth()
    {
        return 100;
    }

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

}

主要方法就是 draw 方法,比較簡單,就只設(shè)置顏色,然后使用畫筆畫一個圓形,其中 setAlpha、setColorFilter、getOpacity、這幾個方法也是必須實現(xiàn)的。
繪制的 Drawable 的大小是隨著 View 大小改變的,但是有一點需要注意,就是在 View 的 布局設(shè)置為 [wrap_content] 這時如果內(nèi)部是圖片,那么默認(rèn)的大小就是圖片的寬和高,如果 Drawable 的形狀是由顏色填充的,那么它的默認(rèn) 會返回 -1,就像我們的這個例子,此時需要設(shè)置一個默認(rèn)的寬和高,通過重寫 getIntrinsicWidth 和 getIntrinsicHeight 即可。如果 View 設(shè)置了大小或者 [match_parent],則不會有問題。

使用時創(chuàng)建 自定義的 Drawable,然后設(shè)置到 View 就行了。

    ImageView imageView6 = findViewById(R.id.imageView6);
    MyDrawable myDrawable = new MyDrawable(0xff255779);

    imageView6.setImageDrawable(myDrawable);

下面換一個樣式自定義 Drawable。有些場景,需要使用圓角圖或者圓形圖,你可能想到使用 Glide 或者 Fresco 加載到 ImageView 中,這完全可以,那還沒有其他的思路呢?
我們嘗試使用自定義 Drawable 的方式來試試,據(jù)說還很高效。主要也是通過重寫 draw 方法,使用通過給 Paint 設(shè)置 BitmapShader,然后繪制出圖片來,繪制前設(shè)置矩形圓角。
這里不再介紹 BitmapShader,可以看看鴻洋大神的講解,Android BitmapShader 實戰(zhàn) 實現(xiàn)圓形、圓角圖片

public class RoundImageDrawable extends Drawable {

    private Paint mPaint;
    private Bitmap mBitmap;

    private RectF rectF;

    public RoundImageDrawable(Bitmap bitmap) {
        mBitmap = bitmap;
        BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,
                Shader.TileMode.CLAMP);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setShader(bitmapShader);
    }

    @Override
    public void setBounds(int left, int top, int right, int bottom) {
        super.setBounds(left, top, right, bottom);
        rectF = new RectF(left, top, right, bottom);
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        canvas.drawRoundRect(rectF, 30, 30, mPaint);
    }

    @Override
    public int getIntrinsicWidth() {
        return mBitmap.getWidth();
    }

    @Override
    public int getIntrinsicHeight() {
        return mBitmap.getHeight();
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

}

使用圓角 Drawable:

    ImageView imageView = findViewById(R.id.round_imageView);

    Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.pic3);
    RoundImageDrawable roundImageDrawable = new RoundImageDrawable(bitmap);
    imageView.setImageDrawable(roundImageDrawable);

對于圓形也是一樣的,通過 drawCircle 實現(xiàn),不再給出代碼了??梢宰约涸囋嚒?/p>

好了,到這里對 Drawable 已經(jīng)有了一個全面的介紹,其實開發(fā)中常用的不會這么多,記住常用的幾種就可以了,其他的如果遇到可以查一查。主要還是靈活使用, 通過使用 Drawable 可以代替一些自定義 view,降低開發(fā)成本,所以拿到一些需求,看看效果能否用 Drawable 實現(xiàn),能的話就不用自定義 View;有時 UI 的小伙伴可能沒有給出圖片,那么我們可以通過使用一些 shape 來完成,讓 UI 的妹子刮目相看一下,哈哈!Over ~~

練習(xí)的代碼

參考

領(lǐng)略千變?nèi)f化的Android Drawable

Android Drawable 那些不為人知的高效用法

[android開發(fā)藝術(shù)探索]

[google android官網(wǎng)]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,825評論 25 709
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 13,918評論 2 59
  • //通過獲得資源文件進(jìn)行設(shè)置。根據(jù)不同的情況R.color.red也可以是R.string.red或者R.draw...
    gogoingmonkey閱讀 2,029評論 0 2
  • 隨著學(xué)歷水平的提高,在職人士感覺發(fā)展受限,壓力也在逐漸增大,因此想通過在職研究生來提高自己的學(xué)歷,但是想要...
    道法自然008閱讀 527評論 0 1
  • 總是在開始 常常覺得亂,事事物物都是有了開頭沒有結(jié)束。 想要瘦,又不去運動,還吃得多。 想要身體健康,又不注意飲食...
    麥粒醬閱讀 167評論 0 1

友情鏈接更多精彩內(nèi)容