How to make colored shadows in Android with gradient and animation

At the presentation of new MacBooks, I drew attention to the processor picture:

Iridescent colored shadows on a dark background looks cool.

So my hands got around, I decided to try to draw on android the same way. Here's what happened:

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

Everything. If this is a button, you need to apply a ripple effect to the foreground of the view and also indent there so that we can display the click animation.




All Articles