Drawable是一种抽象的可以被绘制的对象。和View不同的是,Drawable不能接收事件,也不能和用户进行交互。
Drawable定义了一些通用机制,供客户端调用。例如setBounds是用来确定Drawable绘制的位置和大小的,因此必须被调用。可以通过缩放来改变Drawable大小。可以通过getIntrinsicHeight和getIntrinsicWidth获取Drawable宽和高。
Drawable的展现形式有多种:
Bitmap:最简单的Drawable,是一张PNG或JPEG图片。
Nine Patch:一种扩展的PNG,(.9)可以进行拉伸和指定内容显示范围。
Shape:不是原始位图,可以更好的调整大小。
Layers:多层Drawable,层层绘制
States:(selector)根据状态从集合中选择Drawable。
Levels:根据level选择drawable
Scale:根据level对图片进行缩放。
Drawable 提供了一个回调接口,Callback。如果你想为Drawable子类创建一个动画drawable,可以实现这个接口。
这个回调接口提供了三个方法,
当drawable需要重画的时候会调用这个方法,此时设置drawable的view会被刷新。
public void invalidateDrawable(Drawable who);
可以调用这个方法执行下一帧动画,实现这个接口的类可以简单的调用Handler.postAtTime(Runnable, Object, long)方法
public void scheduleDrawable(Drawable who, Runnable what, long when);
与scheduleDrawable相对应,取消要执行的任务,通过调用Handler.removeCallbacks(Runnable, Object)方法。
public void unscheduleDrawable(Drawable who, Runnable what);
Drawable的经常使用的方法有以下几个:
为Drawable指定一个矩形的边界
public void setBounds(int left, int top, int right, int bottom) {
Rect oldBounds = mBounds;
if (oldBounds == ZERO_BOUNDS_RECT) {
oldBounds = mBounds = new Rect();
}
if (oldBounds.left != left || oldBounds.top != top ||
oldBounds.right != right || oldBounds.bottom != bottom) {
//如果要设置的边界和之前的不相同,并且不是第一次设置,会调用invalidateSelf()方法,这个方法里会判断是否设置了回调,如果设置了,调用callback的invalidateDrawable()方法。
if (!oldBounds.isEmpty()) {
// first invalidate the previous bounds
invalidateSelf();
}
//给mBounds重新赋值。
mBounds.set(left, top, right, bottom);
onBoundsChange(mBounds);//子类重写这个方法来实现自己的逻辑。
}
}
根据bounds(必须)、透明度、颜色过滤器等进行绘制。
public abstract void draw(Canvas canvas);
为drawable设置透明度
public abstract void setAlpha(int alpha);
给支持Xfermode的drawable设置Xfermode。
public void setXfermode(Xfermode mode) {
// Base implementation drops it on the floor for compatibility. Whee!
}
设置颜色过滤器,如果设置了ColorFilter ,每个像素会被修改。
public abstract void setColorFilter(@Nullable ColorFilter colorFilter);
为drawable设置着色的颜色。在绘制到屏幕之前,Drawable绘制的内容会和这个颜色混合在一起。
public void setTint(@ColorInt int tintColor) {
setTintList(ColorStateList.valueOf(tintColor));
}
为Drawable指定一组状态,例如获取焦点,按下,选中等。如果新的状态引起drawble外观的变化,会调用invalidateSelf,进行重画。
需要注意的是:Drawable持有stateSet的引用,直到被设置了新的状态数组,所以stateSet使用期间不能对它进行修改。
public boolean setState(final int[] stateSet) {
if (!Arrays.equals(mStateSet, stateSet)) {
mStateSet = stateSet;
return onStateChange(stateSet);
}
return false;
}
获取drawable的宽度,如果没有宽度返回-1,例如颜色值。
public int getIntrinsicWidth() {
return -1;
}
使Drawable状态可变,这个操作不能逆转。一个可变的Drawable不会和其他Drawable共享状态。如果需要修改从resources加载的drawable的属性,这个方法非常有用。默认情况下,同一个资源文件,虽然有多个实例,但是它们共享共同的状态,如果修改了其中任意一个实例的状态,其他的也会接收到相同的变化。调用这个方法得到的Drawable,修改状态后不会影响其他实例。
public Drawable mutate() {
return this;
}
Drawable还有一个很重要的类是ConstantState,每一种Drawable都有自己的实现类。这个类用来存储drawables之间的共享常量状态和数据。例如从同一个资源文件创建的BitmapDrawables,它们实例里的ConstantState里会存储同一个位图。
public static abstract class ConstantState {
在不提供Resources情况下生成一个Drawable。
注意:使用此方法会导致依赖于密度的drawable(例如Bitmap)无法正确的更新目标密度(图片的大小取决于屏幕密度和所在drawable的文件夹的密度)。所以应该调用newDrawable(Resources)这个方法。
public abstract Drawable newDrawable();
根据ConstantState生成新的Drawable。基于密度变化的drawable必须实现此方法。(例如Bitmap)
public Drawable newDrawable(Resources res) {
return newDrawable();
}
根据ConstantState生成新的Drawable。设置了主题的drawable必须实现此方法。
public Drawable newDrawable(Resources res, Theme theme) {
return newDrawable(null);
}
}
根据资源id获取Drawable一般调用这个方法
getResources().getDrawable(@DrawableRes int id, @Nullable Theme theme);(api21)
这个方法里调用了loadDrawable(value, id, theme);重点来看下这个方法是如何加载drawable的。
@Nullable
Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {
//...................根据value.data 得到key值
// 首先根据key从DrawableCache里查找是否有drawable缓存,drawable已经被加载过,并设置了指定的主题。DrawableCache.getInstance方法先根据key和theme找到ConstantState,如果ConstantState有值,会调用newDrawable(Resources res, Theme theme)方法生成一个新的Drawable对象,所以尽管实例不一样,但是共享同一个ConstantState。
if (!mPreloading) {
final Drawable cachedDrawable = caches.getInstance(key, theme);
if (cachedDrawable != null) {
return cachedDrawable;
}
}
//对drawable进行预加载,没有设置主题的一些属性
final ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
Drawable dr;
if (cs != null) {
dr = cs.newDrawable(this);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
//如果是xml文件,会使用XmlResourceParser生成drawable。否则mAssets会根据资源文件生成InputStream,然后调用Drawable.createFromResourceStream生成drawable。
dr = loadDrawableForCookie(value, id, null);
}
//生成drawble以后,如果提供了主题,会为drawable设置主题,
final boolean canApplyTheme = dr != null && dr.canApplyTheme();
if (canApplyTheme && theme != null) {
dr = dr.mutate();
dr.applyTheme(theme);
dr.clearMutated();
}
最后把DrawableCache 、theme等进行缓存。
if (dr != null) {
dr.setChangingConfigurations(value.changingConfigurations);
cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
}
return dr;
}