Android 圖像繪制之 Drawable


Android 圖像繪制系列文章
Android圖像繪制之Bitmap
Android 圖像繪制之 Drawable
Android 圖像繪制之 Drawable(二)


不要沖動去自定義 View

記得當初學習 Android 的時候铜异,接觸到自定義 View 的時候昭齐,感覺逼格好高参淫。后來公司要開發(fā)一個鎖屏界面 ,科長讓我接下這個任務帶領同事開發(fā),嚇得我當時就接受了這個任務馏谨。然后在跌跌撞撞中完成了,效果我還算滿意。但是后來我仔細回想下開發(fā)過程乞娄,很多問題我都給自己一個問號。那么今天我提一個問題,為什么要自定義 View仪或?

舉個常見例子确镊,我想顯示一張圓形圖片。說實話范删,當初的我第一想法就是繼承 View蕾域,重寫 onDraw() 方法。后來回想下到旦,MDZZ旨巷,Android 能不能智能點,這也需要我自定義 View添忘,萬一再要顯示一張圓形純背景色采呐,我豈不又要自定義 View。 其實我可以告訴你搁骑,繪制簡單的圖形斧吐,以及顯示靜態(tài)的圖片,可以用 Drawable仲器,而不用自定義 View煤率。例如顯示一張圓形圖片,我其實可以用 RoundBitmapDrawable娄周,這個后面我們會看到涕侈。

Drawable是什么

查了下官方文檔,Drawable 是一個抽象的概念煤辨,“something that can be drawn”裳涛,就我的理解話,Drawable 是一種媒介众辨,它可以把內(nèi)容繪制到 Canvas 上端三。

如果你還沒理解,舉個例子鹃彻,BitmapDrawable 包裝了一個 Bitmap郊闯,如果你還不了解 Bitmap,請看我的文章 Android 圖像繪制之 Bitmap蛛株。其實 BitmapDrawable 最終也是調(diào)用了 Canvas 的 drawBitmap() 方法來把 Bitmap 繪制圖像到 View 中的团赁。

Drawable的實現(xiàn)類

查了下 Drawable 的實現(xiàn)類,如ShapeDrawable,BitmapDrawable,ClipDrawable谨履,RoundBitmapDrawable,StateListDrawableRoateDrawable欢摄。從類的名子來看,大致可以猜到這些類是干嘛的吧笋粟,是不是有種相見恨晚的感覺怀挠,那么本篇文章就是介紹這些類析蝴。當然網(wǎng)上很多文章都有寫過,本篇文章不注重摳每個細節(jié)绿淋,但是會讓你感受到這個類到底實際中有何用闷畸。

GradientDrawable

一個創(chuàng)建圖形的 Drawable,圖形包括直線(line), 橢圓(oval)吞滞,圓形(oval佑菩,當橢圓的寬高相等的時候就是圓形),矩形(rectangle)冯吓,圓環(huán)(ring)倘待。不過在這之前,我需要你對 Paint 有所了解组贺,目前我還沒有寫關于 Paint 的文章凸舵,我希望你能從其他文章了解下,這樣能更好理解 GradientDrawable 失尖。

GradientDrawable 它在 XML 文件中對應的根節(jié)點為 <Shape>啊奄,但是它生成的不是 ShapeDrawable,而是 GradientDrawable掀潮,但是又達到了 ShapeDrawable 的效果菇夸, 我查了下官方文檔,沒有錯仪吧,這 TM 就有點搞事了庄新。如果有人知道,可以告訴我為什么。

直線

<?xml version="1.0" encoding="utf-8"?>
<shape    
    xmlns:android="http://schemas.android.com/apk/res/android"    
    android:shape="line">    
    <stroke android:width="5dp" android:color="#ffff0000"/>
</shape>
line.PNG

橢圓或者圓形

<?xml version="1.0" encoding="utf-8"?>
<shape    xmlns:android="http://schemas.android.com/apk/res/android"    
android:shape="oval">    
<stroke android:width="5dp" android:color="#ffff0000"/>    
</shape>
oval.PNG

如果我把定義寬和高相等的話就是圓形

<?xml version="1.0" encoding="utf-8"?>
<shape    xmlns:android="http://schemas.android.com/apk/res/android"    
android:shape="oval">    
    <stroke android:width="1dp" android:color="#ffff0000"/>    
    <size android:width="100dp" android:height="100dp"/>    
</shape>
circle.PNG

如果不用 Stroke( 描邊,對應 Paint.STROKE )梗醇,而用Solid (填充查乒,對應 Paint.FILL)肴捉,會出現(xiàn)實心圓形。

circle_fill.PNG

填充的顏色也可以搞個漸變色

<?xml version="1.0" encoding="utf-8"?>
<shape 
    xmlns:android="http://schemas.android.com/apk/res/android"    
    android:shape="oval">    
<gradient        
    android:startColor="@color/colorAccent"    
    android:endColor="@color/colorPrimary"/>    
<size 
    android:width="100dp" 
    android:height="100dp"/>
</shape>
gradient_circle.png

矩形

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

<stroke 
    android:width="1dp" 
    android:color="@color/colorAccent"/>    
<size        
    android:width="100dp"        
    android:height="100dp"/>
</shape>
rectange.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="10dp"       
    android:useLevel="false">    
<solid 
    android:color="@color/colorAccent"/>
</shape>
ring.PNG
  1. 這里要說下這個 useLevel, 只有在 LevelListDrawable 中才設置 true,這里要設置 false荷科,否則顯示不出來。
  1. 其實 GradientDrawable 或者說是 ShapeDrawable 的繪制原理就是利用 Canvas.drawCircle() 纱注,Canvas.drawRect() 等等方法(Canvas 我將在后面的文章介紹)畏浆。

BitmapDrawable

BitmapDrawable 主要是用于圖片的平鋪( tileMode )顯示,我們先看看沒有設置 tileMode 的 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:mipMap="true"         
    android:src="@drawable/sample_1">
</bitmap>

antialias:抗鋸齒
dither:抗抖動狞贱,當位圖的像素配置與屏幕不同時(例如 ARGB_8888 的位圖和 RGB_565 的屏幕)
filter:過濾刻获。當位圖收縮或者拉伸,為了使外觀平滑使用過濾
mipMap:一種圖像處理技術 斥滤,主要用于在圖片縮小時将鸵,還要獲得高質量圖片。

BitmapDrawable.PNG

BitmapDrawable 的側重功能屬性在于 android:tileMode=["disabled" | "clamp" | "repeat" | "mirror"]

disable:不解釋佑颇,默認不平鋪
clamp:當位圖小于實際顯示區(qū)域的時候顶掉,邊緣的顏色拉伸
repeat:當位圖小于實際顯示區(qū)域的時候,水平和垂直方向挑胸,重復顯示圖片
mirror:當位圖小于實際顯示區(qū)域的時候痒筒,水平和垂直方向,以鏡像的方式(或者說對稱的方式)重復顯示圖片

為了說明 tileMode 茬贵,先看一張原圖

origin.PNG

repeat

repeat.PNG

mirror

mirror.PNG

Clamp

這個就有點意思了簿透,我看到了一個實際用法(參考博客http://blog.csdn.net/u012702547/article/details/51594131

不會 PS 是硬傷,只能借用下別人的圖了解藻。但是我們主要學習這個實際中怎么應用老充。

原圖是這樣

clamp_origin.PNG

clamp 平鋪后

clamp.PNG

把它應用到 TextView 背景后

<TextView    
    android:id="@+id/image_view"        
    android:layout_width="match_parent"     
    android:layout_height="match_parent"    
    android:background="@drawable/bitmapfile"      
    android:padding="40dp"    
    android:text="@string/bitmap_clamp_text"    
    android:textSize="20sp"/>
clamp_bg.PNG

LayerListDrawable

圖層列表,就像我們平時把一張照片放在另外一張照片上螟左,最后的照片在最上面啡浊。 LayerListDrawable 就是這樣設計的。

<?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/colorAccent"/>        
    </shape>    
</item>    
    <item android:width="64dp" android:height="64dp">        
        <bitmap           
            android:gravity="center"            
            android:src="@android:drawable/ic_input_add"/>      
    </item>
</layer-list>
LayerListDrawable.PNG

這個是不是有點像 FloatingActionButton胶背,把這個設置為 Button 的背景巷嚣,然后設置 android:elevation,是不是就 perfect了钳吟。

使用注意事項

  1. 我們用 <item> 控制顯示的大小和位置廷粒,而用<item> 的子節(jié)點控制顯示區(qū)域,如 <bitmap>红且,<shape>坝茎,<nine-path> 等等 Drawable
  2. 不用<item>顯示圖片因為圖片會根據(jù)顯示區(qū)域縮放,如果我們將圖片放在子節(jié)點中直焙,例如 <bitmap>景东,就算圖片小于顯示區(qū)域也不會放大。

StateListDrawable

在 MD 設計之前奔誓,大家應該有個印象斤吐,在點擊像 ListView 的 Item 的時候,是沒有波紋效果的厨喂,我們往往會自己設計一個簡單的效果和措,就是點擊 Item 的時候,背景切換個顏色蜕煌。而這個就是用 StateListDrawable派阱。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">    
  <item android:state_pressed="true">        
    <shape>            
        <corners android:radius="10dp"/>            
        <solid android:color="@color/colorAccent"/>              
    </shape>    
  </item>    

  <item>        
    <shape>            
        <corners android:radius="10dp"/>            
        <stroke 
            android:width="1dp"   
            android:color="#685252"/>        
    </shape>    
  </item>
</selector>

設置為 Button 的 background 后效果如下

StateListDrawable.gif

LevelListDrawable

根據(jù)等級(level)來顯示圖片。典型的應用就是 WIFI 信號斜纪。用 setLevel()來控制 level贫母,根據(jù) level 所在的范圍顯示相應的圖片文兑。

<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" />
    <item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_2" />
    <item android:maxLevel="5" android:drawable="@drawable/ic_wifi_signal_3" />
    <item android:maxLevel="9" android:drawable="@drawable/ic_wifi_signal_4" />
</level-list>

maxLevel = 0 就是默認圖片,看下默認效果

default.PNG

現(xiàn)在給 ImageView 背景

    <ImageView
        android:layout_centerInParent="true"
        android:id="@+id/wifi_image"
        android:onClick="onClickWifi"
        android:background="@drawable/level_list_drawable"
        android:layout_width="200dp"
        android:layout_height="200dp"/>
    private static int i = 1;
    public void onClickWifi(View view) {
        ImageView imageView = (ImageView) findViewById(R.id.wifi_image);
        imageView.getBackground().setLevel(i++ % 10);
    }

現(xiàn)在效果腺劣,每點擊一次 ImageView绿贞,level 改變一次。第一次點擊 橘原,level 是1籍铁,顯示 maxLevel="2" 圖片,再點擊一次趾断,level 是2拒名,還是顯示 maxLevel="2" 圖片,依此類推芋酌,出現(xiàn)如下效果

levelListDrawable.gif

TransitionDrawable

在兩個圖像之間增显,通過改變 alpha 來交替顯示。

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

    <item>
        <bitmap android:src="@drawable/sample_1"/>
    </item>
</transition>

為 ImageView 設置這個背景

    <ImageView
        android:id="@+id/transition"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/transition_drawable"
        android:onClick="onClickTransition"/>

實現(xiàn)點擊事件

    private boolean isReverse;
    public void onClickTransition(View view) {
        ImageView imageView = (ImageView) findViewById(R.id.transition);
        TransitionDrawable drawable = (TransitionDrawable) imageView.getBackground();
        if (!isReverse) {
            drawable.startTransition(2000);
            isReverse = true;
        } else {
            drawable.reverseTransition(2000);
            isReverse = false;
        }
    }
TransitionDrawable.gif

InsetDrawable

看這個命名脐帝,意思是 插入式的 Drawable甸怕。也就是說,可以按照指定的位置(left,top,right,bottom)來顯示圖片

<?xml version="1.0" encoding="utf-8"?>
<inset
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/sample_1"
    android:insetLeft="20dp"
    android:insetRight="10dp"
    android:insetTop="10dp"/>
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="@color/colorAccent"
        android:src="@drawable/inset_drawable"/>
InsetDrawable.PNG

可以看到 left , top , right 有明顯的 padding

ClipDrawable

從名字上看腮恩,意思是可以裁減的 Drawable梢杭。

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

clipOrientation 控制裁減的方向
gravity 控制從哪里開始裁減

布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <ImageView
        android:id="@+id/clip_image"
        android:background="@drawable/clip_drawable"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <SeekBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/seekBar2"/>
</LinearLayout>

Java Code

        final ImageView imageView = (ImageView) findViewById(R.id.clip_image);
        SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar2);
        seekBar.setMax(10000);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                imageView.getBackground().setLevel(i);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
ClipDrawable.gif

ScaleDrawable

意思是可縮放的 Drawable。不過用起來有點費勁秸滴。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">


    <ImageView
        android:id="@+id/scale_image"
        android:layout_width="match_parent"
        android:layout_height="450dp"
        android:background="#FF4081"
        android:src="@drawable/scale_drawable"/>

    <SeekBar
        android:layout_alignParentBottom="true"
        android:id="@+id/seekBar3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"/>
</RelativeLayout>

scaleGravity 控制顯示的位置

    <ImageView
        android:id="@+id/scale_image"
        android:layout_width="match_parent"
        android:layout_height="450dp"
        android:background="#FF4081"
        android:src="@drawable/scale_drawable"/>

基本設置已經(jīng)完畢武契,但是就是不顯示,我們需要控制 ScaleDrawable 的 Level荡含,我們加入一個 SeekBar 控制 Level

        final ImageView imageView = (ImageView) findViewById(R.id.scale_image);
        SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar3);
        seekBar.setMax(10000);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                imageView.getDrawable().setLevel(i);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
    }

level 控制縮小的比例的咒唆,level 值是從 0 到 10000,0代表不顯示释液,從1開始控制圖片放大全释,10000就是原圖,看下面效果演示

ScaleDrawable.gif
  1. android:level 屬性在 API 24 版本后误债,可以在 XML 文件中添加
  2. android:useIntrinsicSizeAsMinimum="true"浸船,這個設置為 true 后,就會一直顯示原圖的大小寝蹈。
  3. 如果你想知道為什么李命,可以探究源碼 ScaleDrawable.java 的 onBoundsChange() 方法

結束

通過本篇文章,我們要達到兩點效果箫老,一是 Drawable 是什么封字,二是為什么要用 Drawable。

由于篇幅原因,現(xiàn)在就介紹這么多阔籽,在下篇會把 NinePathDrawable流妻,ColorDrawable,RoundBitmapDrawable笆制,VectorDrawable 也加進來合冀。同時在后面的文章中,我會介紹如何自定義 Drawable项贺,讓大家逐步感受到其實不用自定義 View 也能做很多事情。

如果大家有何疑問峭判,可以正文評論开缎,我會盡量解答,大家大同學習進步林螃。


如果您喜歡這類文章 奕删,不妨點個贊,甚至可以關注一波我 不惜留戀_疗认,感謝閱讀~

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末完残,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子横漏,更是在濱河造成了極大的恐慌谨设,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缎浇,死亡現(xiàn)場離奇詭異扎拣,居然都是意外死亡,警方通過查閱死者的電腦和手機素跺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門二蓝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人指厌,你說我怎么就攤上這事刊愚。” “怎么了踩验?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵鸥诽,是天一觀的道長。 經(jīng)常有香客問我箕憾,道長衙传,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任厕九,我火速辦了婚禮蓖捶,結果婚禮上,老公的妹妹穿的比我還像新娘扁远。我一直安慰自己俊鱼,他們只是感情好刻像,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著并闲,像睡著了一般细睡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帝火,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天溜徙,我揣著相機與錄音,去河邊找鬼犀填。 笑死蠢壹,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的九巡。 我是一名探鬼主播图贸,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼冕广!你這毒婦竟也來了疏日?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤撒汉,失蹤者是張志新(化名)和其女友劉穎沟优,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體睬辐,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡净神,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了溉委。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹃唯。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖瓣喊,靈堂內(nèi)的尸體忽然破棺而出坡慌,到底是詐尸還是另有隱情,我是刑警寧澤藻三,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布洪橘,位于F島的核電站,受9級特大地震影響棵帽,放射性物質發(fā)生泄漏熄求。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一逗概、第九天 我趴在偏房一處隱蔽的房頂上張望弟晚。 院中可真熱鬧,春花似錦、人聲如沸卿城。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瑟押。三九已至搀捷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間多望,已是汗流浹背嫩舟。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留怀偷,地道東北人家厌。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像枢纠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子黎棠,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355

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