手把手教你用 Jetpack Compose 做會(huì)動(dòng)的點(diǎn)贊按鈕!
??? 四步搞定動(dòng)畫魔法
步驟1:把動(dòng)畫拆成「番茄炒蛋」
畫就像做菜,得分步驟!我們把這個(gè)效果拆成:
1. 圓形背景「膨脹」(像吹氣球??)
2. 大拇指「閃現(xiàn)」
3. 小圓點(diǎn)點(diǎn)「炸煙花」(最炫的part!)
4. 所有元素「優(yōu)雅退場(chǎng)」
// 用枚舉告訴代碼:「現(xiàn)在該哪一步了?」
enum class AnimationState {
IDLE, // 靜止?fàn)顟B(tài)
CIRCLE_GROW, // 背景圓圈擴(kuò)散
THUMB_JUMP, // 點(diǎn)贊圖標(biāo)彈跳
DOT_EXPLOSION, // 小圓點(diǎn)爆發(fā)
FINISH // 動(dòng)畫結(jié)束
}
步驟2:用「時(shí)間管理器」控制動(dòng)畫流程
Jetpack Compose 的 Transition
就像動(dòng)畫導(dǎo)演??,它會(huì)說:「3秒后該放大圓圈了,5秒后彈出大拇指...」
@Composable
fun LikeButton(isLiked: Boolean) {
// ?? 核心狀態(tài)控制
var isLiked by remember { mutableStateOf(false) }
var currentState by remember { mutableStateOf(AnimationState.IDLE) }
// ?? 單位轉(zhuǎn)換(解決DP/PX混亂問題!)
val density = LocalDensity.current
val maxCircleSize = 48.dp
val maxCirclePx = with(density) { maxCircleSize.toPx() }
// ?? 動(dòng)畫過渡控制
val transition = updateTransition(currentState, label = "LikeTransition")
// ? 定義動(dòng)畫數(shù)值
// 背景圓圈半徑
val circleRadius by transition.animateFloat(
transitionSpec = {
when {
AnimationState.IDLE isTransitioningTo AnimationState.CIRCLE_GROW ->
tween(300, easing = FastOutSlowInEasing)
else -> snap()
}
}, label = "circleRadius"
) { state ->
when (state) {
AnimationState.CIRCLE_GROW -> maxCirclePx
else -> 0f
}
}
// ?? 大拇指圖標(biāo)動(dòng)畫(彈跳效果)
val thumbScale by transition.animateFloat(
transitionSpec = {
keyframes {
durationMillis = 600
0f at 0 with LinearEasing
1.2f at 150
1f at 300
}
}, label = "thumbScale"
) { state ->
when (state) {
AnimationState.THUMB_JUMP -> 1f
AnimationState.FINISH -> 1f
AnimationState.IDLE -> 1f// 確保初始狀態(tài)也顯示
else -> 0f
}
}
// ?? 爆炸小圓點(diǎn)
val dotsProgress by transition.animateFloat(
transitionSpec = { tween(500) },
label = "dotsProgress"
) { state ->
when (state) {
AnimationState.DOT_EXPLOSION -> 1f
else -> 0f
}
}
// ?? 點(diǎn)擊觸發(fā)動(dòng)畫
LaunchedEffect(isLiked) {
if (isLiked) {
currentState = AnimationState.CIRCLE_GROW
delay(300)
currentState = AnimationState.THUMB_JUMP
delay(300)
currentState = AnimationState.DOT_EXPLOSION
delay(800)
currentState = AnimationState.FINISH
} else {
currentState = AnimationState.IDLE
}
}
// 繪制界面(下一步講)
}
步驟3:畫一個(gè)「會(huì)動(dòng)的圖層」
用 Canvas 和 Box 像PS一樣疊加圖層:背景圈圈在下,點(diǎn)贊圖標(biāo)居中,小圓點(diǎn)環(huán)繞四周。
// ?? 繪制界面
Box(
modifier = Modifier
.size(72.dp)
.clickable { isLiked = !isLiked },
contentAlignment = Alignment.Center
) {
// 背景擴(kuò)散圓圈
if (currentState != AnimationState.IDLE) {
Canvas(modifier = Modifier.fillMaxSize()) {
drawCircle(
color = Color.Red.copy(alpha = 0.2f),
radius = circleRadius,
style = Stroke(width = 2.dp.toPx())
)
}
}
// 大拇指圖標(biāo)(帶彈跳)
Icon(
imageVector = Icons.Default.ThumbUp,
contentDescription = "Like",
modifier = Modifier
.size(32.dp)
.graphicsLayer {
scaleX = thumbScale
scaleY = thumbScale
},
tint = if (isLiked) Color.Red else Color.Gray
)
// 爆炸小圓點(diǎn)
if (dotsProgress > 0) {
val angles = listOf(0f, 45f, 90f, 135f, 180f, 225f, 270f, 315f)
val radius = with(LocalDensity.current) { 48.dp.toPx() } * dotsProgress
val dotRadius = with(LocalDensity.current) { 4.dp.toPx() }
Canvas(modifier = Modifier.fillMaxSize()) {
val center = Offset(size.width / 2, size.height / 2)
angles.forEach { angle ->
val radians = Math.toRadians(angle.toDouble())
val x = center.x + radius * cos(radians).toFloat()
val y = center.y + radius * sin(radians).toFloat()
drawCircle(
color = Color(0xFFFF6B6B).copy(alpha = 1 - dotsProgress),
radius = dotRadius,
center = Offset(x, y)
)
}
}
}
}
步驟4:加億點(diǎn)點(diǎn)細(xì)節(jié)
- ? 彈性動(dòng)畫:讓圓圈放大時(shí)有「彈簧感」
- ? 漸隱效果:退場(chǎng)時(shí)用 alpha 動(dòng)畫讓元素淡出
圖片
?? 學(xué)完后你能舉一反三
? 替換成愛心、星星?等不同圖標(biāo)
? 把小圓點(diǎn)改成小星星、花瓣??
? 調(diào)整顏色、動(dòng)畫時(shí)長(zhǎng)、彈跳幅度...