淺談 Android L 的 Tint(着色)

Tint 是什麼?

Tint 翻譯爲着色。git

着色,着什麼色呢,和背景有關?固然是着背景的色。當咱們開發 App 的時候,若是使用了 Theme.AppCompat 主題的時候,會發現 ActionBar 或者 Toolbar 及相應的控件的顏色會相應的變成咱們在 Theme 中設置的 colorPrimary, colorPrimaryDark, colorAccent 這些顏色,這是爲何呢,這就全是 Tint 的功勞了!github

這樣作有什麼好處呢?好處就是你沒必要再老老實實的打開 PS 再製做一張新的資源圖。並且你們都知道 apk 包最大的就是圖片資源了,這樣減小沒必要要資源圖,能夠極大的減小了咱們的 apk 包的大小。app

實現的方式就是用一個顏色爲咱們的背景圖片設置 Tint(着色)。ide

例子:

WhiteBall-2WhiteBall-1

你們能夠看上面再張圖,這個是作的一個應用「小白球」,如圖 1 的小圖片原本都是白色的(圓背景的單獨設的),可是通過 Tint 着色後就變成了圖 2 中的淺藍色了。ui

好了,既然理解了tint的含義,咱們趕忙看下這一切是如何實現的吧。 其實底層特別簡單,瞭解過渲染的同窗應該知道PorterDuffColorFilter這個東西,咱們使用SRC_IN的方式,對這個Drawable進行顏色方面的渲染,就是在這個Drawable中有像素點的地方,再用咱們的過濾器着色一次。 實際上若是要咱們本身實現,只用獲取View的backgroundDrawable以後,設置下colorFilter便可。this

看下最核心的代碼就這麼幾行.net

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,若是想了解這個屬性相關的資料,這裏是傳送門: http://blog.csdn.net/t12x3456/article/details/10432935 (中文)翻譯

因爲API Level 21之前不支持background tint在xml中設置,因而提供了ViewCompat.setBackgroundTintList方法和ViewCompat.setBackgroundTintMode用來手動更改須要着色的顏色,但要求相關的View繼承TintableBackgroundView接code

源碼解析

以 EditText 爲例,其它的基本一致xml

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

    ...

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

    ...
}

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


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

而後咱們進入tintViewBackground看下TintManager裏面的源碼

public static void tintViewBackground(View view, TintInfo tint) {
    final Drawable background = view.getBackground();
    if (tint.mHasTintList) {
        //若是設置了tint的話,對背景設置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設置了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進行應用
        }
    }
}

以上是對API21如下的兼容。 若是咱們要實現本身的AppCompat組件實現tint的一些特性的話,咱們就能夠指定好ColorStateList,利用TintManager對本身的背景進行着色,固然須要對外開放設置的接口的話,咱們還要實現TintableBackgroundView接口,而後用ViewCompat.setBackgroundTintList進行設置,這樣能完成對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

相關文章
相關標籤/搜索