美文网首页Android收藏集
ImageView动态着色以及兼容性问题

ImageView动态着色以及兼容性问题

作者: dlihasa | 来源:发表于2020-01-08 11:16 被阅读0次

    前言

    最近看第三方库的demo发现ImageView使用了一个tint的属性,可以改变图片的颜色,这样在部分需求前可以减少项目中图片的数量,从而减少apk安装包大小。

    • 注意:以下示例中的表现效果均有一个前提:xml和代码不要混写。否则会出现很多种情况

    应用

    示例中原图片如下:


    ic_func.png
    1.根据业务逻辑,不同状态下,图片的颜色不同

    (1)xml中可以设置tint属性,如下:

    <ImageView
            android:id="@+id/iv_one"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:tint="@color/colorPrimary"
            android:src="@drawable/ic_func"/>
    <ImageView
            android:id="@+id/iv_two"
            android:layout_below="@+id/iv_one"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:src="@drawable/ic_func"/>
    

    效果如下:


    效果图一

    (2)根据业务动态修改,还得看代码中的方式(xml写法去除上面iv_one的tint属性):

    代码实现一:

    //Drawable drawable = getResources().getDrawable(R.drawable.ic_func);这种获取drawable效果是一样的
    Drawable drawable = iv_one.getDrawable();//
    Drawable wrap = DrawableCompat.wrap(drawable);
    DrawableCompat.setTint(wrap, ContextCompat.getColor(this,android.R.color.holo_red_dark));
    iv_one.setImageDrawable(wrap);
    iv_two.setImageResource(R.drawable.ic_func);
    

    效果如下:


    20200107112227.png

    代码实现二:

    //Drawable drawable = getResources().getDrawable(R.drawable.ic_func).mutate();这种获取drawable效果是一样的
    Drawable drawable = iv_one.getDrawable().mutate();//
    Drawable wrap = DrawableCompat.wrap(drawable);
    DrawableCompat.setTint(wrap, ContextCompat.getColor(this,android.R.color.holo_red_dark));
    iv_one.setImageDrawable(wrap);
    iv_two.setImageResource(R.drawable.ic_func);
    

    效果如下:


    20200107112204.png

    代码一和代码二的区别:

    在xml中都不添加tint属性的情况下,代码二多了一个mutate()方法,具体作用后面在说。只需要记住,如果你不想影响到该图片其他地方的使用,要添加mutate()方法

    代码实现三:

    //Drawable drawable = getResources().getDrawable(R.drawable.ic_func).mutate();这种获取drawable效果是一样的
    Drawable drawable = iv_one.getDrawable().mutate();//
    Drawable wrap = DrawableCompat.wrap(drawable);
    DrawableCompat.setTint(wrap, ContextCompat.getColor(this,android.R.color.holo_red_dark));
    iv_one.setImageDrawable(wrap);
    iv_two.setImageDrawable(drawable);
    

    这种写法效果和代码一相同。

    2.设置图片的点击效果
    • 注意:这个比较麻烦,主要是兼容问题

    先看一看一般的实现步骤:
    (1)首先在res下创建color资源目录,在color目录下创建一个color resource文件,如下:

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:color="@color/colorPrimary" android:state_pressed="true"/>
        <item android:color="@android:color/holo_red_dark"/>
    </selector>
    

    (2)代码中如下:

    //Drawable drawable = getResources().getDrawable(R.drawable.ic_func);
    Drawable drawable = iv_one.getDrawable();
    Drawable wrap = DrawableCompat.wrap(drawable).mutate();
    DrawableCompat.setTintList(wrap,ContextCompat.getColorStateList(this,R.color.click_style_color));
    iv_one.setImageDrawable(wrap);
    iv_one.setOnClickListener(new View.OnClickListener() {
        @Override
         public void onClick(View view) {
             Toast.makeText(MainActivity.this,"点击了",Toast.LENGTH_SHORT).show();
         }
    });
    

    效果如下:


    单击效果.gif

    适配兼容性问题:

    方案一:

    /**
     * 支持tintList着色ImageView
     */
    
    public class TintableImageView extends AppCompatImageView {
    
        private ColorStateList tint;
    
        public TintableImageView(Context context) {
    
            super(context);
    
        }
    
        public TintableImageView(Context context, AttributeSet attrs) {
    
            super(context, attrs);
    
            init(context, attrs, 0);
    
        }
    
        private void init(Context context, AttributeSet attrs, int defStyle) {
    
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TintableImageView, defStyle, 0);
    
            tint = a.getColorStateList(R.styleable.TintableImageView_iv_tint);
    
            a.recycle();
    
        }
    
        public TintableImageView(Context context, AttributeSet attrs, int defStyle) {
    
            super(context, attrs, defStyle);
    
            init(context, attrs, defStyle);
    
        }
    
        public void setColorFilter(ColorStateList tint) {
    
            this.tint = tint;
    
            super.setColorFilter(tint.getColorForState(getDrawableState(), 0));
    
        }
    
        public void setTintList(ColorStateList colorList) {
    
            if (colorList != null) {
    
                tint = colorList;
    
                drawableStateChanged();
    
            }
    
        }
    
        @Override
    
        protected void drawableStateChanged() {
    
            super.drawableStateChanged();
    
            if (tint != null && tint.isStateful())
    
                updateTintColor();
    
        }
    
        private void updateTintColor() {
    
            int color = tint.getColorForState(getDrawableState(), 0);
    
            setColorFilter(color);
    
        }
    
    }
    

    attr.xml中声明属性如下:

    <resources>
        <declare-styleable name="TintableImageView">
            <attr name="iv_tint" format="reference"/>
        </declare-styleable>
    </resources>
    

    代码中使用:

    TintableImageView iv_one;
    ... ...
    iv_one.setTintList(ContextCompat.getColorStateList(this,R.color.click_style_color));
    iv_two.setImageResource(R.drawable.ic_func);
    

    方案二:
    Android 着色器(TintList) 兼容性问题
    这里面也介绍了为啥会出现兼容性的问题,同时给出了一个解决方案,其实解决方案一也是从这个原理来解决的,就是updateTintFilter() 方法只是更新了tintFilter的color和mode,并没有触发Drawable的绘制,所以按下时的着色 tint color自然没有显示出来,这两个解决方案都是添加了重绘的方法。但是随着google官方的androidx替代support包之后,方案二中的android.support.v4.graphics.drawable.DrawableCompatLollipop已经不存在了,所以在使用androidx的项目中,方案二是不生效的。

    注意:

    关于DrawableCompat的wrap(Drawable drawable)方法

    通过这个方法获取的Drawable对象,在使用DrawableCompat类中的染色一类的方法,可以在不同的API级别上应用着色。
    因此想要着色就先把原先的Drawable对象wrap一下后回去到新的Drawable对象。

    上文提到的mutate()方法

    Android为了优化系统性能,同一张资源图片生成的Drawable实例在内存中只存在一份,在不使用mutate的情况下,修改任意Drawable都会全局发生变化。
    使用mutate,Android系统也没有把Drawable实例又单独拷贝一份,仅仅是单独存放了状态值,很小的一部分数据,Drawable实例在内存中仍然保持1份,因而并不会影响系统的性能。

    相关文章:
    Android Drawable / DrawableCompat # setTintList( ) 使用时一个值得注意的问题
    其实你不懂:Drawable着色(tint)的兼容方案 源码解析

    相关文章

      网友评论

        本文标题:ImageView动态着色以及兼容性问题

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