浅谈 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

相关文章
相关标签/搜索