포켓몬 슬립에 나오는 그 수면 시간 원형 progress indicator를 만들어볼 것이다.
Canvas(
modifier = modifier
.fillMaxSize()
) {
val ringWidth = size.width / 5f
drawArc(
color = trackColor,
startAngle = 0f,
sweepAngle = 360f,
useCenter = false,
style = Stroke(ringWidth)
)
drawArc(
color = color,
startAngle = -90f,
sweepAngle = 360 * progress,
useCenter = false,
style = Stroke(ringWidth, cap = StrokeCap.Round)
)
}
progress는 호출한 바깥쪽에서 Ease에 맞춰서 값을 전달해주었다.
var progress by remember { mutableFloatStateOf(0f) }
val max = 0.92f
LaunchedEffect(Unit) {
var time = 0.0f
while (time <= 1.0f) {
time += 0.01f
progress = EaseInOutCirc.transform(time) * max
delay(16L)
}
}
Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgress(
modifier = Modifier
.width(200.dp)
.aspectRatio(1f),
progress = progress
)
}
프로그래스가 움직이면 최종 숫자와 spark표시가 나타난다.
isDone 변수를 통해서 progress가 끝나면 애니메이션을 시작하도록 했다.
LaunchedEffect(progress) {
delay(200)
isDone = true
launch {
fontScaleAnimatable.animateTo(
targetValue = 1.0f,
animationSpec = tween(200)
)
delay(100)
sparkAlphaAnimatable.animateTo(
targetValue = 1.0f,
animationSpec = tween(200)
)
}
launch {
fontAlphaAnimatable.animateTo(
targetValue = 1.0f,
animationSpec = tween(200)
)
}
}
Canvas(
modifier = modifier
.fillMaxSize()
) {
val ringWidth = size.width / 5f
drawArc(
color = trackColor,
startAngle = 0f,
sweepAngle = 360f,
useCenter = false,
style = Stroke(ringWidth)
)
drawArc(
color = color,
startAngle = -90f,
sweepAngle = 360 * progress,
useCenter = false,
style = Stroke(ringWidth, cap = StrokeCap.Round)
)
if (isDone) {
val style = textStyle.copy(
fontSize = textStyle.fontSize * fontScaleAnimatable.value,
color = color.copy(alpha = fontAlphaAnimatable.value)
)
val measured = textMeasure.measure(
text = text,
style = style,
constraints = Constraints(
maxWidth = size.width.toInt() * 2 / 3,
maxHeight = size.height.toInt() * 2 / 3,
)
)
drawText(
textMeasurer = textMeasure,
text = text,
topLeft = Offset(
(size.width - measured.size.width) / 2,
(size.height - measured.size.height) / 2
),
style = style
)
drawSparkEffect(Color(0xFFFFD356), sparkAlphaAnimatable.value)
}
}
private fun DrawScope.drawSparkEffect(color: Color, alpha: Float) {
val numRays = 3
val arcAngle = (-20f).toRadian() // 각도 설정
val longRayLength = (size.width / 2) * 0.95f
val shortRayLength = (size.width / 2) * 0.6f
val angleDiff = 23f.toRadian()
val center = Offset(
size.width / 2 + sin(arcAngle - angleDiff) * (size.width / 2),
size.height / 2 - cos(arcAngle - angleDiff) * (size.width / 2),
)
val path = Path()
val angleDistance = 5.5f.toRadian()
for (i in 0 until numRays) {
val angle = arcAngle - i * angleDiff
val leftStart = Offset(
(center.x + sin(angle - angleDistance) * longRayLength),
(center.y - cos(angle - angleDistance) * longRayLength),
)
val leftEnd = Offset(
(center.x + sin(angle - angleDistance) * shortRayLength),
(center.y - cos(angle - angleDistance) * shortRayLength),
)
val rightStart = Offset(
(center.x + sin(angle + angleDistance) * longRayLength),
(center.y - cos(angle + angleDistance) * longRayLength),
)
val rightEnd = Offset(
(center.x + sin(angle + angleDistance) * shortRayLength),
(center.y - cos(angle + angleDistance) * shortRayLength),
)
path.apply {
moveTo(leftStart.x, leftStart.y)
lineTo(
leftEnd.x,
leftEnd.y
)
lineTo(
rightEnd.x,
rightEnd.y
)
lineTo(
rightStart.x,
rightStart.y
)
lineTo(
leftStart.x,
leftStart.y
)
}
}
drawPath(
path = path,
color = color.copy(alpha = alpha),
)
}
중심점을 기준으로 star polygon 그렸을 때처럼 cos, sin 각도를 이용해서 네 꼭짓점을 계산해서 그려줬다.