Animation in Jetpack Compose

Kodchakon Mtw
te<h @TDG
Published in
3 min readAug 24, 2022

แอนิเมชั่นเป็นพื้นฐานสำคัญในแอปพลิเคชันสมัยนี้ เพราะนอกจากความสวยงามและความทันสมัยแล้ว ก็เพื่อให้ผู้ใช้ได้รับประสบการณ์ที่ราบรื่นและเข้าใจได้ง่ายขึ้น โดย Jetpack Compose มี API แอนิเมชั่นต่างๆ diagram ด้านล่างนี้จะช่วยให้คุณตัดสินใจเลือกได้ตามความเหมาะสม

มาเริ่มกันที่ API แอนิเมชั่นที่ง่ายที่สุดตัวหนึ่งใน Compose: animate*AsState ควรใช้ API นี้เมื่อมีการเปลี่ยนแปลงสถานะ value สามารถเป็นได้ทั้ง Float, Color, Dp, Size .. แต่ถ้าคุณต้องการ animate ค่าสี api ที่ควรใช้ก็คือ

AnimateColorAsState

var isSwitch by remember { mutableStateOf(false) }
val backgroundColor by animateColorAsState(
if (isSwitch) Yellow200 else Purple200,
animationSpec = tween(2000)
)

Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
Modifier
.fillMaxWidth()
.fillMaxHeight(0.8f)
.background(backgroundColor)
)

Button(
colors = ButtonDefaults.buttonColors(backgroundColor = backgroundColor),
onClick = { isSwitch = !isSwitch },
modifier = Modifier
.padding(top = 10.dp)

) {
Text(text = "Change Color")
}
}

เป็นตัวอย่างการสลับสีพื้นหลังและสีปุ่มจากสีม่วงเป็นสีเหลือง สามารถใส่ animationSpec = tween(durationMillis) เข้าไปได้เพื่อให้เห็นเอฟเฟกต์ของแอนิเมชั่นชัดขึ้น
output:

animateContentSize

ทำให้การเปลี่ยนแปลงของขนาดเคลื่อนไหวได้โดยการใช้ animateContentSize

var isExpand by remember { mutableStateOf(false) }
val textContent = if (isExpand) title + description else title

ให้ค่า textContent โดยอิงจากค่าของ isExpand ที่เปลี่ยน state จากการ click

Image(
painter = painterResource(R.drawable.movie),
contentDescription = "movie",
modifier = Modifier
.size(400.dp, 250.dp)
)

Row(
modifier = Modifier
.background(Orange200)
.fillMaxWidth()
.padding(16.dp)
.animateContentSize(
animationSpec = tween(200)
)
.clickable {
isExpand = !isExpand
}
) {
Icon(
imageVector = Icons.Default.Favorite,
contentDescription = null
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = textContent,
style = MaterialTheme.typography.body1
)
}

Row จะเปลี่ยนขนาดเมื่อมีการเปลี่ยนแปลงเนื้อหา
output:

Repeating animation

การทำ animation แบบซ้ำโดยใช้ InfiniteTransition สามารถ create instance ของ InfiniteTransition ด้วย rememberInfiniteTransition และสามารถเพิ่ม child animations ด้วย animateColor, animatedFloat, หรือ animatedValue

val infiniteTransition = rememberInfiniteTransition()
val heartSize by infiniteTransition.animateFloat(
initialValue = 150.0f,
targetValue = 300.0f,
animationSpec = infiniteRepeatable(
animation = tween(1000),
repeatMode = RepeatMode.Reverse
)
)
Image(
painter = painterResource(R.drawable.icon_heart),
contentDescription = "icon heart",
modifier = Modifier.size(heartSize.dp),
alpha = alpha
)

ใน case นี้เราใช้ animateFloat เพื่อกำหนด size ให้กับรูปภาพโดยเริ่มที่ initialValue ที่ขนาดเล็ก และ targetValue ที่ขนาดใหญ่ขึ้น และ set animationSpec ให้ repeatMode เป็น Reverse โดยค่า default ของมันคือ Restart

val alpha by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = keyframes {
durationMillis = 1000
0.7f at 500
},
repeatMode = RepeatMode.Reverse
)
)

set alpha ให้กับรูปได้ด้วย โดยเริ่มที่ 0f ไปถึง 1f และมีการ set keyframes ให้ alpha 0.7f เมื่อ duration 500ms
output:

AnimatedVisibility

เป็นการใส่ animation ให้กับการ show และ hide โดยค่า default ของการโชว์ content คือเฟดเข้าและขยายออก การซ่อนคือการจางลงและย่อขนาด

AnimatedVisibility(
visible = isShow,
enter = slideInVertically(
animationSpec = tween(durationMillis = 100, easing = LinearOutSlowInEasing)
),
exit = slideOutVertically(
animationSpec = tween(durationMillis = 200, easing = FastOutLinearInEasing)
)
)

สามารถกำหนดค่า enter exit ได้ แต่ behavior ของ slideInVertically และ slideOutVertically เป็นการใช้ความสูงของ content เพียงครึ่งนึงเท่านั้น
output:

อ่านเพิ่มเติมเกี่ยวกับ EnterTransition และ ExitTransition click

AnimationSpec

การปรับแต่ง animation ด้วย พารามิเตอร์ animationSpec

  • spring สร้าง animation ตามฟิสิกส์ ระหว่างค่าเริ่มต้นและค่าสิ้นสุด ใช้พารามิเตอร์ 2 ตัวนี้
    dampingRatio -> เป็นตัวกำหนดความเด้งของสปริง ค่าเริ่มต้นคือ Spring.DampingRatioNoBouncy
    stiffness -> กำหนดว่าสปริงควรเคลื่อนที่ไปสู่ค่าสิ้นสุดเร็วเพียงใด ค่าเริ่มต้นคือ Spring.StiffnessMedium
Button(onClick = { isClick = !isClick },
modifier = Modifier
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioHighBouncy,
stiffness = Spring.StiffnessLow
))
) {
Text(
text = text,
textAlign = TextAlign.Center
)
}

output:

  • snap ใช้สำหรับทำให้เคลื่อนไหวจากค่าหนึ่งไปยังอีกค่าหนึ่งอย่างรวดเร็ว นอกจากนี้ เราสามารถตั้งค่า delay ระหว่างช่วงการเปลี่ยนภาพได้อีกด้วย
val alpha by animateFloatAsState(
targetValue = if (isAnimate) 1.0f else 0.5f,
animationSpec = snap(delayMillis = 300),
)
Icon(
painter = painterResource(R.drawable.bin),
contentDescription = "bin",
modifier = Modifier
.size(300.dp)
.alpha(alpha = alpha)
)

output:

ทั้งหมดนี้ก็เป็นตัวอย่างการใช้ animation ใน jetpack compose บางส่วนเท่านั้น สำหรับใครที่สนใจอ่านข้อมูลเพิ่มเติมก็ตามนี้เลย
https://developer.android.com/jetpack/compose/animation

--

--