Android ColorFilter and Tint

本文探讨了Android中ColorFilter和Tint的区别与联系,它们都是用于改变图像颜色的效果。Tint在API 21及以上版本引入,主要用于BitmapDrawable和NinePatchDrawable,可以设置颜色或主题属性进行着色。文章通过示例展示了ImageView和Drawable应用Tint的不同方法,并指出对于多状态着色,ImageButton比ImageView表现更好。最后提到,系统原生支持的多状态Tint仅限于特定视图,期待更好的解决方案。

概述

关于Android ColorFilter 和 Tint之间的关系一直混淆不清。两者均是对显示的图片进行着色或者过滤。

ColorFilter: 色彩过滤
Tint: 着色

从英文原意上来讲,似乎有些相似,而从实际的效果来讲也是一致的。Android 向导文档似乎对此也是一笔带过,不愿深入,让人有些摸不着头脑:

With Android 5.0 (API level 21) and above, you can tint bitmaps and nine-patches defined as alpha masks. You can tint them with color resources or theme attributes that resolve to color sources (for example, ?android:attr/colorPrimary). Usually, you create these assets only once and color them automatically to match your theme.

You can apply a tint to BitmapDrawable or NinePatchDrawable objects with the setTint() method. You can also set the tint color and mode in your layouts with the android:tint and android:tintMode attributes.

这段文字讲述的大意是:Tint 是 API 21之后才添加的功能,可以对BitmapDrawableNinePatchDrawable 应用Tint 效果。使用tint效果可以省去我们为不同theme创建同一张图片的多个版本的麻烦。紧接着第二段讲道如何使用tint, 有两种方法:

1 调用函数:
 class Drawable {
     ...
     public void setTint (int tint);                       //Added in API 21
     public void setTintMode (PorterDuff.Mode tintMode);   //Added in API 21
     ...
}
2 XML Layout:

drawable file location: res/drawable/tintedrawable.xml

<nine-path xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/rawdrawable"
    android:tint="@color/what_ever_color"
    android:tintMode="multiply" />

layout file location: res/layout/layout_main.xml


<View
    android:id="@+id/my_view_id"
    android:layout_width="20dp"
    android:layout_height="10dp"
    android:background="@drawable/tintedrawable"/>

ImageView and Tint

class ImageView {

    public final void setColorFilter (int color, PorterDuff.Mode mode); //Added in API 1
    public void setImageTintList (ColorStateList tint);                 //Added in API 21
    public void setImageTintMode (PorterDuff.Mode tintMode);            //Added in API 21

}

除了 ImageView 可以继续使用setColorFilter(int, PorterDuff.Mode), API 21 也给ImageView 添加了setImageTintList(), setImageTintMode(), android:tint, android:tintMode 等特性!


首先我们看一个Sample, 给一个ImageView 使用这三种不同方法来着色。

        <!-- 方法一使用setColorFilter -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="setColorFilter" />

        <ImageView
            android:id="@+id/image1"
            android:layout_width="200dp"
            android:layout_height="50dp"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/rawdrawable"
            android:scaleType="fitXY"/>

        <!--方法二使用setImageTintList -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="setImageTintList" />

        <ImageView
            android:id="@+id/image2"
            android:layout_width="200dp"
            android:layout_height="50dp"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/rawdrawable"
            android:scaleType="fitXY"/>

        <!--方法三使用XML android:tint 属性 -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="android:tint" />

        <ImageView
            android:id="@+id/image3"
            android:layout_width="200dp"
            android:layout_height="50dp"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/rawdrawable"
            android:scaleType="fitXY"
            android:tint="#673AB7"
            android:tintMode="multiply"/>
    @Override
    public View onCreateView(LayoutInflater inflater,
         ViewGroup container, Bundle savedInstanceState) {

        View v = inflater.inflate(R.layout.tinting_fragment, null);

        mImage1 = (ImageView) v.findViewById(R.id.image1);

        mImage2 = (ImageView) v.findViewById(R.id.image2);

        updateTint( Color.argb(0xFF, 0x67, 0x3A, 0xB7),
            PorterDuff.Mode.MULTIPLY );
    }

    public void updateTint(int color, PorterDuff.Mode mode) {
        mImage1.setColorFilter(color, mode);
        mImage2.setImageTintList(ColorStateList.valueOf(color));
        mImage2.setImageTintMode(mode);
    }

显示效果

从上图可以看到, 其实对于ImageView 不管采用何种方法,其最终的结果都是一样的。


我们看到ImageView.setImageTintList(ColorStateList) 实际上是接受一个ColorStateList参数,上面我们android:tint="#673AB7"的tint值是一个单一的颜色,如果改成ColorStateList那效果如何呢?

1 res/color/custom_tint.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Pressed state -->
    <item android:color="#ff0000" android:state_pressed="true" />
    <!-- Default -->
    <item android:color="#4aff48" />
</selector>
2 res/layout/layout_main.xml
 <ImageView
            android:id="@+id/image3"
            android:clickable="true"
            android:layout_width="200dp"
            android:layout_height="50dp"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/rawdrawable"
            android:scaleType="fitXY"
            android:tint="@color/custom_tint"
            android:tintMode="multiply"/>

显示效果

当点击最下面的ImageView 的时候, 颜色并没有随着状态的改变而改变。接着我们再把ImageView 改成ImageButton:

 <ImageButton
            android:id="@+id/image3"
            android:layout_width="200dp"
            android:layout_height="50dp"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/rawdrawable"
            android:scaleType="fitXY"
            android:tint="@color/custom_tint"
            android:tintMode="multiply"/>

显示效果


Drawable and Tint

文档里面明确讲明了tint的引入是为了对Drawable 进行着色支持。至少对于BitmapDrawableNinePatchDrawable 文档明确指明了支持 android:tintandroid:tintMode 属性。这样的话我们对于android:tint的属性的支持就很容易从ImageView扩展到任何View, 只要将Viewandroid:background指向我们自定义的drawable:

1 res/drawable/tintedrawable.xml
<bitmap xmlns="http://schemas.android.com/apk/res/android"
    android:src="@drawable/background_image"
    android:tint="@color/custom_tint"
    android:tintMode="multiply" />
2 res/layout/layout_main.xml
 <View
            android:id="@+id/image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:background="@drawable/tintedrawable"/>

但是和上面的问题一样,使用这种方法不支持多状态的情形。而如果我们将xml layout 中的View 改成ImageButton并且将android:background 改成android:src问题就解决了: 

 <ImageButton
            android:id="@+id/image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/tintedrawable"
            android:scaleType="centerInside"/>

显示效果

上面的两张图片分别是在默认状态和按下状态的ImageButton着色的效果。如果换成其他的View, 红色着色并不会出现,但是仍然会有绿色的着色效果。

通过以上的分析,发现对于tint在各个状态下自动改变着色色彩的支持仍然仅仅局限于ImageButton, 这里或许我们可以通过自定义View的方法来修正这个问题,但是如果能够做到系统原生的支持当然更好,期待读者能够提供更好的方法。

/*********************************************************** * Copyright NEC Corporation 2025. All rights reserved * No permission to use, copy, modify and distribute this * software and its documentation for any purpose is granted. * This software is provided under applicable license * agreement only. * * システム名 : JAF RSシステム * サブシステム名 : 車業アプリ * クラス名 : JAFRSHO10010DropText * * @author NEC * ***********************************************************/ package jp.or.jaf.syg.feature.jafrsho10.jafrsho10010.components import android.annotation.SuppressLint import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import jp.or.jaf.syg.core.designsystem.theme.FigmaDesign.dp import jp.or.jaf.syg.core.designsystem.theme.FigmaDesign.sp import jp.or.jaf.syg.core.designsystem.theme.dropdownMenuBox import jp.or.jaf.syg.core.designsystem.theme.font.LocalNotoSansJPFontFamily import jp.or.jaf.syg.core.designsystem.theme.resources import jp.or.jaf.syg.core.designsystem.theme.tableText import jp.or.jaf.syg.core.ui.components.Item /** * カスタムドロップダウンメニュー(昼夜モード対応) * * @param item 選択項目 * @param onClick コールバックをクリック * @param modifier Modifier * @param maxDisplayLength 最大文字数を表す * @param enabled 有効状態 */ @Composable fun JAFRSHO10010DropText( item: Item, onClick: () -> Unit = {}, @SuppressLint("ModifierParameter") modifier: Modifier = Modifier, maxDisplayLength: Int = Int.MAX_VALUE, enabled: Boolean = true, isExpanded: Boolean = false ) { val arrowIconRes = if (isExpanded) { MaterialTheme.resources.dropDownMenuRight() } else { MaterialTheme.resources.dropDownMenuDown() } // カラー設定 val textColor = MaterialTheme.colorScheme.tableText val iconColorFilter = ColorFilter.tint(textColor) Box( modifier = modifier .clickable(enabled = enabled) { onClick() } .background( color = MaterialTheme.colorScheme.dropdownMenuBox, shape = RoundedCornerShape(4.dp) ) ) { Row(verticalAlignment = Alignment.CenterVertically) { Text( text = item.value.let { if (it.length > maxDisplayLength) { it.take(maxDisplayLength - 1) + "…" } else { it } }, style = MaterialTheme.typography.bodyMedium.copy( color = textColor, fontSize = 22.sp, fontFamily = LocalNotoSansJPFontFamily.current, fontWeight = FontWeight.Medium ), modifier = Modifier.padding(start = 20.dp) ) Image( painter = painterResource(id = arrowIconRes), contentDescription = if (isExpanded) { "Drop Down Menu Right" } else { "Drop Down Menu Down" }, colorFilter = iconColorFilter, modifier = Modifier.size(24.dp).padding(end = 20.dp) ) } } } 这个代码中为什么右边的图片image与最右端不是20.dp,画面上看不到图片了,如果把.padding(end = 20.dp)去掉,图片现在在最右端
最新发布
09-27
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值