淺談 Android L 的 Tint(著色)


淺談 Android L 的 Tint(著色)


Tint 是什么?

Tint 翻譯為著色。

著色,著什么色呢,和背景有關(guān)?當(dāng)然是著背景的色。當(dāng)我們開發(fā) App 的時候,如果使用了 Theme.AppCompat 主題的時候,會發(fā)現(xiàn) ActionBar 或者 Toolbar 及相應(yīng)的控件的顏色會相應(yīng)的變成我們在 Theme 中設(shè)置的 colorPrimary, colorPrimaryDark, colorAccent 這些顏色,這是為什么呢,這就全是 Tint 的功勞了!

這樣做有什么好處呢?好處就是你不必再老老實(shí)實(shí)的打開 PS 再制作一張新的資源圖。而且大家都知道 apk 包最大的就是圖片資源了,這樣減少不必要資源圖,可以極大的減少了我們的 apk 包的大小。

實(shí)現(xiàn)的方式就是用一個顏色為我們的背景圖片設(shè)置 Tint(著色)。

例子:

WhiteBall-1
WhiteBall-1

大家可以看上面再張圖,這個是做的一個應(yīng)用“小白球”,如圖 1 的小圖片本來都是白色的(圓背景的單獨(dú)設(shè)的),但是經(jīng)過 Tint 著色后就變成了圖 2 中的淺藍(lán)色了。

好了,既然理解了tint的含義,我們趕緊看下這一切是如何實(shí)現(xiàn)的吧。
其實(shí)底層特別簡單,了解過渲染的同學(xué)應(yīng)該知道PorterDuffColorFilter這個東西,我們使用SRC_IN的方式,對這個Drawable進(jìn)行顏色方面的渲染,就是在這個Drawable中有像素點(diǎn)的地方,再用我們的過濾器著色一次。
實(shí)際上如果要我們自己實(shí)現(xiàn),只用獲取View的backgroundDrawable之后,設(shè)置下colorFilter即可。

看下最核心的代碼就這么幾行

if (filter == null) {
    // Cache miss, so create a color filter and add it to the cache
    filter = new PorterDuffColorFilter(color, mode);
}

d.setColorFilter(filter);

通常情況下,我們的mode一般都是SRC_IN,如果想了解這個屬性相關(guān)的資料,這里是傳送門: http://blog.csdn.net/t12x3456/article/details/10432935 (中文)

由于API Level 21以前不支持background tint在xml中設(shè)置,于是提供了ViewCompat.setBackgroundTintList方法和ViewCompat.setBackgroundTintMode用來手動更改需要著色的顏色,但要求相關(guān)的View繼承TintableBackgroundView接

源碼解析

以 EditText 為例,其它的基本一致

public AppCompatEditText(Context context, AttributeSet attrs, int defStyleAttr) {
    super(TintContextWrapper.wrap(context), attrs, defStyleAttr);

    ...

    ColorStateList tint = a.getTintManager().getTintList(a.getResourceId(0, -1)); //根據(jù)背景的resource id獲取內(nèi)置的著色顏色。
    if (tint != null) {
        setInternalBackgroundTint(tint); //設(shè)置著色
    }

    ...
}

private void setInternalBackgroundTint(ColorStateList tint) {
    if (tint != null) {
        if (mInternalBackgroundTint == null) {
            mInternalBackgroundTint = new TintInfo();
        }
        mInternalBackgroundTint.mTintList = tint;
        mInternalBackgroundTint.mHasTintList = true;
    } else {
        mInternalBackgroundTint = null;
    }
    //上面的代碼是記錄tint相關(guān)的信息。
    applySupportBackgroundTint();  //對背景應(yīng)用tint
}


 private void applySupportBackgroundTint() {
    if (getBackground() != null) {
        if (mBackgroundTint != null) {
            TintManager.tintViewBackground(this, mBackgroundTint);
        } else if (mInternalBackgroundTint != null) {
            TintManager.tintViewBackground(this, mInternalBackgroundTint); //最重要的,對tint進(jìn)行應(yīng)用
        }
    }
}

然后我們進(jìn)入tintViewBackground看下TintManager里面的源碼

 public static void tintViewBackground(View view, TintInfo tint) {
    final Drawable background = view.getBackground();
    if (tint.mHasTintList) {
        //如果設(shè)置了tint的話,對背景設(shè)置PorterDuffColorFilter
        setPorterDuffColorFilter(
                background,
                tint.mTintList.getColorForState(view.getDrawableState(),
                        tint.mTintList.getDefaultColor()),
                tint.mHasTintMode ? tint.mTintMode : null);
    } else {
        background.clearColorFilter();
    }

    if (Build.VERSION.SDK_INT <= 10) {
        // On Gingerbread, GradientDrawable does not invalidate itself when it's ColorFilter
        // has changed, so we need to force an invalidation
        view.invalidate();
    }
}


private static void setPorterDuffColorFilter(Drawable d, int color, PorterDuff.Mode mode) {
    if (mode == null) {
        // If we don't have a blending mode specified, use our default
        mode = DEFAULT_MODE;
    }

    // First, lets see if the cache already contains the color filter
    PorterDuffColorFilter filter = COLOR_FILTER_CACHE.get(color, mode);

    if (filter == null) {
        // Cache miss, so create a color filter and add it to the cache
        filter = new PorterDuffColorFilter(color, mode);
        COLOR_FILTER_CACHE.put(color, mode, filter);
    }

    // 最最重要,原來是對background drawable設(shè)置了colorFilter 完成了我們要的功能。
    d.setColorFilter(filter);
}

private void applySupportBackgroundTint() {
    if (getBackground() != null) {
        if (mBackgroundTint != null) {
            TintManager.tintViewBackground(this, mBackgroundTint);
        } else if (mInternalBackgroundTint != null) {
            TintManager.tintViewBackground(this, mInternalBackgroundTint); //最重要的,對tint進(jìn)行應(yīng)用
        }
    }
}

以上是對API21以下的兼容。
如果我們要實(shí)現(xiàn)自己的AppCompat組件實(shí)現(xiàn)tint的一些特性的話,我們就可以指定好ColorStateList,利用TintManager對自己的背景進(jìn)行著色,當(dāng)然需要對外開放設(shè)置的接口的話,我們還要實(shí)現(xiàn)TintableBackgroundView接口,然后用ViewCompat.setBackgroundTintList進(jìn)行設(shè)置,這樣能完成對v7以上所有版本的兼容。

自定義控件的著色

public class AppCompatView extends View implements TintableBackgroundView {

    private TintInfo mTintInfo;

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

    public AppCompatView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AppCompatView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        EmBackgroundTintHelper.loadFromAttributes(this, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mTintInfo = new TintInfo();
        mTintInfo.mHasTintList = true;
    }

    @Override
    public void setSupportBackgroundTintList(ColorStateList tint) {
        EmBackgroundTintHelper.setSupportBackgroundTintList(this, mTintInfo,tint);
    }

    @Nullable
    @Override
    public ColorStateList getSupportBackgroundTintList() {
        return EmBackgroundTintHelper.getSupportBackgroundTintList(mTintInfo);
    }

    @Override
    public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
        EmBackgroundTintHelper.setSupportBackgroundTintMode(this, mTintInfo, tintMode);
    }

    @Nullable
    @Override
    public PorterDuff.Mode getSupportBackgroundTintMode() {
        return EmBackgroundTintHelper.getSupportBackgroundTintMode(mTintInfo);
    }
}

最后

最后打個小廣告,并附上 Github 及我的 Blog

WhiteBall
最后編輯于
?著作權(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)容