I want to tint my tabhost's icons using xml, instead of doing it programatically (I wasn't able to do that anyway)... So I found this thread on SO: Android imageview change tint to simulate button click
That seems to be a pretty good solution, but I wasn't able to adapt it correctly in my project... I did the following changes:
public class TintableImageView extends ImageView {
private ColorStateList tint;
public TintableImageView(Context context) {
super(context);
}
//this is the constructor that causes the exception
public TintableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public TintableImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
//here, obtainStyledAttributes was asking for an array
private void init(Context context, AttributeSet attrs, int defStyle) {
TypedArray a = context.obtainStyledAttributes(attrs, new int[]{R.styleable.TintableImageView_tint}, defStyle, 0);
tint = a.getColorStateList(R.styleable.TintableImageView_tint);
a.recycle();
}
@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);
}
}
I also wasn't able to reference @drawable/selector.xml at android:tint, so I did this at colors.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="azulPadrao">#2e7cb4</color>
<drawable name="tab_icon_selector">@drawable/tab_icon_selector</drawable>
</resources>
My selector:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:tint="#007AFF" />
<item android:state_focused="true" android:tint="#007AFF" />
<item android:state_pressed="true" android:tint="#007AFF" />
<item android:tint="#929292" />
</selector>
My tab layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:id="@+id/TabLayout"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:gravity="center" android:background="@drawable/tab_bg_selector">
<com.myapp.TintableImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView" android:layout_gravity="center" android:tint="@drawable/tab_icon_selector"/>
<TextView android:id="@+id/TabTextView" android:text="Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:textColor="@drawable/tab_text_selector"
android:textSize="10dip"
android:textStyle="bold" android:layout_marginTop="2dip"/>
</LinearLayout>
Any suggestions? Thanks in advance
[EDIT] I was getting a NumberFormatException for using android:tint, when the correct was app:tint (after setting xmlns:app="http://schemas.android.com/apk/res/com.myapp")... but now I think I'm using my selector in a wrong way, because the icons are all black, no matter the state...
I've tried setting <drawable name="tab_icon_selector">@drawable/tab_icon_selector</drawable> from within colors.xml, didn't work
[/EDIT]
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.
来源:https://stackoverflow.com/questions/19500039/how-to-use-selector-to-tint-imageview-in-android