使用裝飾器模式動態設置Drawable的ColorFilter
歡迎各位關注我的新浪微博:微博
轉載請標明出處(kifile的博客)
很多時候我們都希望Android控件點擊的時候,有按下效果,選中時有選中效果。通常我們都是通過使用selector來生成一個StateListDrawable來實現。
可是這樣我們會面臨一個問題,如果使用selector的xml文件生成,那麼對於不同的狀態,我們就會需要不同的圖片,才能夠實現drawable的動態改變。
可是有時候,我們的按下狀態同普通狀態之間唯一的區別只是顏色的差異。那麼這個時候,我們真的有必要在resources中放入多個顏色不同的圖片嗎?
或許很多人不會太在意幾張圖片的空間消耗,但是有時候,放着放着,包體就變大了。爲了減小包體,我們真的有必要只放置一張圖片,然後設置他在不同狀態下的色值。
首先附上我寫的一個Drawable裝飾器:
package com.kifile.android.drawable;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.StateSet;
/**
* 根據當前狀態選擇色值過濾器的Drawable.
* <p/>
* 使用{@link #addState(int[], int)} 添加色值.
*
* @author kifile
*/
public class ColorFilterStateListDrawable extends Drawable implements Drawable.Callback {
private Drawable mDrawable;
private StateListState mStateSets;
private int[] mCurrentState;
public ColorFilterStateListDrawable(Drawable drawable) {
if (drawable == null) {
throw new IllegalArgumentException("drawable cannot be null.");
}
mStateSets = new StateListState();
mDrawable = drawable;
mDrawable.setCallback(this);
}
public void addState(int[] stateSet, int color) {
mStateSets.addStateSet(stateSet, color);
}
@Override
public void draw(Canvas canvas) {
ColorFilter filter = selectColorFilter();
if (filter != null) {
mDrawable.setColorFilter(filter);
}
mDrawable.draw(canvas);
}
private ColorFilter selectColorFilter() {
if (mCurrentState == null) {
return null;
}
int index = mStateSets.indexOfStateSet(mCurrentState);
int color = mStateSets.getColor(index);
return new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP);
}
@Override
public boolean getPadding(Rect padding) {
return mDrawable.getPadding(padding);
}
@Override
public int getIntrinsicHeight() {
return mDrawable.getIntrinsicHeight();
}
@Override
public int getIntrinsicWidth() {
return mDrawable.getIntrinsicWidth();
}
@Override
public int getMinimumHeight() {
return mDrawable.getMinimumHeight();
}
@Override
public int getMinimumWidth() {
return mDrawable.getMinimumWidth();
}
@Override
public int getChangingConfigurations() {
return mDrawable.getChangingConfigurations();
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mDrawable.setBounds(bounds);
}
@Override
public void setAlpha(int alpha) {
mDrawable.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mDrawable.setColorFilter(cf);
}
@Override
public int getOpacity() {
return mDrawable.getOpacity();
}
@Override
protected boolean onStateChange(int[] state) {
mCurrentState = state;
return mDrawable.setState(state);
}
@Override
public boolean isStateful() {
return true;
}
@Override
public void invalidateDrawable(Drawable who) {
if (who == mDrawable && getCallback() != null) {
getCallback().invalidateDrawable(this);
}
}
@Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
if (who == mDrawable && getCallback() != null) {
getCallback().scheduleDrawable(this, what, when);
}
}
@Override
public void unscheduleDrawable(Drawable who, Runnable what) {
if (who == mDrawable && getCallback() != null) {
getCallback().unscheduleDrawable(this, what);
}
}
private static class StateListState {
private static final int INITIAL_SIZE = 5;
int mNumChildren;
int[][] mStateSets;
int[] mColors;
StateListState() {
mStateSets = new int[INITIAL_SIZE][];
mColors = new int[INITIAL_SIZE];
}
int addStateSet(int[] stateSet, int color) {
final int pos = addChild(color);
mStateSets[pos] = stateSet;
return pos;
}
public final int addChild(int color) {
final int pos = mNumChildren;
if (pos >= mColors.length) {
growArray(pos, pos + 10);
}
mColors[pos] = color;
mNumChildren++;
return pos;
}
private void growArray(int oldSize, int newSize) {
int[] newColors = new int[newSize];
System.arraycopy(mColors, 0, newColors, 0, oldSize);
mColors = newColors;
final int[][] newStateSets = new int[newSize][];
System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize);
mStateSets = newStateSets;
}
int indexOfStateSet(int[] stateSet) {
final int[][] stateSets = mStateSets;
final int N = getChildCount();
for (int i = 0; i < N; i++) {
if (StateSet.stateSetMatches(stateSets[i], stateSet)) {
return i;
}
}
return -1;
}
public final int getChildCount() {
return mNumChildren;
}
public int getColor(int index) {
if (index >= 0 && index < mColors.length) {
return mColors[index];
}
return 0;
}
}
}
簡單介紹一下寫這個裝飾器的基本思路吧
這個類其實也挺簡單的,就是使用裝飾器模式包裝了一個Drawable對象,然後將涉及到會引起界面變化的類都分發到所包裝的drawable裏。
核心代碼其實在於,初始化drawable的時候,通過addState加入一個狀態和該狀態指定的Color色值,然後在draw()的時候,通過當前的狀態去匹配當前應該顯示的色值,然後通過setColorFilter設置應該顯示的色值,從而令drawable顯示的色值發生變化。
另外匹配當前狀態的代碼參考自StateListDrawable。