在 Android 開發中,會大量使用到自定義View,于是在很多開發者心里形成了自定義View優先的原則,需要一個新的效果,沒問題,寫個自定義View。但是寫個自定義View還是一個比較麻煩的事,比如說需要復寫onMeasure
,onDraw
,要考慮padding
,考慮wrap_content
等等。但是很多效果都沒必要自定義View,寫個Drawable
就可以了。
Android 提供的 Drawable
Drawable,表示一個能夠在Canvas
上繪制的抽象的概念。相比于View,它相當的輕量級,不用考慮measure
、layout
,需要考慮的僅僅是如何繪制,即draw
函數。
在 Android 系統中,包含了幾種已經定義好的 Drawable,它們和它們對應的 XML 標簽如下所示:
Drawable類 | 對應的xml標簽 | 描述 |
---|---|---|
BitmapDrawable | bitmap | 就是表示一張圖片 |
ShapeDrawable | shape | 通過顏色構建的圖形 |
LayerDrawable | layer-list | 表示層次化的Drawable集合 |
StateListDrawable | selector | Drawable集合,其中每個Drawable對應一個狀態 |
LevelListDrawable | level-list | Drawable集合,其中每個Drawable對應一個等級 |
TransitionDrawable | transition | 用于實現兩個Drawable直接按的淡入淡出效果 |
InsetDrawable | inset | 可以將其他Drawable內嵌到自己當中 |
ScaleDrawable | scale | 可將指定的Drawable縮放到一定比例 |
ClipDrawable | clip | 根據當前等級來裁剪另一個Drawable |
以上是Android 系統中提供的一些Drawable,有些可能很少用,但是知道一些也是好的。另外需要說的就是關于Drawable的寬高。
Drawable作為一個能夠繪制到Canvas上的對象,是沒有寬高的概念的。這個可能不好理解,但是想一想這個問題就知道了:Drawable既然能在Canvas上繼續進行繪制,那么它也不會知道Canvas是多寬多高,不管Canvas是多大的,它都必須能夠保證正常的繪制,當作用于View的背景時,Drawable會被拉伸至View的同等大小。但是大家會注意到兩個函數:
int Drawable.getIntrinsicHeight() //獲取其固有高度
int Drawable.getIntrinsicWidth() //獲取其固有寬度
這兩個參數并不是每個Drawable都具有,對于大多數Drawable來說,它們都是返回-1的,但是對于BitmapDrawable,它的固有寬高就是圖片的寬高。這兩個值還會影響到View的測量,關于這一點請看后面例子。
自定義 Drawable
Drawable的最主要的使用場景一是作用于ImageView,二是作為View的背景。自定義的Drawable是無法在XML中使用的。自定義一個Drawable僅僅需要復寫幾個函數,可以說是相當的容易。下面的例子就是自定義一個圓角的Drawable,當它作用于View的背景時會繪制一個圓角的純色背景,而關于每個函數的意義和注意事項都寫在注釋里了。
public class RoundColorDrawable extends Drawable {
private Paint paint;
public RoundColorDrawable() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLUE);
}
public void setColor(int color) {
paint.setColor(color);
}
/**
* 重點函數,在該函數中需要處理該Drawable如何進行繪制,并使用Canvas的方法將需要繪制的東西
* 繪制在畫布上。
* @param canvas 該Drawable將要繪制到的畫布,一般情況下,這個{@link Canvas} 對象就是
* View在 onDraw 方法中的 畫布。
*/
@Override
public void draw(@NonNull Canvas canvas) {
//這里僅僅繪制了一個于canvas尺寸大小相同的圓角矩形
canvas.drawRoundRect(new RectF(0, 0, canvas.getWidth(), canvas.getHeight()), 40, 40, paint);
}
@Override
public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
paint.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
paint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
/**
* 返回固有的高度,會影響到測量
*/
@Override
public int getIntrinsicHeight() {
return 200;
}
/**
* 返回固有寬度,會影響到測量
*/
@Override
public int getIntrinsicWidth() {
return -1;
}
}
需要注意的是,繼承Drawable必須復寫4個函數:draw
、setAlpha
、setColorFilter
、getOpacity
。這4個函數的覆寫都是比較容易的。另外兩個返回固有寬高的函數:getIntrinsicHeight
、getIntrinsicWidt
h,不強制覆寫,但是對它們的意義要了解,因為它們會設計到 View 的測量,其返回了固定值之后則View必須會撐大到能夠包裹住這個尺寸,例如這里的getIntrinsicHeight
高度返回了200,那么將這個Drawable作為View的背景時,View的尺寸肯定要大于等于200。
下面就將這個Drawable引用到代碼中,布局比較簡單就不貼出來了,下面是代碼部分:
setContentView(R.layout.image_test);
imageView = (ImageView) findViewById(R.id.image_view);
textView = (TextView) findViewById(R.id.text_view);
RoundColorDrawable drawable = new RoundColorDrawable();
imageView.setImageDrawable(drawable);
textView.setBackground(drawable);
效果圖如下,上面是TextView
,中間則是一個ImageView
: