
Предисловие
Классическая ситуация: допустим у нас есть какой-то 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. Всем удачи!