安卓着色器(tint)使用实践。

作者: 阿飞咯 | 来源:发表于2016-05-31 23:57 被阅读8179次

    学习tint的目的:

    1.一张矢量图适配所有颜色(妈妈再也不要担心我找图了)。
    2.更优雅的selector实现方式。
    [图片上传失败...(image-3878ff-1510058207692)]](http:https://img.haomeiwen.com/i1757399/c52e20090d67b2e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    小试牛刀,一张矢量图适配所有颜色。

    如何在代码中实现下图效果


    效果1

    方法一:xml
    方法很简单直接看代码

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/image"
            android:src="@mipmap/icon"
            android:clickable="true"
            />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/image2"
            android:src="@mipmap/icon"
            android:tint="#FFCDD2"
            android:clickable="true"
            />
    

    用到的属性android:tint="@color"
    至于原理不做过多说明,有兴趣看看源码比较简单,也可以参考一下官网。
    方法二:代码实现

            Drawable drawable = ContextCompat.getDrawable(this,R.mipmap.icon);
            Drawable.ConstantState state = drawable.getConstantState();
            Drawable drawable1 = DrawableCompat.wrap(state == null ? drawable : state.newDrawable()).mutate();
            drawable1.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
            DrawableCompat.setTint(drawable,ContextCompat.getColor(this,R.color.pink));
            imageView.setImageDrawable(drawable);
            imageView1.setImageDrawable(drawable1);
    

    DrawableCompat类:是Drawable的向下兼容类,我们为了在6.0一下兼容tint属性而使用的,有兴趣的看看源码哦,也是很简单的一个兼容类。

    wrap方法:使用tint就必须调用该方法对Drawable进行一次包装。

    mutate方法:(个人简单的理解就是类似于对象的深拷贝与浅拷贝),如果不调用该方法,我们进行操作的就是原drawable,着色之后原drawable也改变的,所有两个ImageView都会显示着色之后的drawable。调用mutate后会对ConstantState进行一次拷贝,详情可看源码,以及参考

    恩,目的一基本完成了,我们来看目的二。

    更加优雅的使用selector
    第一次尝试

    为了更加优雅的使用selector可谓是踩了很多坑啊,但是实践才是检验真理的唯一标准,遇到坑就多去看看源码。
    以前使用selector是这样的。

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_pressed="true" android:drawable="@mipmap/icon_pressed"></item>
        <item android:drawable="@mipmap/icon_normal"></item>
    </selector>
    

    所以咯,我们需要在mipmap中放置两张图片,但是目的一中我们知道一张矢量图是能适配出所有颜色的。所以我们开始踩坑。
    我们可以在color中定义一个selector,然后设置tint属性。

    //color/icon.xml
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_pressed="true" android:color="@color/pink" ></item>
        <item android:color="@color/pink1"></item>
    </selector>
    
    <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/image"
            android:src="@mipmap/icon"
            android:tint="@color/icon"
            android:clickable="true"
            />
    

    设置后你会发现,并没有效果啊!这是为什么呢,我们看看BitmapDrawable源码吧。

        @Override
        protected boolean onStateChange(int[] stateSet) {
            final BitmapState state = mBitmapState;
            if (state.mTint != null && state.mTintMode != null) {
                mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
                return true;
            }
            return false;
        }
    

    DEBUG发现updateTintFilter()方法已经执行,为什么没有效果呢?
    进一步DEBUG发现界面未刷新,invalidateSelf()方法未调用,ImageView的onDraw()方法未执行,所以Drawable的draw()方法也未执行。所以暂时这种方法是行不通的。(但是在java逻辑代码中设置了在6.0以下是可以实现的,具体为什么我也有点纳闷…可以去看看我的demo)

    第二次尝试

    于是我们使用StateListDrawable,那么先在xml中试试可行性吧。

    //drawable/icon.xml
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_pressed="true" android:color="@drawable/icon" ></item>
        <item android:color="@color/pink1"></item>
    </selector>
    
    <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/image"
            android:src="@drawable/icon"
            android:tint="@color/icon"
            android:clickable="true"
            />
    

    我们在drawable目录下icon.xml中两种状态都设置的同一张图。(为什么要设置同一张图是有根据的,因为DrawableStateList源码中,如果你当然状态未单独设置drawable,将不会触发刷新,具体看源码,本篇侧重于实践。)
    恩,到这里你就会惊人的发现,效果出来了!(点击后变色)

    效果2.gif

    但是很遗憾6.0以下会直接crash的,因为不兼容,所以我们必须在java代码中设置。按照这个思路撸出代码。

        Drawable drawable = ContextCompat.getDrawable(this,R.mipmap.icon);
        int[] colors = new int[] { ContextCompat.getColor(this,R.color.pink),ContextCompat.getColor(this,R.color.pink1)};
        int[][] states = new int[2][];
        states[0] = new int[] { android.R.attr.state_pressed};
        states[1] = new int[] {};
        ColorStateList colorList = new ColorStateList(states, colors);
        StateListDrawable stateListDrawable = new StateListDrawable();
        stateListDrawable.addState(states[0],drawable);//注意顺序
        stateListDrawable.addState(states[1],drawable);
        Drawable.ConstantState state = stateListDrawable.getConstantState();
        drawable = DrawableCompat.wrap(state == null ? stateListDrawable : state.newDrawable()).mutate();
        DrawableCompat.setTintList(drawable,colorList);
        imageView.setImageDrawable(drawable);
    

    (代码有点多,但是这样做是很值得的。其实这样做感觉更符合逻辑一点,不过期间踩了很多坑,特别是在做6.0和6.0以下适配的时候)
    恩,效果出来了,具体原因可以去看看StateListDrawable源码,然后自己DEBUG一下。
    到这里踩坑完成了。更优雅的实现selector,既减少了apk大小又节约了内存。

    源码在这里,更多踩坑请点击...

    相关文章

      网友评论

      • 请叫我四爷:Android 7.0是上面 ,不管用 ,求助
      • leiiiooo:android.support.v7.widget.AppCompatImageView
      • 37aa2169d00a:Android 6.0以下失效
      • 键盘男:只要4.x不能用,就gg了
        键盘男:@昨天还是一个小白 xml那个android:tint那个不能啊
        阿飞咯:@键盘男kkmike999 兼容代码可以用的
      • f9d3d9cdfec1:楼主你好,用了你的代码,
        ImageView imageView = (ImageView) findViewById(R.id.image);
        Drawable drawable = ContextCompat.getDrawable(MainActivity.this, R.mipmap.ic_launcher);
        Drawable.ConstantState state = drawable.getConstantState();
        Drawable drawable1 = DrawableCompat.wrap(state == null ? drawable : state.newDrawable()).mutate();
        drawable1.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        DrawableCompat.setTint(drawable1, getResources().getColor(R.color.colorPrimary));
        imageView.setImageDrawable(drawable1);

        可能有几处不太一样,可是就是setTint方法处的Drawable对象,以及setImageDrable方法处使用的对象,几种情况我都用了,可是还是没有效果,请问是有其他的方没有注意到吗
        请叫我四爷:@FuriousTurbo 怎么解决的?
        f9d3d9cdfec1:@昨天还是一个小白 谢谢,已经解决了 :blush:
        阿飞咯:@WGMa 你可以clone一下我的demo 你的代码是实现的基础着色吧?
      • 393fd43f90c4:谢谢分享
        阿飞咯:@壳里的小鱼 互相学习:yum:
      • 闪电代码手:棒棒哒,学习了。
        阿飞咯:@黑猫很紧张 互相学习:yum:
      • Torang:写的很清楚,希望继续写作!
        阿飞咯:@Torang 向学霸学习:sunglasses:
      • wenhuaijun:good
        阿飞咯:@wenhuaijun :grin:向俊俊学习
      • HarveyLegend:支持支持,第一次听说tint
        阿飞咯:@HarveyLegend :relaxed:也是做项目遇到的
      • 我是你小小:支持 学习学习
        阿飞咯:@我是你小小 互相学习:grin:
      • 小诸葛007:厉害~~ :relaxed:
        阿飞咯:@小诸葛007 :blush:互相学习

      本文标题:安卓着色器(tint)使用实践。

      本文链接:https://www.haomeiwen.com/subject/zyjqdttx.html