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>
橢圓或者圓形
<?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>
如果我把定義寬和高相等的話就是圓形
<?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>
如果不用 Stroke( 描邊,對應 Paint.STROKE )梗醇,而用Solid (填充查乒,對應 Paint.FILL)肴捉,會出現(xiàn)實心圓形。
填充的顏色也可以搞個漸變色
<?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>
矩形
<?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>
圓環(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>
- 這里要說下這個 useLevel, 只有在 LevelListDrawable 中才設置 true,這里要設置 false荷科,否則顯示不出來。
- 其實 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 的側重功能屬性在于 android:tileMode=["disabled" | "clamp" | "repeat" | "mirror"]
disable:不解釋佑颇,默認不平鋪
clamp:當位圖小于實際顯示區(qū)域的時候顶掉,邊緣的顏色拉伸
repeat:當位圖小于實際顯示區(qū)域的時候,水平和垂直方向挑胸,重復顯示圖片
mirror:當位圖小于實際顯示區(qū)域的時候痒筒,水平和垂直方向,以鏡像的方式(或者說對稱的方式)重復顯示圖片
為了說明 tileMode 茬贵,先看一張原圖
repeat
mirror
Clamp
這個就有點意思了簿透,我看到了一個實際用法(參考博客http://blog.csdn.net/u012702547/article/details/51594131)
不會 PS 是硬傷,只能借用下別人的圖了解藻。但是我們主要學習這個實際中怎么應用老充。
原圖是這樣
clamp 平鋪后
把它應用到 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"/>
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>
這個是不是有點像 FloatingActionButton胶背,把這個設置為 Button 的背景巷嚣,然后設置 android:elevation,是不是就 perfect了钳吟。
使用注意事項
- 我們用 <item> 控制顯示的大小和位置廷粒,而用<item> 的子節(jié)點控制顯示區(qū)域,如 <bitmap>红且,<shape>坝茎,<nine-path> 等等 Drawable
- 不用<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 后效果如下
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 就是默認圖片,看下默認效果
現(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)如下效果
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;
}
}
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"/>
可以看到 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) {
}
});
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就是原圖,看下面效果演示
- android:level 屬性在 API 24 版本后误债,可以在 XML 文件中添加
- android:useIntrinsicSizeAsMinimum="true"浸船,這個設置為 true 后,就會一直顯示原圖的大小寝蹈。
- 如果你想知道為什么李命,可以探究源碼 ScaleDrawable.java 的 onBoundsChange() 方法
結束
通過本篇文章,我們要達到兩點效果箫老,一是 Drawable 是什么封字,二是為什么要用 Drawable。
由于篇幅原因,現(xiàn)在就介紹這么多阔籽,在下篇會把 NinePathDrawable流妻,ColorDrawable,RoundBitmapDrawable笆制,VectorDrawable 也加進來合冀。同時在后面的文章中,我會介紹如何自定義 Drawable项贺,讓大家逐步感受到其實不用自定義 View 也能做很多事情。
如果大家有何疑問峭判,可以正文評論开缎,我會盡量解答,大家大同學習進步林螃。
如果您喜歡這類文章 奕删,不妨點個贊,甚至可以關注一波我 不惜留戀_疗认,感謝閱讀~