Cómo hacer sombras de colores en Android con degradado y animación

En la presentación de las nuevas MacBooks, llamé la atención sobre la imagen del procesador:

Las sombras de colores iridiscentes sobre un fondo oscuro se ven geniales.

Así que mis manos se movieron, decidí intentar dibujar en Android de la misma manera. Esto es lo que sucedió:

, , 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
)

Todo. Si se trata de un botón, debe aplicar un efecto dominó al primer plano de la vista y también sangrar allí para que podamos mostrar la animación de clic.




All Articles