iT邦幫忙

2024 iThome 鐵人賽

DAY 18
0
Mobile Development

從零開始學習 Jetpack Compose系列 第 18

從零開始學習 Jetpack Compose Day17 - Canvas + Animate

  • 分享至 

  • xImage
  •  

今天颱風天放假所以不用像平常那麼趕著發文章,因此就用前兩天的 Canvas 以及 Animate 做個小專案,順便嘗試看看不同的分享方式,以及看看自己目前對 Compose 的理解程度。

題目:

今天會做一個顏色的轉盤,透過點擊按鈕來旋轉轉盤,最後文字顯示對應的顏色。

需求拆解:

針對此題目,首先畫面拆成三個元件,分別是:轉盤、按鈕以及最後顯示文字,那轉盤的部分又分成兩個,一個是指針另一個是後面的轉盤,因為到時候旋轉時是針對畫布旋轉,因此他們兩個必須分開做。
另外還必須做動畫,那因為到時候是針對角度變化來旋轉因此使用 Animatable 。

UI 開發

  1. 圓盤
    這邊直接套用Day15的圓盤,只是他的資料就改由外部提供,另外新增 graphicsLayer 到時候會需要透過它來旋轉。
@Composable
fun Roulette(currentRotation: Float, colorList: List<Pair<String, Color>>, modifier: Modifier = Modifier) {
    Canvas(
        modifier = modifier
            .width(200.dp)
            .height(200.dp)
            .graphicsLayer {
                rotationZ = currentRotation
            }
    ) {
        var mStartAngle = 0f
        val mSweepAngle = 360f / colorList.size
        colorList.forEach { color ->
            drawArc(
                color = color.second,
                startAngle = mStartAngle,
                sweepAngle = mSweepAngle,
                useCenter = true,
                size = Size(size.width * .50f, size.height * .50f),
                topLeft = Offset(size.width * .25f, size.width * .25f)
            )
            mStartAngle += mSweepAngle
        }
    }
}
  1. 指針
    透過 drawPath 畫出三角形,搭配 drawArc 畫出圓弧來做出指針。
@Composable
fun Pointer(modifier: Modifier = Modifier) {
    Canvas(
        modifier = modifier.width(200.dp).height(200.dp)
    ) {
        val path = Path().apply {
            moveTo(size.width * .5f, size.width * .3f)
            lineTo(size.width * .5f - 20f , size.width * .5f)
            lineTo(size.width * .5f + 20f, size.width * .5f)
            close()
        }
        drawPath(
            path = path,
            color = Color.Black,
        )
        drawArc(
            color = Color.Black,
            startAngle = 0f,
            sweepAngle = 180f,
            useCenter = true,
            size = Size(40f, 40f),
            topLeft = Offset(size.width * .5f - 20f, size.width * .5f - 20f)
        )
    }
}
  1. 畫面雛形
@Composable
fun Greeting(modifier: Modifier = Modifier) {
    val colorMap = listOf(
        Pair("Red", Color.Red),
        Pair("Blue", Color.Blue),
        Pair("Yellow", Color.Yellow),
        Pair("Green", Color.Green),
        Pair("Cyan", Color.Cyan),
        Pair("LightGray", Color.LightGray),
    )
    val colorName = remember { mutableStateOf("") }

    Column(
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Box {
            Roulette(rotation.value, colorMap)
            Pointer()
        }
        Text(
            text = "${colorName.value}"
        )
        Button(
            modifier = modifier.align(Alignment.CenterHorizontally),
            onClick = {
            
            }
        ) {
            Text(
                text = "旋轉"
            )}
    }
}

https://raw.githubusercontent.com/jian-fu-hung/ithelp-2024/refs/heads/main/Images/Day17/%E6%88%AA%E5%9C%96%202024-10-02%20%E4%B8%8B%E5%8D%886.05.03.png

動畫

畫面的部分已經完成,那接著就是動畫的部分。那動畫就跟前面說的一樣因為要調整角度因此使用AnimatableanimateTo 來達成。

targetValue :目標值,在此專案為目標角度。
easing :類似類似插值器,可以設定動畫呈現方式如快到慢。

rotation.animateTo(
	targetValue = 360 * 10 + finalAngle.toFloat(), 
  animationSpec = tween(
     durationMillis = 1000, // 動畫秒數
     easing = FastOutSlowInEasing
   )
)

加入動畫後

透過計算角度讓動畫旋轉到指定位置,每次點擊會先回到初始位置,然後再進行旋轉。

至於計算的具體方法這裡就不詳述了,有興趣的朋友可以自行查閱或在下方留言詢問。

@Composable
fun Greeting(modifier: Modifier = Modifier) {
    val colorMap = listOf(
        Pair("Red", Color.Red),
        Pair("Blue", Color.Blue),
        Pair("Yellow", Color.Yellow),
        Pair("Green", Color.Green),
        Pair("Cyan", Color.Cyan),
        Pair("LightGray", Color.LightGray),
    )
    val rotation = remember { Animatable(0f) }
    val scope = rememberCoroutineScope()
    val colorName = remember { mutableStateOf("") }

    Column(
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Box {
            Roulette(rotation.value, colorMap)
            Pointer()
        }
        Text(
            text = "${colorName.value}"
        )
        Button(
            modifier = modifier.align(Alignment.CenterHorizontally),
            onClick = {
                // 計算每個區塊的角度範圍
                val anglePerSection = 360 / colorMap.size
                // 隨機選擇區塊編號 (0 到 colorMap.size - 1)
                val randomSection = (0 until 6).random()
                // 指針可接受範圍
                val targetAngle = (270 - anglePerSection .. 271).random()
                // 減360是為了解決大於270的值避免逆時針旋轉
                val finalAngle = if (randomSection * anglePerSection > 271) {
                    targetAngle - (randomSection * anglePerSection - 360)
                } else {
                    targetAngle - (randomSection * anglePerSection)
                }
                scope.launch{
                    // Reset
                    rotation.animateTo(
                        targetValue = 0f,
                        animationSpec = tween(
                            durationMillis = 100,
                            easing = LinearEasing
                        )
                    )

                    rotation.animateTo(
                        targetValue = 360 * 10 + finalAngle.toFloat(),
                        animationSpec = tween(
                            durationMillis = 1000,
                            easing = FastOutSlowInEasing
                        )
                    )
                    colorName.value = colorMap[randomSection].first
                }

            }
        ) {
            Text(
                text = "旋轉"
            )}
    }
}

初始畫面

https://raw.githubusercontent.com/jian-fu-hung/ithelp-2024/refs/heads/main/Images/Day17/%E6%88%AA%E5%9C%96%202024-10-02%20%E4%B8%8B%E5%8D%886.05.03.png

點擊旋轉後

https://raw.githubusercontent.com/jian-fu-hung/ithelp-2024/refs/heads/main/Images/Day17/%E6%88%AA%E5%9C%96%202024-10-02%20%E4%B8%8B%E5%8D%886.20.02.png

附上完整專案
GitHub


上一篇
從零開始學習 Jetpack Compose Day16 - Animation
下一篇
從零開始學習 Jetpack Compose Day18 - 狀態管理
系列文
從零開始學習 Jetpack Compose30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言