Android AppCompat 23.1.0 Tint Compound Drawable

Я использовал метод ниже, чтобы правильно подкрасить составные чертежи с помощью android.support.design 23.0.1. Теперь, когда они выпустили 23.1.0, он больше не работает на API LVL16, все мои рисунки черные.

У кого-нибудь есть предложение?

  private void setCompoundColor(TextView view) {
    Drawable drawable = view.getCompoundDrawables()[0];
    Drawable wrap = DrawableCompat.wrap(drawable);
    DrawableCompat.setTint(wrap, ContextCompat.getColor(this, R.color.primaryLighter2));
    DrawableCompat.setTintMode(wrap, PorterDuff.Mode.SRC_IN);
    wrap = wrap.mutate();
    view.setCompoundDrawablesRelativeWithIntrinsicBounds(wrap, null, null, null);
  }

Спасибо.


person Philippe David    schedule 19.10.2015    source источник
comment
проверьте этот ответ на наличие обновлений.   -  person Amit Vaghela    schedule 09.03.2016
comment
Код от Philippe David работает, но по моему опыту вы должны писать wrap = wrap.mutate(); перед DrawableCompat.setTint(). В противном случае он не будет работать должным образом, поскольку исходный рисунок будет изменен.   -  person marius    schedule 11.04.2016


Ответы (2)


Я столкнулся с той же проблемой на прошлой неделе, и оказалось, что в AppCompatTextView v23.1.0 составные чертежи автоматически окрашиваются.

Вот решение, которое я нашел, с дополнительными пояснениями о том, почему я это сделал ниже. Это не очень чисто, но, по крайней мере, это позволяет вам подкрашивать составные рисунки!

РЕШЕНИЕ

Поместите этот код во вспомогательный класс или в свой собственный TextView/Button:

/**
 * The app compat text view automatically sets the compound drawable tints for a static array of drawables ids.
 * If the drawable id is not in the list, the lib apply a null tint, removing the custom tint set before.
 * There is no way to change this (private attributes/classes, only set in the constructor...)
 *
 * @param object the object on which to disable default tinting.
 */
public static void removeDefaultTinting(Object object) {
    try {
        // Get the text helper field.
        Field mTextHelperField = object.getClass().getSuperclass().getDeclaredField("mTextHelper");
        mTextHelperField.setAccessible(true);
        // Get the text helper object instance.
        final Object mTextHelper = mTextHelperField.get(object);
        if (mTextHelper != null) {
            // Apply tint to all private attributes. See AppCompat source code for usage of theses attributes.
            setObjectFieldToNull(mTextHelper, "mDrawableStartTint");
            setObjectFieldToNull(mTextHelper, "mDrawableEndTint");
            setObjectFieldToNull(mTextHelper, "mDrawableLeftTint");
            setObjectFieldToNull(mTextHelper, "mDrawableTopTint");
            setObjectFieldToNull(mTextHelper, "mDrawableRightTint");
            setObjectFieldToNull(mTextHelper, "mDrawableBottomTint");
        }
    } catch (NoSuchFieldException e) {
        // If it doesn't work, we can do nothing else. The icons will be white, we will see it.
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        // If it doesn't work, we can do nothing else. The icons will be white, we will see it.
        e.printStackTrace();
    }
}

/**
 * Set the field of an object to null.
 *
 * @param object    the TextHelper object (class is not accessible...).
 * @param fieldName the name of the tint field.
 */
private static void setObjectFieldToNull(Object object, String fieldName) {
    try {
        Field tintField;
        // Try to get field from class or super class (depends on the implementation).
        try {
            tintField = object.getClass().getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            tintField = object.getClass().getSuperclass().getDeclaredField(fieldName);
        }
        tintField.setAccessible(true);
        tintField.set(object, null);

    } catch (NoSuchFieldException e) {
        // If it doesn't work, we can do nothing else. The icons will be white, we will see it.
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        // If it doesn't work, we can do nothing else. The icons will be white, we will see it.
        e.printStackTrace();
    }
}

Затем вы можете вызвать removeDefaultTinting(this); для каждого конструктора вашего класса, расширяющего AppCompatTextView или AppCompatButton. Например :

public MyCustomTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    removeDefaultTinting(this);
}

При этом код, работающий с v23.0.1, должен работать и на v23.1.0.

Меня не устраивает использование отражения для изменения атрибутов в библиотеке AppCompat, но это единственный способ, который я нашел для использования тонирования составных рисунков с v23.1.0. Будем надеяться, что кто-то найдет лучшее решение, или в общедоступные методы AppCompat будет добавлено сложное окрашивание.

ОБНОВЛЕНИЕ

Я нашел другое более простое решение: эта ошибка возникает, только если вы устанавливаете составные чертежи с помощью xml. Не устанавливайте их в xml, затем установите их в свой код, и он будет работать. Неисправный код, находящийся в конструкторе, установка рисунков после его вызова не затрагивается.

ПОЯСНЕНИЯ

В конструкторе AppCompatTextView инициализируется текстовый хелпер:

mTextHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper.applyCompoundDrawablesTints();

В функции TextHelper loadFromAttributes список оттенков создается для каждого составного рисунка. Как видите, mDrawableXXXTint.mHasTintList всегда имеет значение true. mDrawableXXXTint.mTintList — это цвет оттенка, который будет применяться, и его можно получить только из жестко заданных значений AppCompat. Для ваших пользовательских рисунков он всегда будет нулевым. Таким образом, вы получите оттенок с нулевым «списком оттенков».

TypedArray a = context.obtainStyledAttributes(attrs, VIEW_ATTRS, defStyleAttr, 0);
    final int ap = a.getResourceId(0, -1);

    // Now read the compound drawable and grab any tints
    if (a.hasValue(1)) {
        mDrawableLeftTint = new TintInfo();
        mDrawableLeftTint.mHasTintList = true;
        mDrawableLeftTint.mTintList = tintManager.getTintList(a.getResourceId(1, 0));
    }
    if (a.hasValue(2)) {
        mDrawableTopTint = new TintInfo();
        mDrawableTopTint.mHasTintList = true;
        mDrawableTopTint.mTintList = tintManager.getTintList(a.getResourceId(2, 0));
    }

...

Проблема в том, что этот оттенок применяется в конструкторе и каждый раз, когда устанавливается или изменяется объект рисования:

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

Итак, если вы примените оттенок к составному рисунку, а затем вызовете суперметод, такой как view.setCompoundDrawablesRelativeWithIntrinsicBounds, текстовый помощник применит свой нулевой оттенок к вашему рисунку, удалив все, что вы сделали...

Наконец, вот функция, применяющая оттенок:

final void applyCompoundDrawableTint(Drawable drawable, TintInfo info) {
    if (drawable != null && info != null) {
        TintManager.tintDrawable(drawable, info, mView.getDrawableState());
    }
}

TintInfo в параметрах — это атрибут mDrawableXXXTint класса texthelper. Как видите, если он равен нулю, оттенок не применяется. Установка для всех атрибутов отрисовываемого оттенка значения null не позволяет AppCompat применять свой оттенок и позволяет вам делать все, что вы хотите, с отрисовываемыми объектами.

Я не нашел чистого способа заблокировать это поведение или заставить его применить оттенок, который я хочу. Все атрибуты закрытые, без геттеров.

person David Lericolais    schedule 20.10.2015
comment
Ух ты. Не тот ответ, который я ожидал получить! Попробую ваше решение, но такой обходной путь - позор для Android ... как вы думаете, было бы разумно открыть ошибку для Google, чтобы проверить? Большое спасибо :) - person Philippe David; 20.10.2015
comment
Пожалуйста ! Я потратил полдня, используя отладчик, чтобы понять, почему мои рисунки были белыми, поэтому, когда я увидел ваш вопрос, я почувствовал себя обязанным создать учетную запись и опубликовать то, что я нашел :) Вероятно, это хорошая идея, чтобы открыть ошибку, я просто не сделал не торопитесь. Это временное решение, которое, вероятно, перестанет работать при следующем изменении AppCompat. Код, который они используют для окрашивания составных рисунков, не слишком длинный и сложный, он просто совершенно недоступен из внешнего класса. Простой сеттер для оттенка каждого составного рисунка, и он исправлен... - person David Lericolais; 20.10.2015
comment
Сделано для вопроса. Со вчерашнего дня я обнаружил, что эта библиотека также ломает TransitionDrawable на некоторых устройствах :) code.google.com/p/android/issues/detail?id=191111 - person David Lericolais; 21.10.2015
comment
mTextHelper не существует для AppCompatAutoCompleteTextView? - person Philippe David; 21.10.2015
comment
Использование метода не помещать их в xml работает отлично! - person Philippe David; 21.10.2015
comment
Рад, что это сработало! mTextHelper существует в AppCompatAutoCompleteTextView, поэтому он должен работать, если ваш класс напрямую расширяет его, если только вы не используете TransitionDrawables в качестве составных рисунков. В этом случае не помещать их в xml кажется единственным решением! - person David Lericolais; 22.10.2015
comment
хм, а что такое TintManager? а что такое TintInfo, я не понимаю твоего описания и их не существует - person behelit; 26.05.2016
comment
Это был хак, чтобы заставить AppCompat 23.1.0 работать в некоторых конкретных случаях. В последней версии (23.4.0) в этом больше нет необходимости. TintManager и TintInfo — это объекты, используемые внутри библиотеки AppCompat, они не видны за пределами библиотеки. - person David Lericolais; 27.05.2016

Вы можете попробовать что-то вроде этого

ContextCompat.getDrawable(context, R.drawable.cool_icon)?.apply {
    setTint(ContextCompat.getColor(context, R.color.red))
}
person 0wl    schedule 01.11.2018