Как исправить баг с Drawable.setTint в API 21 Android SDK

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Привет, в этой заметке наш android-разработчик Влад Титов расскажет о том, как решить проблему с использованием инструмента изменения цвета для Drawable. Поехали.

В 21 версии API Android SDK появился универсальный инструмент изменения цвета для всех Drawable - Drawable.setTint(int color). Но как раз-таки в этой самой версии он не работает у некоторых наследников Drawable, а именно GradientDrawable, InsetDrawable, RippleDrawable и всех наследников DrawableContainer. 

Если посмотреть в исходники API 21, скажем, GradientDrawable (прямого наследника Drawable), мы не найдем переопределенного метода setTint и его вариаций. А это значит, что в данной реализации разработчики попросту не поддержали эту функцию.

Проблему условно решили в библиотеке обратной совместимости. Сейчас ее можно найти по артефакту androidx.core:core. Чтобы поддержать tinting на версиях 14-22, были созданы обертки WrappedDrawableApi14 и WrappedDrawableApi21. Последняя является наследницей первой и, по сути, не несет логики по поддержке окрашивания. 

Чтобы обернуть оригинальный Drawable, нужно всего лишь подать его в метод DrawableCompat.wrap(Drawable). Основная идея состоит в том, что сам ColorStateList тинта хранится в обертках, а у оригинального Drawable изменяется цветовой фильтр при изменении состояния Drawable.

final ColorStateList tintList = mState.mTint;
final PorterDuff.Mode tintMode = mState.mTintMode;

if (tintList != null && tintMode != null) {
   final int color = tintList.getColorForState(state, tintList.getDefaultColor());
   if (!mColorFilterSet || color != mCurrentColor || tintMode != mCurrentMode) {
       setColorFilter(color, tintMode);
       mCurrentColor = color;
       mCurrentMode = tintMode;
       mColorFilterSet = true;
       return true;
   }
} else {
   mColorFilterSet = false;
   clearColorFilter();
}

Данный кусок кода будет вызываться каждый раз при вызове Drawable.setState(int[] stateSet).

При использовании этих оберток вы теряете возможность вызывать специфические методы для конкретных Drawable. Так, например, при оборачивании GradientDrawable вы не сможете управлять градиентом, так как обертка в своем интерфейсе не имеет методов таких, как setShape, setGradientType и.т.п. Чтобы получить доступ к данным методам, обернутый Drawable придется развернуть (DrawableCompat.unwrap(Drawable)). Но в таком случае вы теряете тинт. Если он у вас состоял только из одного цвета, ничего страшного, ведь этот цвет сохранится как цветовой фильтр в оригинальном Drawable. Но если тинт был stateful, цвета для стейтов, отличных от текущего, будут потеряны.

Выходом из сложившейся ситуации может быть пример, приведенный далее. 

Если ваш тинт состоит лишь из одного цвета, вы можете в любой момент выполнить следующие действия:

val wrapped = DrawableCompat.wrap(drawable)
wrapped.setTint(...)
drawable = DrawableCompat.unwrap(wrapped)

После чего смело делать дальше свои дела.

В ином случае есть смысл воспользоваться следующим решением:

class GradientDrawableWrapper(
    val original: GradientDrawable, 
    var ColorStateList tint
) {

    fun get(): Drawable {
        return wrap()
    }

    fun setShape(@Shape shape: Int) {
        original.setShape(shape)
    }

    // other specific method proxies...

    private fun wrap(): Drawable {
        val wrapped = DrawableCompat.wrap(original)
        wrapped.setTint(tint)
        return wrapped
    }
}

Такое решение выглядит немного объемным, но полностью решает указанную проблему.

Источник: https://habr.com/ru/post/527138/


Интересные статьи

Интересные статьи

Оптимизация сборки — вишенка на торте мобильного приложения. К счастью, существуют инструменты, проверенные временем и заслужившие доверие сообщества. К сожалению, ее не ...
Привет! Меня зовут Сергей Иванов, я ведущий разработчик Android в Redmadrobot. С 2016 использую автотесты различных категорий и успел в этом набить немало шишек. Именно п...
Корпорация Google опубликовала релиз мобильной ОС Android 11. Исходные тексты операционной системы размещены в Git-репозитории проекта (ветка android-11.0.0_r1). Главный акце...
Однажды вы захотите продать что-нибудь на Avito и, выложив подробное описание своего товара (например, модуль оперативной памяти), получите вот такое сообщение:
Привет, Хабр! В этой статье мы рассмотрим процесс создания экранов, опираясь на макеты из первой части.