StateListDrawable初始化、繪制、setColorFilter講解

該篇是繼上兩篇文章分析的,主要分析了background到view顯示的流程,以及后面也分析了foregrounds在view上顯示的過程,后面也介紹了foreground顯示水波效果,以及如何自定義foreground的水波效果顏色,如果還沒有看前面兩節(jié)的內(nèi)容,大家先看看前兩節(jié)的內(nèi)容:

StateListDrawable的state初始化

還記得在第一篇介紹drawable顯示到view的過程說過,通過xml各種標(biāo)簽名生成drawable的時候,后面繼續(xù)調(diào)用了drawable的inflate方法吧,咱們就順著這個方向看下inflate方法做了啥,直接看StateListDrawable下面的inflate方法:

@Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
        throws XmlPullParserException, IOException {
    final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawable);
    super.inflateWithAttributes(r, parser, a, R.styleable.StateListDrawable_visible);
    updateStateFromTypedArray(a);
    updateDensity(r);
    a.recycle();
    //方法很重要,用來獲取xml中的屬性
    inflateChildElements(r, parser, attrs, theme);
    //獲取完屬性之后,觸發(fā)state的改變
    onStateChange(getState());
}

看注釋一,調(diào)用了inflateChildElements方法:

private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attire
        Theme theme) throws XmlPullParserException, IOException {
    final StateListState state = mStateListState;
    final int innerDepth = parser.getDepth() + 1;
    int type;
    int depth;
    //遍歷里面的每一個item標(biāo)簽
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && ((depth = parser.getDepth()) >= innerDepth
            || type != XmlPullParser.END_TAG)) {
        if (type != XmlPullParser.START_TAG) {
            continue;
        }
        //如果里面的標(biāo)簽不是item直接跳出該處循環(huán)
        if (depth > innerDepth || !parser.getName().equals("item")) {
            continue;
        }
        final TypedArray a = obtainAttributes(r, theme, attire
                R.styleable.StateListDrawableItem);
        //如果當(dāng)前屬性值直接是一個drawable的話,而不是一個xml文件,直接返回drawable
        Drawable dr = a.getDrawable(R.styleable.StateListDrawableItem_drawable);
        a.recycle();
        //拿到item下面的各種狀態(tài)值
        final int[] states = extractStateSet(attires
        if (dr == null) {
            //如果drawable屬性是單獨(dú)的xml文件,還得繼續(xù)去解析drawable下面的xml文件
            dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
        }
        //最后將不同的狀態(tài)加到StateListState里面
        state.addStateSet(states, dr);
    }
}

上面代碼是解析每一個item標(biāo)簽,如果標(biāo)簽里面drawable的值直接是一個值,而不是一個drawable的xml文件的時候直接返回dr,通過extraStateSet方法,將各個狀態(tài)下對應(yīng)的state是true或者false的狀態(tài)值獲取到:

int[] extractStateSet(AttributeSet attrs) {
    int j = 0;
    final int numAttrs = attrs.getAttributeCount();
    int[] states = new int[numAttrs];
    for (int i = 0; i < numAttrs; i++) {
        final int stateResId = attrs.getAttributeNameResource(i);
        switch (stateResId) {
            case 0:
                break;
            //如果屬性是drawable或者id直接不要
            case R.attr.drawable:
            case R.attr.id:
                continue;
            default:
                //通過屬性的布爾值,返回對應(yīng)state_***的整型值
                states[j++] = attrs.getAttributeBooleanValue(i, false)
                        ? stateResId : -stateResId;
        }
    }
    states = StateSet.trimStateSet(states, j);
    return states;
}

上面方法如果item標(biāo)簽里面有drawable或者是id的屬性,直接不要;如果獲取的state_*** 是true,那么返回它對應(yīng)的state_*** 對應(yīng)的id,如果為false,則返回它對應(yīng)的-id。

獲取到對應(yīng)的state_***的對應(yīng)的id之后放到數(shù)組states里面。如果上面定義的drawable值不能通過上面獲取到,那么通過Drawable.createFromXmlInner方法獲取。最后將各個state下對應(yīng)的drawable,添加到StateListState里面,StateListStateStateListDrawable的子類,它將每一個state下的drawable存儲下來,放到父類的mDrawables變量里面,將state放到mStateSets里面,下面我們通過一個例子來說說上面的api:
寫了一個selector的drawable,名字是test_back.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/colorAccent" android:state_pressed="true" />
    <item android:drawable="@color/colorAccent" android:state_selected="true" />
    <item android:drawable="@color/colorPrimary" />
</selector>

三種狀態(tài),對應(yīng)的都是colorDrawable,然后在布局中給view設(shè)置背景:

image

然后通過debug,可以看下代碼跟蹤的情況:

image

第一次獲取的dr是colorDrawable,然后我們看下對應(yīng)的狀態(tài)id:

image

對應(yīng)的id是16842919,這個怎么看對應(yīng)的state_*** 呢,我們可以android.R.attr下面找到對應(yīng)的state_*** 可以看到:

image

正好對應(yīng)的id是state_press的id。看到這里,第二次循環(huán)應(yīng)該是state_selected對應(yīng)的id吧:

image

哈哈哈,還真的是state_pressed對應(yīng)的id,第三次對應(yīng)的就是普通時候的state了。第三次對應(yīng)的states是空的,因?yàn)樵谡G闆r下在extractStateSet方法里面獲取的stateResId=0,因此直接跳出循環(huán)extractStateSet方法的循環(huán)了,所以下面通過日志打印到不同狀態(tài)下state的drawable如下:

final View view2 = findViewById(R.id.view2);
Drawable background = view2.getBackground();
StateListDrawable.DrawableContainerState constantState =
        (StateListDrawable.DrawableContainerState) background.getConstantState();
Drawable[] children = constantState.getChildren();
for (int i = 0; i < children.length; i++) {
    Drawable child = children[i];
    if (child instanceof ColorDrawable) {
        ColorDrawable colorDrawable = (ColorDrawable) child;
        int color = colorDrawable.getColor();
        Log.d(TAG, "color:" + toHexEncoding(color));
    } else {
        Log.d(TAG, "drawable:" + children[i]);
    }
}
image

在color.xml中定義的幾個顏色值如下:

<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
</resources>

所以更加說明了,在StateListDrawable.StateListState類通過addStateSet方法,將不同狀態(tài)下對應(yīng)的drawable放到mDrawable[]數(shù)組里面,但是有人好奇了,為什么在打印日志里面,除了前面三個狀態(tài)下的drawable都是colorDrawable,而后面7個是為空呢,其實(shí)這個當(dāng)時我也很好奇,為什么輸出的長度是10個,翻開StateSet類發(fā)現(xiàn),所有關(guān)于state_***的屬性總共10個:

image

所以我在想是不是有什么地方做了mDrawable長度的限制,果然在StateListDrawable.StateListState類調(diào)用addStateSet方法的時候,調(diào)用了父類DrawableContaineraddChild方法的時候有這么一句:

image

第一次addState的時候,mNumChildren=0,這個時候mDrawables.length=0,此時調(diào)用了growArray方法:

public void growArray(int oldSize, int newSize) {
    //newSize=10,oldSize=0
    Drawable[] newDrawables = new Drawable[newSize];
    //arraycopy是將mDrawables拷貝到newDrawables里面,所以此時mDrawables的長度=10
    System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
    mDrawables = newDrawables;
}

從這里不難看出,最終是將長度=10的newDrawables作為拷貝的數(shù)組,放到了mDrawables里面。所以印證了上面日志上輸出的長度=10的打印結(jié)果。

StateListDrawable的繪制過程

還記得我們在第一節(jié)android中drawable顯示到view上的過程的時候,說過view在action_down和action_up的時候會觸發(fā)drawable的setState方法:

public boolean setState(@NonNull final int[] stateSet) {
    if (!Arrays.equals(mStateSet, stateSet)) {
        mStateSet = stateSet;
        return onStateChange(stateSet);
    }
    return false;
}

傳進(jìn)來的是當(dāng)前的state數(shù)組狀態(tài),mStateSet表示當(dāng)前drawable正在執(zhí)行的state,mStateSet默認(rèn)是一個空的數(shù)組,因此Arrays.equals(mStateSet, stateSet)肯定不相等,所以會進(jìn)到if,將stateSet賦給了mStateSet,回調(diào)給了onStateChange方法,drawable該方法下面是個空方法,因此可以看得出來狀態(tài)的改變交給了子類去完成:

@Override
protected boolean onStateChange(int[] stateSet) {
    //調(diào)用了父類的onStateChange方法
    final boolean changed = super.onStateChange(stateSet);
    //通過傳來的state在R文件中的int值來獲取idx
    int idx = mStateListState.indexOfStateSet(stateSet);
    if (idx < 0) {
        idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
    }
    return selectDrawable(idx) || changed;
}

//父類的onStateChange方法
@Override
protected boolean onStateChange(int[] state) {
    //剛開始mLastDrawable和mCurrDrawable為空
    if (mLastDrawable != null) {
        return mLastDrawable.setState(state);
    }
    if (mCurrDrawable != null) {
        return mCurrDrawable.setState(state);
    }
    return false;
}

可以看到上面onStateChange方法先是調(diào)用了父類的onStateChange方法,然后通過mStateListState.indexOfStateSet獲取到idx值,最后調(diào)用了父類的selectDrawable(idx)方法,通過日志我們在按下的時候獲取到日志如下:

image

通過android.R.attr文件找到了對應(yīng)的state_***:

image

mStateListState.indexOfStateSet中做的工作是如果找到了StateListDrawable中和傳過來的state有對應(yīng)關(guān)系,直接返回StateListDrawable.StateListStatemStateSets二維數(shù)組的索引。
這里可以看到對應(yīng)的ids=0:

image

我們可以做個驗(yàn)證,將xml中的state_pressed狀態(tài)放在后面的位置,再來看下日志:

image

這里我把pressed的item放到了第二個位置,然后通過debug日志繼續(xù)可以看到:

image

看到了吧,獲取到的idx=1,正好對應(yīng)了selector里面第二個item,也就是state_pressed對應(yīng)的位置。

這里提個醒哈,如果將默認(rèn)的item放到了第二個位置,按下的item放到第三個位置,按下的時候直接不顯示按下的drawable了,為啥呢,這是因?yàn)?br> int idx = mStateListState.indexOfStateSet(stateSet);這句返回的idx=1,正好返回的drawable是第二個位置的item,所以按下的時候,還是顯示正常情況下的drawable。

上面最后調(diào)用了selectDrawable方法,該方法在父類里面:

public boolean selectDrawable(int index) {
    if (index == mCurIndex) {
        return false;
    }

    final long now = SystemClock.uptimeMillis();

    //默認(rèn)情況下mDrawableContainerState.mExitFadeDuration=0
    if (mDrawableContainerState.mExitFadeDuration > 0) {
        if (mLastDrawable != null) {
            mLastDrawable.setVisible(false, false);
        }
        if (mCurrDrawable != null) {
            mLastDrawable = mCurrDrawable;
            mLastIndex = mCurIndex;
            mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
        } else {
            mLastDrawable = null;
            mLastIndex = -1;
            mExitAnimationEnd = 0;
        }
    } else if (mCurrDrawable != null) {
        mCurrDrawable.setVisible(false, false);
    }
    //實(shí)際上看這里就行,index始終是>=0的,并且mDrawableContainerState.mNumChildren=10
    if (index >= 0 && index < mDrawableContainerState.mNumChildren) {
        //獲取到當(dāng)前的drawable
        final Drawable d = mDrawableContainerState.getChild(index);
        //將d賦給mCurrDrawable
        mCurrDrawable = d;
        mCurIndex = index;
        if (d != null) {
            if (mDrawableContainerState.mEnterFadeDuration > 0) {
                mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
            }
            initializeDrawableForDisplay(d);
        }
    } else {
        mCurrDrawable = null;
        mCurIndex = -1;
    }
    //默認(rèn)是等于0的
    if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
        if (mAnimationRunnable == null) {
            mAnimationRunnable = new Runnable() {
                @Override public void run() {
                    animate(true);
                    invalidateSelf();
                }
            };
        } else {
            unscheduleSelf(mAnimationRunnable);
        }
        // Compute first frame and schedule next animation.
        animate(true);
    }
    //這里會觸發(fā)自己的draw方法
    invalidateSelf();

    return true;
}

上面這么多的代碼,其實(shí)只需要看這段邏輯就行:
if (index >= 0 && index < mDrawableContainerState.mNumChildren),可以看到將當(dāng)前獲取到的drawable賦值給了mCurrDrawable變量,在最后觸發(fā)了invalidateSelf方法,如果看過我寫的第一節(jié)android中drawable顯示到view上的過程,一定會知道,最后會觸發(fā)到StateListDrawable的draw方法,draw方法在DrawContainer里面:

@Override
public void draw(Canvas canvas) {
    if (mCurrDrawable != null) {
        mCurrDrawable.draw(canvas);
    }
    if (mLastDrawable != null) {
        mLastDrawable.draw(canvas);
    }
}

說白了,正常情況下按下和抬起的時候,用到了上面數(shù)組中的第二個和第三個colorDrawable,將drawable賦值給了mCurrDrawable,所以最終會繪制成mCurrDrawable的樣子。

在上面selectDrawable方法中有這么一句if (mDrawableContainerState.mExitFadeDuration > 0),該if可以通過xml或者setExitFadeDuration方法來實(shí)現(xiàn):

image

image

英語不好的筒子們,可以看下翻譯該方法啥意思:在drawable離開時淡入淡出的時間間隔

可以看下設(shè)置該屬性之后的效果,這是按下一會兒和松開的時候效果:

image

通過該效果分析下過程,回到剛才的selectDrawable方法,

public boolean selectDrawable(int index) {
    if (index == mCurIndex) {
        return false;
    }

    final long now = SystemClock.uptimeMillis();

    //此時會走這里
    if (mDrawableContainerState.mExitFadeDuration > 0) {
        if (mLastDrawable != null) {
            mLastDrawable.setVisible(false, false);
        }
        //第二次mCurrDrawable不為空,
        if (mCurrDrawable != null) {
            mLastDrawable = mCurrDrawable;
            mLastIndex = mCurIndex;
            //設(shè)置動畫維持的時間
            mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
        } else {
            mLastDrawable = null;
            mLastIndex = -1;
            mExitAnimationEnd = 0;
        }
    } else if (mCurrDrawable != null) {
        mCurrDrawable.setVisible(false, false);
    }
   
    //默認(rèn)是等于0的
    if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
        if (mAnimationRunnable == null) {
            mAnimationRunnable = new Runnable() {
                @Override public void run() {
                    //動畫執(zhí)行的地方
                    animate(true);
                    invalidateSelf();
                }
            };
        } else {
            //釋放任務(wù)
            unscheduleSelf(mAnimationRunnable);
        }
        //此處會觸發(fā)動畫執(zhí)行
        animate(true);
    }
    //這里會觸發(fā)自己的draw方法
    invalidateSelf();

    return true;
}

如果設(shè)置了mDrawableContainerState.mExitFadeDuration > 0,mCurrDrawable是正常state下的drawable,因此會將currentDrawable賦值給lastDrawable,此時mExitAnimationEnd就是我們設(shè)置的淡入淡出的時間,緊接著就是在animate方法里面觸發(fā)mAnimationRunnable的執(zhí)行:

void animate(boolean schedule) {
    mHasAlpha = true;

    final long now = SystemClock.uptimeMillis();
    boolean animating = false;
    //此處是設(shè)置drawable進(jìn)入的時候動畫,如果設(shè)置了enterAnimationEnd屬性才會走這里
    //實(shí)際上進(jìn)入的動畫是不斷改變mCurrDrawable的動畫,而此時mLastDrawable是空的 
    //所以可以想象下,當(dāng)按下的時候,按下的drawable的alpha從0到255
    //當(dāng)抬起的時候,正常的drawable也會從0到255
    if (mCurrDrawable != null) {
        if (mEnterAnimationEnd != 0) {
            if (mEnterAnimationEnd <= now) {
                mCurrDrawable.setAlpha(mAlpha);
                mEnterAnimationEnd = 0;
            } else {
                int animAlpha = (int)((mEnterAnimationEnd-now)*255)
                        / mDrawableContainerState.mEnterFadeDuration;
                mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255);
                animating = true;
            }
        }
    } else {
        mEnterAnimationEnd = 0;
    }
    if (mLastDrawable != null) {
        if (mExitAnimationEnd != 0) {
            if (mExitAnimationEnd <= now) {
                mLastDrawable.setVisible(false, false);
                mLastDrawable = null;
                mLastIndex = -1;
                mExitAnimationEnd = 0;
            } else {
                //實(shí)際上就是不斷改變mLastDrawable的透明度,透明度到了mExitAnimationEnd的時候就為0,
                //所以到了最后就只能看到mCurrDrawable
                int animAlpha = (int)((mExitAnimationEnd-now)*255)
                        / mDrawableContainerState.mExitFadeDuration;
                mLastDrawable.setAlpha((animAlpha*mAlpha)/255);
                animating = true;
            }
        }
    } else {
        mExitAnimationEnd = 0;
    }

    if (schedule && animating) {
        scheduleSelf(mAnimationRunnable, now + 1000 / 60);
    }
}

上面如果mExitAnimationEnd>0,lastDrawable是正常狀態(tài)下的drawable,此時lastDrawable的alpha值從255到0,看到的效果就是按下的時候正常的drawable(lastDrawable)慢慢地變淡,等now到了mExitAnimationEnd時候,按下的drawable才會顯示。而抬起的時候,此時lastDrawable是按下的drawable,此時lastDrawable的alpha值從255到0,所以看到的效果就是抬起時按下的drawable慢慢變淡。

如果mEnterAnimationEnd>0,此時lastDrawable為空,只繪制currentDrawable,而此時currentDrawable的alpha值從0到255,所以按下的時候,按下的drawable會從淡到顯示。當(dāng)抬起的時候,此時currentDrawable是正常情況的drawable,因此會出現(xiàn)正常drawable從淡到顯示。

enterFadeDuration設(shè)置要顯示的drawable的淡入時間。
exitFadeDuration設(shè)置當(dāng)前drawable離開的淡出時間。

關(guān)于enterFadeDuration在上面例子中沒有演示,大家可以自己嘗試該屬性。

上面例子中,currentDrawable和lastDrawable實(shí)際都是colorDrawable,因?yàn)槲覀冊诶又卸x的drawable只是一個顏色值,因此咱們看看colorDrawable實(shí)際繪制的過程,在說colorDrawable的繪制之前,我們先來回憶下在第一節(jié)講android中drawable顯示到view上的過程,applyBackgroundTint方法當(dāng)時提過是用來著色用的,其實(shí)它的實(shí)質(zhì)是通過paint.setColorFilter(new PorterDuffColorFilter(color,mode))來實(shí)現(xiàn)的,下面先度娘看下paint.setColorFilter能做些啥事:

setColorFilter

ColorFiler一共有三個子類,分別是ColorMatrixColorFilterLightingColorFilter、PorterDuffColorFilter

ColorMatrixColorFilter

是一個顏色矩陣器,它需要一個ColorMatrix對象,ColorMatrix需要設(shè)置顏色矩陣,下面通過一個demo來說明該問題:

public class ColorFilterView extends View {
    private static final String TAG = ColorFilterView.class.getSimpleName();
    Paint mPaint;

    public ColorFilterView(Context context) {
        this(context, null);
    }

    public ColorFilterView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ColorFilterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        int color = Color.parseColor("#666666");//R=102,G=102,B=102,A=255
        int red = Color.red(color);
        int green = Color.green(color);
        int blue = Color.blue(color);
        Log.d(TAG, "red:" + red);
        Log.d(TAG, "green:" + green);
        Log.d(TAG, "blue:" + blue);
        Log.d(TAG, "alpha:" + Color.alpha(color));
        mPaint.setColor(color);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.d(TAG, "onDraw");
        // 生成色彩矩陣
        ColorMatrix colorMatrix = new ColorMatrix(new float[]{
                0.5f, 0, 0, 0, 0,//R=0.5*102+0*102+0*102+0*255+0=51
                0, 0.5f, 0, 0, 0,//G=0*102+0.5*102+0*102+0*255+0=51
                0, 0, 0.5f, 0, 0,//B=0*102+0*102+0.5*102+0*255+0=51
                0, 0, 0, 1, 0,//B=0*102+0*102+0*102+1*255+0=255
        });
        ColorMatrix colorMatrix1 = new ColorMatrix(new float[]{
                1, 0, 0, 0, 0,//R=1*102+0*102+0*102+0*255+0=102
                0, 1, 0, 0, 0,//G=0*102+1*102+0*102+0*255+0=102
                0, 0, 1, 0, 0,//B=0*102+0*102+1*102+0*255+0=102
                0, 0, 0, 1, 0,//A=0*102+0*102+0*102+1*255+0=255
        });
        mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

        // 設(shè)置畫筆顏色為自定義顏色

        // 繪制圓環(huán) (x坐標(biāo),y坐標(biāo),半徑,畫筆)
        canvas.drawCircle(240, 600 / 2, 200, mPaint);
    }
}

例子中的顏色值是#666666,顏色值R=102,G=102,B=102,A=255,
在colorMatrix運(yùn)算的時候,矩陣第一行算出來的結(jié)果是R的值,算法過程是每一行的每一個數(shù)分別與顏色值相乘然后相加。所以第一個矩陣算出來的結(jié)果:R=51,G=51,B=51,A=255,對應(yīng)顏色的16進(jìn)制是#333333;在第二個矩陣中,每一行每一個數(shù)相乘的顏色位正好是原來的102的值,因此第二個顏色矩陣算出來的值還是原來的顏色值。
這里借用網(wǎng)上一張圖:

image

下面來介紹下在bitmap情況下,ColorMatrix是怎么工作的,先上一個demo看下,圖片就用android studio自帶的:

image

這里寫了一個紅色通道的colorMatrix,意思是如果你想讓圖片偏向哪個顏色,對應(yīng)的通道就盡量大點(diǎn),最后一列表示圖片的飽和度:

image

這里寫了幾種情況,第一個是原始圖片,第二個是紅色通道生成的,第三個是紅色通道+100的飽和度生成的,第四個是藍(lán)色通道生成的,第五個是綠色通道生成的,第六個透明度通道生成的,透明度是0.5。

//紅色通道
ColorMatrix colorMatrixR = new ColorMatrix(new float[]{
        1, 0, 0, 0, 0,
        0, 0, 0, 0, 0,
        0, 0, 0, 0, 0,
        0, 0, 0, 1, 0,
});
//綠色通道
ColorMatrix colorMatrixG = new ColorMatrix(new float[]{
        0, 0, 0, 0, 0,
        0, 1, 0, 0, 0,
        0, 0, 0, 0, 0,
        0, 0, 0, 1, 0,
});
//紅色通道+100的飽和度
ColorMatrix colorMatrixRB = new ColorMatrix(new float[]{
        1, 0, 0, 0, 100,
        0, 0, 0, 0, 0,
        0, 0, 0, 0, 0,
        0, 0, 0, 1, 0,
});
//藍(lán)色通道
ColorMatrix colorMatrixB = new ColorMatrix(new float[]{
        0, 0, 0, 0, 0,
        0, 0, 0, 0, 0,
        0, 0, 1, 0, 0,
        0, 0, 0, 1, 0,
});
//透明度0.5的通道
ColorMatrix colorMatrixA = new ColorMatrix(new float[]{
        1, 0, 0, 0, 0,
        0, 1, 0, 0, 0,
        0, 0, 1, 0, 0,
        0, 0, 0, 0.5f, 0,
});
 // LightingColorFilter lightingColorFilter = new LightingColorFilter(0x0000ff, 0x000000);
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixR));
// 設(shè)置畫筆顏色為自定義顏色
// 繪制圓環(huán) (x坐標(biāo),y坐標(biāo),半徑,畫筆)
  canvas.drawCircle(240, 600 / 2, 200, mPaint);
canvas.drawBitmap(bitmap, 100, 100, null);
canvas.drawBitmap(bitmap, 300, 100, mPaint);
mPaint.setColorFilter(null);
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixRB));
canvas.drawBitmap(bitmap, 500, 100, mPaint);
mPaint.setColorFilter(null);
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixB));
canvas.drawBitmap(bitmap, 700, 100, mPaint);
mPaint.setColorFilter(null);
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixG));
canvas.drawBitmap(bitmap, 900, 100, mPaint);
mPaint.setColorFilter(null);
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrixA));
canvas.drawBitmap(bitmap, 100, 300, mPaint);

colorMatrix其他的幾個方法:

setRGB2YUV將通道設(shè)置偏向紅色通道

setSaturation設(shè)置飽和度

setScale設(shè)置各個通道的縮放比

setRotate

image

該注釋說明如果參數(shù)axis=0,設(shè)置紅色通道旋轉(zhuǎn)的角度,axis=1,設(shè)置綠色通道旋轉(zhuǎn)的角度,axis=2,設(shè)置藍(lán)色通道的旋轉(zhuǎn)角度。下面也是將每個api跑了一遍demo:

image
//圖二
ColorMatrix colorMatrix1 = new ColorMatrix();
colorMatrix1.setSaturation(100);//設(shè)置每個通道的飽和度

//圖三
ColorMatrix colorMatrix2 = new ColorMatrix();
colorMatrix2.setRGB2YUV();//設(shè)置偏向紅色通道

//圖四
ColorMatrix colorMatrix3 = new ColorMatrix();
colorMatrix3.setYUV2RGB();//也是偏向紅色通道

//圖五
ColorMatrix colorMatrix4 = new ColorMatrix();
colorMatrix4.setScale(50, 50, 50, 50);//指定每一個通道放大的倍數(shù)

//圖六,因?yàn)槭菄@G通道,圍繞那個通道旋轉(zhuǎn),就偏向旋轉(zhuǎn)的通道
ColorMatrix colorMatrix5 = new ColorMatrix();
colorMatrix5.setRotate(0, 180);//圍繞某一個通道進(jìn)行旋轉(zhuǎn)多少度,1.是圍繞G通道,0.是圍繞R通道,2.是圍繞B通道

關(guān)于ColorMatrix就說這么多,大家只要記住計(jì)算公式就行,需要偏向那個通道,就設(shè)置那個通道的值偏大,其余的通道就不用管或者調(diào)小。

LightingColorFilter

是一個光照顏色過濾器,他只有一個帶兩個參數(shù)的構(gòu)造器,第一個參數(shù)是色彩倍增,第二個參數(shù)是色彩增加,兩個參數(shù)都是16進(jìn)制的顏色值,下面看看如何使用:

//光照colorFilter
//第一個參數(shù)的顏色值,通過ARGB每兩位設(shè)置通道的倍數(shù)
//第二個參數(shù)設(shè)置每一個通道的偏移量
//下面應(yīng)該可以看出來繪制的顏色是偏向R通道的,因此呈現(xiàn)出紅色
LightingColorFilter lightingColorFilter = new LightingColorFilter(0x66660000, 0x00000000);
mPaint.setColorFilter(null);
mPaint.setColorFilter(lightingColorFilter);
canvas.drawBitmap(bitmap, 300, 300, mPaint);

很簡單,如果你想讓顏色偏向什么通道顏色,直接將通道顏色值設(shè)置大點(diǎn),偏移量參數(shù)也可以適當(dāng)?shù)脑O(shè)置相應(yīng)的通道。關(guān)于LightingColorFilter就說這么多,基本知道兩個參數(shù)的含義就ok。

PorterDuffColorFilter

它是我們今天drawable中用到的著色器colorFilter,PorterDuffColorFilter提供了兩個參數(shù),第一個參數(shù)是16進(jìn)制顏色值,第二個參數(shù)是PorterDuff.Mode,表示顏色混合模式,而在模式里面又分為dst和src,src表示當(dāng)前PorterDuffColorFilter第一個參數(shù)的顏色,dst表示在繪制用到paint時候的圖案,下面的demo以繪制bitmap作為dat:

//繪制的顏色是src,dst是圖片區(qū)域
PorterDuffColorFilter srcPorterDuffColorFilter = new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC);
mPaint.setColorFilter(null);
mPaint.setColorFilter(srcPorterDuffColorFilter);
canvas.drawBitmap(bitmap, 500, 300, mPaint);
//DST模式下只顯示圖片
PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DST);
mPaint.setColorFilter(null);
mPaint.setColorFilter(porterDuffColorFilter);
canvas.drawBitmap(bitmap, 700, 300, mPaint);

下面來看看android中真實(shí)案例用到了PorterDuffColorFilter的繪圖重疊,其實(shí)在imageView中的setColorFilter正是用到PorterDuffColorFilter的api,最終調(diào)用了drawable.setColorFilter:

image

image

上面運(yùn)行出來的結(jié)果是項(xiàng)目中用到PorterDuffColorFilter的特性,通過設(shè)置mode為MULTIPLY,顏色值為Color.GRAY,MULTIPLY模式表示取src和dst的交集,并且src在上面,dst在下面,通過兩者的復(fù)合達(dá)到遮罩效果。該demo樣式是在recyclerView的holder中使用的,部分代碼如下:

public class ManagerBookShelfHolder extends ViewHolderImpl<BookShelfItem> {

    private ImageView bookCoverimg;
    private TextView bookName;
    private ImageView select;

    @Override
    protected int getItemLayoutId() {
        return R.layout.book_shelf_item;
    }

    @Override
    public void initView() {
        bookCoverimg = findById(R.id.bookCoverimg);
        bookName = findById(R.id.bookName);
        select = findById(R.id.select);
    }

    @Override
    public void onBind(BookShelfItem data, int pos) {
        Glide.with(getContext()).load(data.bookCoverimg).listener(new RequestListener<Drawable>() {
            @Override
            public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                return false;
            }

            @Override
            public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                bookCoverimg.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);
                return false;
            }
        }).into(bookCoverimg);
        if (data.select) {
            select.setImageResource(R.mipmap.select);
            bookCoverimg.clearColorFilter();
        } else {
            bookCoverimg.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);
            select.setImageResource(R.mipmap.un_select);
        }
        bookName.setText(data.bookName);
        select.setVisibility(View.VISIBLE);

    }

}

關(guān)于其他的mode就都不嘗試了,大家自己根據(jù)下面的mode圖自己嘗試?yán)L制能體會到:

image

其實(shí)在android中paint.setXmode方法中,也有此mode的應(yīng)用,關(guān)于paint.setXmode的應(yīng)用,我在仿蘋果版小黃車(ofo)app主頁菜單效果中有使用到,大家如果喜歡該文章,可以關(guān)注下文章。

好了,關(guān)于ColorFilter就說這么多,我們再簡單的回到drawable的setColorFilter看下:

public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
    if (getColorFilter() instanceof PorterDuffColorFilter) {
        PorterDuffColorFilter existing = (PorterDuffColorFilter) getColorFilter();
        if (existing.getColor() == color && existing.getMode() == mode) {
            return;
        }
    }
    setColorFilter(new PorterDuffColorFilter(color, mode));
}

默認(rèn)getColorFilter()肯定不是PorterDuffColorFilter類型的,所以會走setColorFilter(new PorterDuffColorFilter(color, mode)),因此順著找到對應(yīng)的setColorFilter,該方法在drawable中是個抽象的方法,因此可以看下colorDrawable里面的setColorFilter方法:

@Override
public void setColorFilter(ColorFilter colorFilter) {
    mPaint.setColorFilter(colorFilter);
}

好吧,如此簡單調(diào)用了piant.setColorFilter,最終會由view觸發(fā)到drawable的繪制。

TODO

  • 本來是想將StateListDrawable和RippleDrawable放在一起講的,限于篇幅,將RippleDrawable放到后面部分說水波效果的實(shí)現(xiàn),以及如何實(shí)現(xiàn)定義view的時候水波效果。
  • ColorMatrixColorFilter延伸講解。

如果還不熟悉drawable在view上的顯示流程還不熟悉,請看之前的兩節(jié)內(nèi)容:

);s,s,

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容