Content Based Theme в Android приложении

Content Based Theme в Android приложении

Предисловие

Классическая ситуация: допустим у нас есть какой-то RecyclerView, элементы которого - карточки с картинками.

В этой статье мы будем раскрашивать MaterialCardView и её дочерние элементы в цвета, сочетающиеся с цветами изображения. Для этого воспользуемся DynamicColors API.

Мы не будем использовать Compose, на эту тему уже есть несколько статей с его использованием.

Заготовка

Для наших экспериментов создадим какой-то абстрактный data-класс, который будет нашим элементом списка в RecyclerView:

data class SomeListItem( val image: Drawable, val title: String, val tag: String )

У него есть image для хранения картинки, title для хранения заголовка, а также tag для хранения каких-нибудь тегов.

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

Ну и давайте сразу создадим какой-то простенький адаптер для RecyclerView:

class SomeListAdapter( private val items: List<SomeListItem> ) : RecyclerView.Adapter<SomeListAdapter.ViewHolder>() { inner class ViewHolder(val binding: ItemSomeListBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(item: SomeListItem) { binding.imageView.setImageDrawable(item.image) binding.titleView.text = item.title binding.tagView.text = item.tag } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val binding = ItemSomeListBinding.inflate(LayoutInflater.from(parent.context), parent, false) return ViewHolder(binding) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.bind(items[position]) } override fun getItemCount(): Int = items.size }

Ну и в классе Activity в onCreate создадим простой список из элементов и подключим адаптер к RecyclerView:

val myListOfItems = listOf( SomeListItem( image = ContextCompat.getDrawable(this, R.drawable.flower1)!!, title = "Красный цветок", tag = "#red #flowers #simple" ), SomeListItem( image = ContextCompat.getDrawable(this, R.drawable.flower2)!!, title = "Синий цветок", tag = "#simple" ), SomeListItem( image = ContextCompat.getDrawable(this, R.drawable.flower3)!!, title = "Розовый цветок", tag = "#flowers #simple" ), SomeListItem( image = ContextCompat.getDrawable(this, R.drawable.flower4)!!, title = "Фиолетовый цветок", tag = "#purple #flowers #prettynice" ), SomeListItem( image = ContextCompat.getDrawable(this, R.drawable.flower5)!!, title = "Зелёный цветок", tag = "#simple #green" ), SomeListItem( image = ContextCompat.getDrawable(this, R.drawable.flower6)!!, title = "Жёлтый цветок", tag = "#flowers #yellow" ), SomeListItem( image = ContextCompat.getDrawable(this, R.drawable.flower7)!!, title = "Голубой цветок", tag = "#blue #nice" ), ) val adapter = SomeListAdapter(myListOfItems) binding.recyclerview.adapter = adapter binding.recyclerview.layoutManager = LinearLayoutManager(this)
Content Based Theme в Android приложении

Теперь у нас есть простенькое приложение со списком карточек.

Теперь наша задача - заставить карточки раскрашиваться в зависимости от изображения на них.

В целом можно придумать много способов это реализовать. В этом примере мы воспользуемся DynamicColors API.

К сожалению, Dynamic Colors API не будет работать на версиях Android ниже 12ой. Однако если у вас есть желание реализовать подобное на ранних версиях Android - можно воспользоваться Palette API.

Красим карточки

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

В нашем ViewHolder модифицируем метод bind. Для начала возьмём bitmap из imageView и сожмём его. Так мы ускорим процесс определения цветовой схемы карточки:

val bitmap = binding.image.drawable.toBitmap() val compressedBitmap = bitmap.scale(10, 6, false)

Теперь можно воспользоваться прекрасным функционалом wrapContextIfAvailable из Dynamic Colors API. Создадим контекст, используя compressedBitmap в качестве сontentBasedSource:

val newContext: Context = DynamicColors.wrapContextIfAvailable( itemView.context, DynamicColorsOptions.Builder() .setContentBasedSource(compressedBitmap) .build() )

Дело остаётся за малым: перекрасить элементы ViewHolder цветами темы нового контекста:

//устанавливаем цвет для карточки (binding.root as MaterialCardView).setCardBackgroundColor(MaterialColors.getColor(newContext, com.google.android.material.R.attr.colorSecondaryContainer, Color.GRAY)) //и цвет заголовка binding.title.setTextColor(MaterialColors.getColor(newContext, com.google.android.material.R.attr.colorPrimary, Color.GRAY)) //и цвет для тегов binding.tags.setTextColor(MaterialColors.getColor(newContext, com.google.android.material.R.attr.colorOnSecondaryContainer, Color.GRAY))

Стоит учесть, что wrapContextIfAvailable - достаточно трудоёмкая функция. Чтобы избежать лагов - можно воспользоваться корутинами.

Таким образом метод bind нашего ViewHolder выглядит следующим образом:

fun bind(item: SomeListItem) { binding.image.setImageDrawable(item.image) binding.title.text = item.title binding.tags.text = item.tag CoroutineScope(Dispatchers.IO).launch { val bitmap = binding.image.drawable.toBitmap() val compressedBitmap = bitmap.scale(10, 6, false) val newContext: Context = DynamicColors.wrapContextIfAvailable( itemView.context, DynamicColorsOptions.Builder() .setContentBasedSource(compressedBitmap) .build() ) withContext(Dispatchers.Main){ (binding.root as MaterialCardView).setCardBackgroundColor(MaterialColors.getColor(newContext, com.google.android.material.R.attr.colorSecondaryContainer, Color.GRAY)) binding.title.setTextColor(MaterialColors.getColor(newContext, com.google.android.material.R.attr.colorPrimary, Color.GRAY)) binding.tags.setTextColor(MaterialColors.getColor(newContext, com.google.android.material.R.attr.colorOnSecondaryContainer, Color.GRAY)) } } }

Теперь карточки в нашем RecyclerView будут раскрашиваться в зависимости от цвета изображения. Цвет карточки мы выбрали ColorSecondaryContainer, а цвет заголовка: ColorPrimary. Вы можете выбрать какие-то другие цвета на свой вкус.

Content Based Theme в Android приложении

Результат

Заключение

Надеюсь, статься была полезной для вас. Вы можете ознакомиться с исходным кодом на GitHub. Всем удачи!

Начать дискуссию