How to use selector to tint imageview in android

巧了我就是萌 提交于 2019-11-27 03:45:08
Stephen Kidson

In reference to my solution at https://stackoverflow.com/a/18724834/2136792, there are a few things you're missing:

TintableImageView.java

@Override
protected void drawableStateChanged() {
    super.drawableStateChanged();
    if (tint != null && tint.isStateful())
        updateTintColor();
}

public void setColorFilter(ColorStateList tint) {
    this.tint = tint;
    super.setColorFilter(tint.getColorForState(getDrawableState(), 0));
}

private void updateTintColor() {
    int color = tint.getColorForState(getDrawableState(), 0);
    setColorFilter(color);
}

drawableStateChanged() must be overridden for the tint to be updated when the element's state changes.

I'm not sure if referencing a drawable from a drawable might cause an issue, but you can simply move your selector.xml into a folder "/res/color" to reference it with "@color/selector.xml" (aapt merges both /res/values/colors.xml and the /res/color folder).

If you're in API 21+ you can do this easily in XML with a selector and tint:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_activated="true">
        <bitmap android:src="@drawable/ic_settings_grey"
                android:tint="@color/primary" />
    </item>

    <item android:drawable="@drawable/ic_settings_grey"/>
</selector>

I implemented this using DrawableCompat from the Android support-v4 library.

With a regular ImageButton (which subclasses ImageView, so this info also applies to ImageViews), using a black icon from the material icons collection:

<ImageButton
  android:id="@+id/button_add"
  android:src="@drawable/ic_add_black_36dp"
  android:background="?attr/selectableItemBackgroundBorderless"
  android:contentDescription="@string/title_add_item" />

This is the utility method I created:

public static void tintButton(@NonNull ImageButton button) {
    ColorStateList colours = button.getResources()
            .getColorStateList(R.color.button_colour);
    Drawable d = DrawableCompat.wrap(button.getDrawable());
    DrawableCompat.setTintList(d, colours);
    button.setImageDrawable(d);
}

Where res/color/button_colour.xml is a selector that changes the icon colour from red to semi-transparent red when the button is pressed:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item
      android:state_pressed="false"
      android:color="@color/red" />

    <item
      android:color="@color/red_alpha_50pc" />

</selector>

After the ImageButton has been inflated in my activity's onCreate() method, I just call the tintButton(...) helper method once for each button.


I have tested this on Android 4.1 (my minSdkVersion) and 5.0 devices, but DrawableCompat should work back to Android 1.6.

With support library 22.1 we can use DrawableCompat to tint drawable, API level 4+

DrawableCompat.wrap(Drawable) and setTint(), setTintList(), and setTintMode() will just work: no need to create and maintain separate drawables only to support multiple colors!

I agree with @Dreaming in Code and I will give an example.

ic_up_small

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:color="@color/comment_count_selected_color" android:state_selected="true" />
    <item android:color="@color/comment_count_text_color"/>

</selector>

layout/item_post_count_info.xml

<android.support.v7.widget.AppCompatImageView
    android:id="@+id/post_upvote_icon"
    android:layout_width="14dp"
    android:layout_height="14dp"
    android:layout_marginLeft="17dp"
    app:srcCompat="@drawable/ic_up_small"
    app:tint="@color/post_up_color"/>

Attention: We should use app:tint instead of android:tint.

My support library version is 26.0.2.

app/build.gradle

implementation 'com.android.support:appcompat-v7:26.0.2'
implementation 'com.android.support:support-core-utils:26.0.2'
implementation 'com.android.support:support-annotations:26.0.2'
implementation 'com.android.support:support-v4:26.0.2'
implementation 'com.android.support:design:26.0.2'

If we use android:tint, it will crash and the log is like this:

E/AndroidRuntime: FATAL EXCEPTION: main android.view.InflateException: Binary XML file line #0: Error inflating class at android.view.LayoutInflater.createView(LayoutInflater.java:613) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:687) at android.view.LayoutInflater.rInflate(LayoutInflater.java:746) at android.view.LayoutInflater.inflate(LayoutInflater.java:489) at android.view.LayoutInflater.inflate(LayoutInflater.java:396) at com.opera.six.viewholder.post.PostCountInfoViewHolder$1.create(PostCountInfoViewHolder.java:29) at com.opera.six.viewholder.post.PostCountInfoViewHolder$1.create(PostCountInfoViewHolder.java:25) at com.opera.six.collection.CollectionAdapter.onCreateViewHolder(CollectionAdapter.java:39) at com.opera.six.collection.CollectionAdapter.onCreateViewHolder(CollectionAdapter.java:19) at android.support.v7.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:6493) at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5680) at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5563) at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5559) at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2229) at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1556) at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1516) at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:608) at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3693) at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3410) at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3962) at android.view.View.layout(View.java:13754) at android.view.ViewGroup.layout(ViewGroup.java:4364) at android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:610) at android.view.View.layout(View.java:13754) at android.view.ViewGroup.layout(ViewGroup.java:4364) at android.support.design.widget.HeaderScrollingViewBehavior.layoutChild(HeaderScrollingViewBehavior.java:132) at android.support.design.widget.ViewOffsetBehavior.onLayoutChild(ViewOffsetBehavior.java:42) at android.support.design.widget.AppBarLayout$ScrollingViewBehavior.onLayoutChild(AppBarLayout.java:1361) at android.support.design.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:869) at android.view.View.layout(View.java:13754) at android.view.ViewGroup.layout(ViewGroup.java:4364) at android.support.v4.view.ViewPager.onLayout(ViewPager.java:1767) at android.view.View.layout(View.java:13754) at android.view.ViewGroup.layout(ViewGroup.java:4364) at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1649) at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1507) at android.widget.LinearLayout.onLayout(LinearLayout.java:1420) at android.view.View.layout(View.java:13754) at android.view.ViewGroup.layout(ViewGroup.java:4364) at android.widget.FrameLayout.onLayout(FrameLayout.java:448) at android.view.View.layout(View.java:13754) at android.view.ViewGroup.layout(ViewGroup.java:4364) at android.widget.FrameLayout.onLayout(FrameLayout.java:448) at android.view.View.layout(View.java:13754) at android.view.ViewGroup.layout(ViewGroup.java:4364) at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1649) at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1507) at android.widget.LinearLayout.onLayout(LinearLayout.java:1420) at android.view.View.layout(View.java:13754) at android.view.ViewGroup.layout(ViewGroup.java:4364) at android.widget.FrameLayout.onLayout(FrameLayout.java:448) at android.view.View.layout(View.java:13754) at android.view.ViewGroup.layout(ViewGroup.java:4364) at android.widget.FrameLayout.onLayout(FrameLayout.java:448) at android.view.View.layout(View.java:13754) at android.view.ViewGroup.layout(Vi

With current AppCompat support library, you can use app:tint on ImageView tag which will be inflated as AppCompatImageView and handle the state change properly.

In AppCompatImageView, you can see that mImageHelper is notified of the state change:

@Override
protected void drawableStateChanged() {
    super.drawableStateChanged();
    if (mBackgroundTintHelper != null) {
        mBackgroundTintHelper.applySupportBackgroundTint();
    }
    if (mImageHelper != null) {
        mImageHelper.applySupportImageTint();
    }
}

Android Studio currently gives a warning on this, but you can safely suppress it.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!