使用裝飾器模式動態設置Drawable的ColorFilter

使用裝飾器模式動態設置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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章