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

本文介绍如何利用一张矢量图实现不同颜色适配,减少资源占用;并通过StateListDrawable实现更优雅的Selector,降低APK体积并节省内存。


转载地址:http://www.jianshu.com/p/6bd7dd1cd491

学习tint的目的:

1.一张矢量图适配所有颜色(妈妈再也不要担心我找图了)。
2.更优雅的selector实现方式。

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

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


效果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大小又节约了内存。

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



文/昨天还是一个小白(简书作者)
原文链接:http://www.jianshu.com/p/6bd7dd1cd491
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

<think>我们正在讨论如何通过自定义XML文件修改SVG图片的tint(色调)属性。在Android开发中,我们通常使用VectorDrawable来渲染SVG图像(因为Android原生支持VectorDrawable,而SVG需要转换为VectorDrawable兼容格式)。修改tint的目的是改变图片的颜色,而不需要提供多张不同颜色的图片。根据引用[4]中的信息,在Android5.0(API21)及以上版本中,我们可以使用tint属性来改变VectorDrawable的颜色。但是,在早期版本中,我们需要使用兼容方式。方法一:使用XML的tint属性(API21+)我们可以直接在ImageView上设置tint属性,或者使用一个tint的drawableXML文件。例如,在res/drawable目录下创建一个XML文件(比如ic_arrow_tinted.xml):```xml<bitmapxmlns:android="http://schemas.android.com/apk/res/android"android:src="@drawable/ic_arrow"android:tint="@color/your_tint_color"/>```但是,这种方法对于VectorDrawable可能不直接支持。更常见的做法是使用AppCompat库提供的兼容方式。方法二:使用AppCompat的VectorDrawableCompat(支持低版本)从AppCompat23.2开始,支持了VectorDrawable到低版本(最低到API7)。我们可以使用AppCompatImageView,并设置tint属性。步骤:1.在build.gradle中确保使用了正确的AppCompat版本(至少23.2)。2.在布局文件中使用AppCompatImageView,并设置srcCompat属性为你的VectorDrawable,然后使用tint属性设置颜色。```xml<androidx.appcompat.widget.AppCompatImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"app:srcCompat="@drawable/ic_arrow"android:tint="@color/your_tint_color"/>```方法三:通过自定义drawableXML文件(使用selector或layer-list)我们可以创建一个带有tint的drawableXML文件,例如:```xml<?xmlversion="1.0"encoding="utf-8"?><selectorxmlns:android="http://schemas.android.com/apk/res/android"><itemandroid:drawable="@drawable/ic_arrow"android:tint="@color/your_tint_color"/></selector>```但是,这种方法在低版本上可能无效。因此,更推荐使用AppCompat的方式。方法四:在代码中设置tint(兼容低版本)我们可以在代码中设置tint使用AppCompatDrawableManager或者DrawableCompat。例如:```javaImageViewimageView=findViewById(R.id.imageView);Drawabledrawable=AppCompatResources.getDrawable(this,R.drawable.ic_arrow);drawable=DrawableCompat.wrap(drawable);DrawableCompat.setTint(drawable,ContextCompat.getColor(this,R.color.your_tint_color));imageView.setImageDrawable(drawable);```但是,用户要求通过自定义XML文件来实现,所以我们重点放在XML方法上。方法五:使用vectordrawable的android:tint属性(API21+)在定义vectordrawable时,可以直接在<vector>标签中指定tint属性(但注意,这个tint是固定的,不能通过外部XML修改)。方法六:使用ColorStateList(推荐)我们可以创建一个ColorStateList资源,然后在drawableXML中引用它。1.在res/color目录下创建一个颜色状态列表(例如tint_color.xml):```xml<?xmlversion="1.0"encoding="utf-8"?><selectorxmlns:android="http://schemas.android.com/apk/res/android"><itemandroid:color="@color/your_tint_color"android:state_enabled="true"/><itemandroid:color="@color/another_color"android:state_enabled="false"/><!--可以定义不同状态下的颜色--></selector>```2.然后在drawableXML中(比如创建一个新的drawable资源,命名为ic_arrow_tinted.xml):```xml<bitmapxmlns:android="http://schemas.android.com/apk/res/android"android:src="@drawable/ic_arrow"android:tint="@color/tint_color"/>```但是,这种方法同样只适用于API21+。为了兼容低版本,我们可以采用AppCompat库的方式,结合XML和主题属性。方法七:使用主题属性(Themeattributes)我们可以通过主题属性来设置tint,这样可以在不同主题下改变颜色。1.在主题中定义属性:```xml<stylename="AppTheme"parent="Theme.AppCompat.Light"><itemname="colorControlNormal">@color/primary</item></style>```2.在VectorDrawable中,使用主题属性作为tint颜色:```xml<vectorxmlns:android="http://schemas.android.com/apk/res/android"android:width="24dp"android:height="24dp"android:viewportWidth="24.0"android:viewportHeight="24.0"android:tint="?attr/colorControlNormal"><pathandroid:fillColor="#FF000000"android:pathData="M..."/></vector>```这样,ImageView使用这个VectorDrawable时,就会根据当前主题的colorControlNormal来着色。总结:如果只需要支持API21+,可以直接在XML中使用android:tint。如果需要兼容低版本,推荐使用AppCompat库,并在代码中设置tint,或者使用主题属性。但是,用户要求使用自定义XML文件,所以我们可以创建一个XML文件(如上面的方法二或方法六)来设置tint,但要注意兼容性。然而,根据引用[4]中的警告,在Android24之前,通过XML设置tint可能会导致效率问题,并且有状态覆盖的问题。因此,在低版本上,更安全的方式是在代码中设置tint。考虑到用户要求用XML文件,我们可以这样建议:对于API21+,可以创建一个drawableXML文件,使用<bitmap>元素并设置tint属性(如方法一)。但是,这种方法不支持VectorDrawable。所以对于VectorDrawable,我们可以使用AppCompat的方式(方法二)在布局文件中直接设置tint。如果一定要在drawable资源文件中定义带tint的VectorDrawable,我们可以尝试在<vector>标签内使用tint属性(方法五),但这样颜色就固定了,不能动态改变。因此,如果用户需要动态改变tint(比如根据状态改变颜色),那么使用ColorStateList(方法六)结合主题属性(方法七)可能是更好的选择。根据用户的需求,他可能想要一个自定义的XML文件,使得同一个SVG(VectorDrawable)可以显示不同的颜色。所以,我们可以创建一个选择器(selector)drawable,其中每个item指定不同的drawable和tint。但是,在低版本上,tint可能不被支持。所以,最可靠且兼容的方法是使用AppCompat库,并在代码中设置tint。如果坚持使用XML,那么只能在高版本上使用,或者接受在低版本上可能无法显示tint。建议:使用AppCompatImageView,并在布局文件中设置tint(方法二),这样在低版本上也能工作(通过AppCompat库实现兼容)。这样就不需要创建额外的自定义drawableXML文件,而是直接在布局文件中指定。但是,用户明确要求“自定义一个XML文件”,所以我们可以创建一个drawableXML文件,但是这种文件在低版本上可能无效。因此,我们创建一个XML文件,使用<vector>标签并设置tint属性(固定颜色):```xml<!--res/drawable/ic_arrow_tinted.xml--><vectorxmlns:android="http://schemas.android.com/apk/res/android"android:width="24dp"android:height="24dp"android:viewportWidth="24.0"android:viewportHeight="24.0"android:tint="@color/your_color"><pathandroid:fillColor="#FF000000"<!--注意:fillColor是图片的原始颜色,tint会覆盖它-->android:pathData="M..."/></vector>```然后,在布局文件中引用这个drawable:```xml<ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/ic_arrow_tinted"/>```这样,在高版本上,tint会生效。在低版本上(如果使用AppCompatImageView和srcCompat),VectorDrawable会显示,但tint可能不会生效(除非使用AppCompat方式设置tint)。因此,如果用户需要兼容低版本,并且必须使用XML文件,那么我们可以使用AppCompat的方式,但不是通过自定义drawableXML,而是通过布局文件设置tint(方法二)。或者,我们可以创建一个自定义的drawable,但是使用代码来设置tint(不在XML中)。最终,根据引用[4]的提示,在低版本上使用XML设置tint可能会遇到问题,所以如果用户的应用需要支持低版本,建议在代码中设置tint。但是,用户的需求很明确:通过自定义XML文件修改tint。所以,我们只能提供高版本可用的XML方案(方法五)或者使用AppCompat库的布局设置(方法二,但这不是自定义drawableXML,而是布局属性)。折中方案:创建一个自定义的drawableXML,但里面使用selector,并在不同状态下设置不同的drawable(每个drawable是一个vector,并设置不同的tint)。这样,即使低版本不支持tint,我们也可以提供不同颜色的图片(但这样需要多份vector资源,不推荐)。因此,最符合用户要求(自定义XML文件)且尽可能兼容的方案是:使用AppCompat库,并创建一个布局文件,在布局文件中使用AppCompatImageView并设置tint属性(这不是drawableXML,但可以达到效果)。如果用户坚持要drawableXML,那么只能放弃低版本兼容。考虑到用户可能没有说明兼容性要求,我们提供两种方案:方案A(高版本):在drawable资源中创建vector,并设置tint属性(固定颜色)。方案B(兼容):不在drawableXML中设置tint,而是使用布局XML文件设置tint使用AppCompatImageView)。方案C(动态颜色):使用主题属性,这样颜色可以跟随主题变化。由于用户要求自定义XML文件,我们假设他想要一个drawable资源文件,所以提供方案A。步骤:1.将SVG转换为AndroidVectorDrawable(可以使用AndroidStudio的转换工具)。2.在res/drawable目录下,创建一个XML文件(例如ic_arrow_tinted.xml),内容如下:```xml<vectorxmlns:android="http://schemas.android.com/apk/res/android"android:width="24dp"android:height="24dp"android:viewportWidth="24"android:viewportHeight="24"android:tint="#FF0000"><!--这里设置你想要的颜色,例如红色--><pathandroid:fillColor="#FF000000"android:pathData="M..."/><!--这里是你的SVG路径--></vector>```3.然后在布局文件中,使用ImageView引用这个drawable:```xml<ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@drawable/ic_arrow_tinted"/>```注意:这种方法在高版本(API21+)上有效,低版本上如果没有使用AppCompat,则可能无法显示VectorDrawable(即使使用AppCompat,tint也可能不生效)。因此,为了在低版本上使用VectorDrawable,请确保使用AppCompatImageView和srcCompat属性,但tint属性在低版本上可能不会生效。所以,如果用户需要低版本支持tint,那么建议使用代码设置。结论:通过自定义XML文件(VectorDrawable)设置tint属性,仅适用于API21+。对于更广泛的兼容性,建议在代码中使用DrawableCompat设置tint。根据引用[4]的内容,在24之前使用XML设置tint可能会遇到效率问题和状态覆盖问题,所以即使在高版本上,使用XML设置tint也可能不是最佳实践。因此,如果用户的应用目标版本较高(API21+),并且不介意在低版本上不显示tint(或者已经放弃低版本),那么可以使用上述方案A。最后,我们提供一种在低版本上也能工作的XML方案(但并不是通过tint属性,而是通过改变fillColor):我们可以直接修改VectorDrawable的fillColor,这样就不需要tint了。但是,这意味着我们需要为每种颜色创建一个VectorDrawable,不能动态改变。所以,如果用户想要动态改变颜色,还是建议使用代码设置。综上所述,我们建议用户根据目标API等级选择合适的方法。如果必须使用XML文件且目标API>=21,那么可以直接在<vector>标签中设置tint。否则,考虑使用代码设置。</think>### 通过自定义 XML 文件修改 SVG 图片的 tint 属性 在 Android 开发中,可以通过创建自定义的 **ColorStateList** XML 文件结合 **VectorDrawable** 来实现 SVG 图片的 tint(色调)修改。以下是具体步骤: --- #### 方法一:使用 XML 定义 tint(推荐) 1. **创建 ColorStateList 文件** 在 `res/color/` 目录下创建 XML 文件(如 `svg_tint.xml`): ```xml <!-- res/color/svg_tint.xml --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 默认颜色 --> <item android:color="#FF4081"/> <!-- 粉色 --> <!-- 按下状态颜色 --> <item android:state_pressed="true" android:color="#3F51B5"/> <!-- 蓝色 --> </selector> ``` 2. **在 VectorDrawable 中引用 tint** 修改 SVG 对应的 VectorDrawable 文件(如 `ic_svg_icon.xml`): ```xml <!-- res/drawable/ic_svg_icon.xml --> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="@color/svg_tint"> <!-- 关键 tint 属性 --> <path android:fillColor="#FF000000" <!-- 原始颜色(会被 tint 覆盖) --> android:pathData="M12,2L3,9v11h18V9L12,2z"/> </vector> ``` 3. **在布局中使用** ```xml <ImageView android:layout_width="40dp" android:layout_height="40dp" android:src="@drawable/ic_svg_icon"/> ``` > **效果**:图片会显示为粉色,按下时变为蓝色。 --- #### 方法二:动态代码设置(灵活控制) 若需运行时修改 tint,可在代码中实现: ```java ImageView imageView = findViewById(R.id.imageView); Drawable drawable = AppCompatResources.getDrawable(this, R.drawable.ic_svg_icon); // 包装 Drawable 并设置 tint Drawable wrappedDrawable = DrawableCompat.wrap(drawable); DrawableCompat.setTintList( wrappedDrawable, ContextCompat.getColorStateList(this, R.color.svg_tint) ); imageView.setImageDrawable(wrappedDrawable); ``` --- #### 关键注意事项 1. **兼容性**: - `android:tint` 属性需要 **API 21+**(Android 5.0+) - 低版本(API 21以下)需使用 **AppCompat** 库: ```gradle implementation 'androidx.appcompat:appcompat:1.4.0' ``` 并在布局中使用 `app:tint` 替代: ```xml <androidx.appcompat.widget.AppCompatImageView app:tint="@color/svg_tint" android:src="@drawable/ic_svg_icon"/> ``` 2. **性能优化**: - 避免在 `VectorDrawable` 中直接修改 `android:fillColor`(会导致重新渲染) - 优先使用 `android:tint` 或 `app:tint` 减少资源开销[^4] 3. **状态管理**: - 在 `ColorStateList` 中可定义多状态(按下/选中/禁用等) - 使用主题属性实现动态换肤: ```xml <item android:color="?attr/colorPrimary"/> <!-- 跟随主题色 --> ``` --- #### 示例项目结构 ``` res/ ├── drawable/ │ └── ic_svg_icon.xml <!-- SVG 矢量图 --> ├── color/ │ └── svg_tint.xml <!-- 色调定义 --> └── layout/ └── activity_main.xml <!-- 使用 ImageView --> ``` 通过这种方式,您无需修改 SVG 源文件即可动态控制其色调,同时保持图像的矢量特性(无损缩放)[^1][^2]。 --- ### 相关问题 1. 如何在 Android 低版本(API < 21)中兼容 SVG 的 tint 效果? 2. VectorDrawable 与普通 SVG 文件在 Android 中有何区别? 3. 如何通过代码实现 SVG 图片的渐变着色(Gradient Tint)? 4. SVG 图片在 Android 中出现渲染模糊的可能原因及解决方法?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值