Как использовать селектор для подкрашивания ImageView?

Я хочу подкрасить значки моего табулятора с помощью XML, а не программно (я все равно не смог этого сделать)...

Я нашел этот поток на SO: Android imageview изменяет оттенок для имитации нажатия кнопки< /а>

Это кажется довольно хорошим решением, но я не смог правильно адаптировать его в своем проекте... Я сделал следующие изменения:

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);
    }

}

Я также не смог сослаться на @drawable/selector.xml в android:tint, поэтому я сделал это в 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>

Мой селектор:

<?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>

Мой макет вкладок:

<?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>

Какие-либо предложения? заранее спасибо

РЕДАКТИРОВАТЬ

Я получал NumberFormatException за использование android:tint, когда правильным было app:tint (после установки xmlns:app="http://schemas.android.com/apk/res/com.myapp")... но теперь я думаю, что неправильно использую свой селектор, потому что значки все черные, независимо от состояния.. , Я пытался установить <drawable name="tab_icon_selector">@drawable/tab_icon_selector</drawable> из colors.xml, не сработало.


person Lucas Jota    schedule 21.10.2013    source источник
comment
Плохо, я не вставил полный код TintableImageView, теперь он завершен в вопросе... Я просто вставил ваш код в свой класс, единственное изменение было в _1_... кстати, IntelliJ говорит, что setColorFilter( ) и ни один из конструкторов никогда не используется...   -  person mpellegr    schedule 17.07.2014


Ответы (6)


Что касается моего решения на https://stackoverflow.com/a/18724834/2136792, есть несколько вещей, которые вы отсутствуют:

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() необходимо переопределить, чтобы оттенок обновлялся при изменении состояния элемента.

Я не уверен, что ссылка на рисуемый объект может вызвать проблему, но вы можете просто переместить файл selector.xml в папку «/res/color», чтобы сослаться на него с помощью «@color/selector.xml» (aapt объединяет как /res/values/colors.xml, так и в папке /res/color).

person Stephen Kidson    schedule 22.10.2013
comment
Отлаживая свой код, я заметил следующее поведение: 1) init() вызывается в первый раз 2) вызывается drawableStateChanged(), но tint=null, поэтому он не вызывает updateTintColor() 3) init() вызывается еще 3 раза (я пытаюсь подкрасить 4 изображения), но с этого момента init() больше не вызывается - person Lucas Jota; 23.10.2013
comment
Наконец-то я понял это! Я использовал drawableStateChanged(), а правильный _2_... Спасибо, что поделились своими знаниями! - person Lucas Jota; 23.10.2013
comment
Добавление TypedArray a = context.obtainStyledAttributes(attrs, new int[]{R.styleable.TintableImageView_tint}, defStyle, 0); в TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TintableImageView, defStyle, 0); помогло мне - person Lucas Jota; 23.10.2013
comment
как сделать это обратно совместимым? - person XurajB; 14.08.2016

Если вы используете API 21+, вы можете легко сделать это в XML с помощью селектора и оттенка. :

<?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>
person Christopher Perry    schedule 05.02.2015
comment
@bEtTyBarnes использует атрибуты _1_ и _2_ xml для совместимости с устройствами до леденцов. - person ; 14.11.2015
comment
к вашему сведению, это не сработает, если ваш рисунок находится в формате xml. - person nradk; 17.02.2018
comment
Используйте _1_ вместо _2_, если _3_ представляет собой форму в формате xml. - person Dhunju_likes_to_Learn; 18.04.2018
comment
Имейте в виду, что если ваш селектор использует атрибуты стиля, они, похоже, не разрешаются автоматически. Вам нужно будет вызвать <shape />, чтобы это произошло, и, к сожалению, для этого требуется API 21. - person Peike; 16.07.2018

Я реализовал это с помощью DrawableCompat из библиотеки Android support-v4.

С обычным ImageButton (который является подклассом ImageView, так что эта информация также применима к ImageViews) с использованием черного значка из коллекции материальных значков:

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

Это метод утилиты, который я создал:

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);
}

Где res/color/button_colour.xml — это селектор, который меняет цвет значка с красного на полупрозрачный красный при нажатии кнопки:

<?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>

После того, как ImageButton был раздут в методе onCreate() моей активности, я просто вызываю вспомогательный метод tintButton(...) один раз для каждой кнопки.


Я тестировал это на устройствах Android 4.1 (мой minSdkVersion) и 5.0, но DrawableCompat должен работать и на Android 1.6.

person Christopher Orr    schedule 05.08.2015
comment
@SkyKelsey Возможно, использование DrawableCompat.setTheme() работает вместо _2_? - person Sky Kelsey; 24.03.2016
comment
Да, спасибо. Это работает. Мне по-прежнему приходится вручную применять включенное/отключенное состояние к API ‹ 21, но это позволяет мне избежать вызова API ›= 21 ContextCompat.getColorStateList(). - person Christopher Orr; 29.03.2016
comment
Добавление setTheme() в _2_ помогло мне - person Sky Kelsey; 30.03.2016
comment
Я не могу заставить это работать в API v19 - мои кнопки изображения окрашены в красный цвет, независимо от цветов, указанных в селекторе. Отлично работает в API v23! @SkyKelsey, что вы имели в виду, когда сказали, что мне все еще нужно вручную применять включенное/отключенное состояние к API ‹ 21. Есть ли какой-то дополнительный шаг, который я пропустил? Пожалуйста, опишите, если это так. Спасибо. - person XurajB; 14.08.2016
comment
Большое спасибо @ChristopherOrr, ваш ответ спас мне жизнь - person Sky Kelsey; 10.10.2016
comment
@SkyKelsey, вы упомянули _1_ для API 21. Это сработало для вас (конечно, на API ›= 21)? Если да, не могли бы вы уточнить, как/когда вы это назвали? - person Maryoomi1; 27.09.2018
comment
Я согласен с вами, создание подкласса ImageView - это хлопотно, и его следует избегать, если это возможно. Не могли бы вы привести полный пример? - person ernazm; 21.11.2018

С библиотекой поддержки 22.1 мы можем использовать DrawableCompat для подкрашивания рисунков, уровень API 4+.

DrawableCompat.wrap(Drawable) и setTint(), setTintList() и setTintMode() будут просто работать: не нужно создавать и поддерживать отдельные чертежи только для поддержки нескольких цветов!

person bugraoral    schedule 22.04.2015
comment
Спасибо, что упомянули подсказку о app:tint вместо android:tint. - person Nimrod Dayan; 22.06.2015

Я согласен с @Dreaming in Code и приведу пример.

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>

макет/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"/>

Внимание: мы должны использовать app:tint вместо android:tint.

Моя версия библиотеки поддержки — 26.0.2.

приложение/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'

Если мы используем android:tint, произойдет сбой, и журнал будет выглядеть так:

С текущей библиотекой поддержки AppCompat вы можете использовать

<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>
на теге
<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"/>
, который будет расширен как
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'
и правильно обрабатывать изменение состояния.

person Francis Bacon    schedule 15.11.2017
comment
Привет, вы указали селектор xml в качестве srcCompat, это правильно? - person Javad; 25.01.2018
comment
+1, но XML селектора является цветным и назначен app:tint; вектор, который можно нарисовать, назначается app:srcCompat. Из того, что вы написали, кажется, что drawable - это селектор, что неверно. - person Manukumar; 28.08.2018
comment
E/AndroidRuntime: НЕИСПРАВНОЕ ИСКЛЮЧЕНИЕ: основной android.view.InflateException: строка двоичного XML-файла № 0: ошибка при раздувании класса в android.view.LayoutInflater.createView(LayoutInflater.java:613) в android.view.LayoutInflater.createViewFromTag(LayoutInflater. java:687) в android.view.LayoutInflater.rInflate(LayoutInflater.java:746) в android.view.LayoutInflater.inflate(LayoutInflater.java:489) в android.view.LayoutInflater.inflate(LayoutInflater.java:396) в com.opera.six.viewholder.post.PostCoun tInfoViewHolder$1.create(PostCountInfoViewHolder.java:29) в com.opera.six.viewholder.post.PostCountInfoViewHolder$1.create(PostCountInfoViewHolder.java:25) в com.opera.six.collection.CollectionAdapter.onCreateViewHolder(CollectionAdapter.java: 39) в com.opera.six.collection.CollectionAdapter.onCreateViewHolder(CollectionAdapter.java:19) в android.support.v7.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:6493) в android.support.v7.widget .RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5680) на Android. support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5563) в android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5559) в android.support.v7.widget.LinearLayoutManager$ LayoutState.next(LinearLayoutManager.java:2229) в android.support.v7.widget. LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1556) в android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1516) в android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:608) в android .support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3693) в android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3410) в android.support.v7.widget.RecyclerView.onLayout(RecyclerView .java:3962) в android.view.View.layout(View.java:13754) в android.view.ViewGroup.layout(ViewGroup.java:4364) в android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:610) в android.view.View.layout(View.java:13754) в android.view.ViewGroup.layout(ViewGroup.java:4364) в android.support.design.widget.HeaderScrollingViewBehavior.layoutChild(HeaderScrollingViewBehavior.java:132) в android.support.design.widget.ViewOffsetBehavior.onLayoutChild(ViewOffsetBehavior.java: 42) на android.support.design.widget.AppBarLayout$ScrollingViewBehavior.onL ayoutChild(AppBarLayout.java:1361) в android.support.design.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:869) в android.view.View.layout(View.java:13754) в android.view.ViewGroup.layout (ViewGroup.java:4364) на android.support.v4.view. ViewPager.onLayout(ViewPager.java:1767) в android.view.View.layout(View.java:13754) в android.view.ViewGroup.layout(ViewGroup.java:4364) в android.widget.LinearLayout.setChildFrame(LinearLayout) .java:1649) в android.widget.LinearLayout.layoutVertical(LinearLayout.java:1507) в android.widget.LinearLayout.onLayout(LinearLayout.java:1420) в android.view.View.layout(View.java:13754) в android.view.ViewGroup.layout(ViewGroup.java:4364) в android.widget.FrameLayout.onLayout(FrameLayout.java:448) в android.view.View.layout(View.java:13754) в android.view.ViewGroup.layout(ViewGroup.java:4364) в android.widget. FrameLayout.onLayout(FrameLayout.java:448) в android.view.View.layout(View.java:13754) в android.view.ViewGroup.layout(ViewGroup.java:4364) в android.widget.LinearLayout.setChildFrame(LinearLayout) .java:1649) в android.widget.LinearLayout.layoutVertical(LinearLayout.java:1507) в android.widget.LinearLayout.onLayout(LinearLayout.java:1420) в android.view.View.layout(View.java:13754) в android.view.ViewGroup.layout(ViewGroup.java:4364) в android.widget. FrameLayout.onLayout(FrameLayout.java:448) в android.view. View.layout(View.java:13754) в android.view.ViewGroup.layout(ViewGroup.java:4364) в android.widget.FrameLayout.onLayout(FrameLayout.java:448) в android.view.View.layout(View .java:13754) в android.view.ViewGroup.layout(Vi - person Giulio Piancastelli; 07.11.2018

В AppCompatImageView вы можете видеть, что mImageHelper уведомлен об изменении состояния:

Android Studio в настоящее время выдает предупреждение об этом, но вы можете безопасно подавить его.

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

В методе init() должно быть указано TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TintableImageView, defStyle, 0); оттенок = a.getColorStateList (R.styleable.TintableImageView_tint); а.переработать(); как и исходный ответ, иначе это не сработает.

person Hai Zhang    schedule 19.06.2017