Compose 仿微信点赞动画

410 阅读2分钟

微信点赞

录制_2025_05_24_22_54_34_410.gif

compose 点赞效果

录制_2025_05_25_11_47_54_696.gif

动画选择

多状态顺序动画

  1. 使用 Transition 同时为多个属性添加动画效果
  2. Animatable:基于协程顺序动画

拆分动画状态

  1. INITIAL, // 初始状态(半径0)
  2. CIRCLE_GROW, // 圆半径增长
  3. THUMB_UP, // 显示大拇指
  4. SMALL_CIRCLES, // 周围出现小圆形
  5. CIRCLES_FADE, // 小圆形消失
  6. THUMB_FADE // 大拇指消失

代码实战

使用 Transition 管理多状态

使用 Animatable 一样的效果

enum class LikeAnimationState {
    INITIAL,       // 初始状态(半径0)
    CIRCLE_GROW,   // 圆半径增长到
    THUMB_UP,      // 显示大拇指
    SMALL_CIRCLES, // 周围出现小圆形
    CIRCLES_FADE,  // 小圆形消失
    THUMB_FADE     // 大拇指消失
}

LikeView

@Composable
fun LikeView(start: Boolean) {
    //初始状态
    var currentState by remember { mutableStateOf(LikeAnimationState.INITIAL) }
    
    //Transition 管理多状态
    val transition = updateTransition(targetState = currentState, label = "likeAnimationView")
    //圆半径
    val density = LocalDensity.current
    val radius =
        remember {
            with(density) {
                60.dp.toPx()
            }
        }

    //圆形增长动画
    val circleRadius by transition.animateFloat(
        transitionSpec = {
            tween(
                durationMillis = 380,
                easing = LinearEasing
            )
        },
    ) { state ->
        when (state) {
            //其他状态 -> CIRCLE_GROW ->其他状态
            // 0f-> radius
            // radius -> 0f
            LikeAnimationState.CIRCLE_GROW -> radius
            else -> 0f
        }
    }


    //大拇指
    val thumbAlpha by transition.animateFloat(
        transitionSpec = {
            tween(durationMillis = if (currentState == LikeAnimationState.THUMB_FADE) 1200 else 300)
        },
    ) { state ->
        when (state) {
            LikeAnimationState.THUMB_UP,
            LikeAnimationState.SMALL_CIRCLES,
            LikeAnimationState.CIRCLES_FADE -> 1f

            else -> 0f
        }
    }

    //周围小圆点
    val smallCirclesAlpha by transition.animateFloat(
        transitionSpec = { tween(durationMillis = 380) },
    ) { state ->
        when (state) {
            LikeAnimationState.SMALL_CIRCLES -> 1f
            LikeAnimationState.CIRCLES_FADE -> 0f
            else -> 0f
        }
    }

    // 动画序列
    LaunchedEffect(start) {
        // 初始布局
        currentState = LikeAnimationState.INITIAL
        delay(100)
        //圆环增长
        currentState = LikeAnimationState.CIRCLE_GROW
        delay(380)
        //大拇指出现
        currentState = LikeAnimationState.THUMB_UP
        delay(100)
        //围绕点出现
        currentState = LikeAnimationState.SMALL_CIRCLES
        delay(380)
        //围绕点消失
        currentState = LikeAnimationState.CIRCLES_FADE
        delay(300)
        //大拇指消失
        currentState = LikeAnimationState.THUMB_FADE
    }

    Box(
        modifier = Modifier
            .size(100.dp)
            .background(Color.Transparent),
        contentAlignment = Alignment.Center
    ) {
        // 主圆形
        if (currentState == LikeAnimationState.CIRCLE_GROW) {
            Canvas(modifier = Modifier.matchParentSize()) {
                drawCircle(
                    color = Color.Red.copy(alpha = 0.3f),
                    radius = circleRadius,
                    center = center,
                    style = Stroke(width = radius - circleRadius + 4)
                )
            }
        }
        // 大拇指图标
        androidx.compose.material3.Icon(
            imageVector = Icons.Default.ThumbUp,
            contentDescription = "Like",
            modifier = Modifier
                .size(40.dp)
                .alpha(thumbAlpha),
            tint = Color.Red
        )

        // 周围小圆形
        if (smallCirclesAlpha > 0f) {
            val smallCirclePositions = remember {
                List(9) { index ->
                    val angle = index * (360f / 9)
                    angle
                }
            }
            val smallCircleColor = remember {
                arrayListOf(
                    Color.Blue, Color.Cyan, Color.Red,
                    Color.Green, Color.Magenta, Color.Yellow,
                    Color.LightGray, Color(0xFFBB2EC1),
                    Color(0xFFC64C45), Color(0xFFFF9800),
                )
            }
            Canvas(modifier = Modifier.matchParentSize()) {
                smallCirclePositions.forEachIndexed { index, angle ->
                    val radians = Math.toRadians(angle.toDouble())
                    val x =
                        center.x + 60.dp.toPx() * cos(radians).toFloat()
                    val y =
                        center.y + 60.dp.toPx() * sin(radians).toFloat()
                    drawCircle(
                        color = smallCircleColor[index].copy(
                            alpha =
                                smallCirclesAlpha * 0.8f
                        ),
                        radius = (smallCirclesAlpha + 0.5f).coerceAtMost(1f) * 6.dp.toPx(),
                        center = Offset(x, y)
                    )
                }
            }
        }
    }
}