Как сделать цветные тени в Android с градиентом и анимацией

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

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

На презентации новых макбуков и обратил внимание на картинку процессора:

Переливающиеся цветные тени на темном фоне, выглядит классно.

Вот дошли руки, решил попробовать нарисовать на андроиде так же. Вот что получилось:

Сразу оговорюсь, что стандартным способом это сделать нельзя, до api 28 есть поддержка только черных elevation, после api 28 добавили поддержку цветных теней, но градиент сделать не получится. Поэтому мы будет рисовать drawable, устанавливать его в виде background и применять padding на целевой вьюхе, чтобы контент был внутри тени.

Напишем функцию создания Drawable с тенью:

/**
 * Создание drawable с градиентом-тенью
 */
private fun createShadowDrawable(
    @ColorInt colors: IntArray,
    cornerRadius: Float,
    elevation: Float,
    centerX: Float,
    centerY: Float
): ShapeDrawable {

    val shadowDrawable = ShapeDrawable()

    // Устанавливаем черную тень по умолчанию
    shadowDrawable.paint.setShadowLayer(
        elevation, // размер тени
        0f, // смещение тени по оси Х
        0f, // по У
        Color.BLACK // цвет тени
    )

    /**
     * Применяем покраску градиентом
     *
     * @param centerX - Центр SweepGradient по оси Х. Берем центр вьюхи
     * @param centerY - Центр по оси У
     * @param colors - Цвета градиента. Последний цвет должен быть равен первому,
     * иначе между ними не будет плавного перехода
     * @param position - позиции смещения градиента одного цвета относительно другого от 0 до 1.
     * В нашем случае null т.к. нам нужен равномерный градиент
     */
    shadowDrawable.paint.shader = SweepGradient(
        centerX,
        centerY,
        colors,
        null
    )

    // Делаем закугление углов
    val outerRadius = FloatArray(8) { cornerRadius }
    shadowDrawable.shape = RoundRectShape(outerRadius, null, null)

    return shadowDrawable
}

Поскольку у этого drawable фон представлен в виде радуги тех цветов, что мы передали в параметрах, нам нужен нормальный одноцветный фон. Для этого создаем вторую drawable:

/**
 * Создание цветного drawable с закругленными углами
 * Это будет основной цвет нашего контейнера
 */
private fun createColorDrawable(
    @ColorInt backgroundColor: Int,
    cornerRadius: Float
) = GradientDrawable().apply {
        setColor(backgroundColor)
        setCornerRadius(cornerRadius)
    }

Функция установки бэкграунда на вьюху-контейнер. У нас будет LayerDrawable с двумя слоями. 1 - тень, 2 - просто цвет с закругленными углами.

/**
 * Устанавливаем бэкграунд с тенью на вьюху, учитывая padding
 */
private fun View.setColorShadowBackground(
    shadowDrawable: ShapeDrawable,
    colorDrawable: Drawable,
    padding: Int
) {
    val drawable = LayerDrawable(arrayOf(shadowDrawable, colorDrawable))
    drawable.setLayerInset(0, padding, padding, padding, padding)
    drawable.setLayerInset(1, padding, padding, padding, padding)
    setPadding(padding, padding, padding, padding)
    background = drawable
}

Применяем на вьюхе:

// ждем когда вьюха отрисуется чтобы узнать ее размеры
targetView.doOnNextLayout {
    val colors = intArrayOf(
        Color.WHITE,
        Color.RED,
        Color.WHITE
    )
    val cornerRadius = 16f.dp
    val padding = 30.dp
    val centerX = it.width.toFloat() / 2 - padding
    val centerY = it.height.toFloat() / 2 - padding

    val shadowDrawable = createShadowDrawable(
        colors = colors,
        cornerRadius = cornerRadius,
        elevation = padding / 2f,
        centerX = centerX,
        centerY = centerY
    )
    val colorDrawable = createColorDrawable(
        backgroundColor = Color.DKGRAY,
        cornerRadius = cornerRadius
    )

    it.setColorShadowBackground(
        shadowDrawable = shadowDrawable,
        colorDrawable = colorDrawable,
        padding = 30.dp
    )
}

Теперь проанимируем изменение с одного набора цветов на другие. Зациклим.

/**
 * Анимация drawable-градиента
 */
private fun animateShadow(
    shapeDrawable: ShapeDrawable,
    @ColorInt startColors: IntArray,
    @ColorInt endColors: IntArray,
    duration: Long,
    centerX: Float,
    centerY: Float
) {
    /**
     * Меняем значение с 0f до 1f для применения плавного изменения
     * цвета с помощью [ColorUtils.blendARGB]
     */
    ValueAnimator.ofFloat(0f, 1f).apply {
        // Задержка перерисовки тени. Грубо говоря, фпс анимации
        val invalidateDelay = 100
        var deltaTime = System.currentTimeMillis()

        // Новый массив со смешанными цветами
        val mixedColors = IntArray(startColors.size)

        addUpdateListener { animation ->
            if (System.currentTimeMillis() - deltaTime > invalidateDelay) {
                val animatedFraction = animation.animatedValue as Float
                deltaTime = System.currentTimeMillis()

                // Смешиваем цвета
                for (i in 0..mixedColors.lastIndex) {
                    mixedColors[i] = ColorUtils.blendARGB(startColors[i], endColors[i], animatedFraction)
                }

                // Устанавливаем новую тень
                shapeDrawable.paint.shader = SweepGradient(
                    centerX,
                    centerY,
                    mixedColors,
                    null
                )
                shapeDrawable.invalidateSelf()
            }
        }
        repeatMode = ValueAnimator.REVERSE
        repeatCount = Animation.INFINITE
        setDuration(duration)
        start()
    }
}

Применим:

// Второй массив с цветами. Размер массивов должен быть одинаковый.
val endColors = intArrayOf(
	Color.RED,
	Color.WHITE,
	Color.RED
)
animateShadow(
	shapeDrawable = shadowDrawable,
  startColors = colors,
  endColors = endColors,
  duration = 2000,
  centerX = centerX,
  centerY = centerY
)

Все. Если это будет кнопкой, нужно применить ripple эффект для foreground вьюхи и так же прописать там отступ, чтобы у нас отображалась анимация нажатия.

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


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

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

Совсем скоро, 6 и 18 ноября, у нас стартуют новые потоки курса по JavaScript и курса «Профессия Веб-разработчик», специально к их старту делимся с вами полезным туториалом, как настро...
Привет! У исследования IT-брендов работодателей, которое делает Хабр, новый инфопартнёр — Университет Иннополис. Радик Валиев, его директор по развитию и кадровой политике, объясн...
Привет, Хаброжители! На днях начал изучать GitHub Actions для Android. Ранее у меня была удачная попытка настройки данного функционала для проекта на Flutter, но без деплоя, для котор...
Привет, Хабр! Представляю вашему вниманию перевод статьи из журнала APC. В данной статье рассматривается полная установка операционной среды Linux вместе с графическим окружением рабочего ст...
ГЛАВА 1. Амбиции Конец февраля 2018 Мы, как адепты идеологии свободного ПО и свободного рынка считаем, что монополия — это плохо. Огромному количеству людей требуется удобная и быстра...