Comment créer des ombres colorées sous Android avec dégradé et animation

Lors de la présentation des nouveaux MacBook, j'ai attiré l'attention sur l'image du processeur:

Les ombres colorées irisées sur un fond sombre ont l'air cool.

Alors mes mains se sont déplacées, j'ai décidé d'essayer de dessiner sur Android de la même manière. Voici ce qui s'est passé:

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

Tout. S'il s'agit d'un bouton, vous devez appliquer un effet d'entraînement au premier plan de la vue et y mettre en retrait afin que nous puissions afficher l'animation de clic.




All Articles