ContentBasedTheme в 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)

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

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

В целом можно придумать много способов это реализовать. В этом примере мы воспользуемся 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. Вы можете выбрать какие-то другие цвета на свой вкус.

Финальный результат

Финальный результат

Заключение

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


ссылка на оригинал статьи https://habr.com/ru/articles/904258/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *