今天颱風天放假所以不用像平常那麼趕著發文章,因此就用前兩天的 Canvas 以及 Animate 做個小專案,順便嘗試看看不同的分享方式,以及看看自己目前對 Compose 的理解程度。
今天會做一個顏色的轉盤,透過點擊按鈕來旋轉轉盤,最後文字顯示對應的顏色。
針對此題目,首先畫面拆成三個元件,分別是:轉盤、按鈕以及最後顯示文字,那轉盤的部分又分成兩個,一個是指針另一個是後面的轉盤,因為到時候旋轉時是針對畫布旋轉,因此他們兩個必須分開做。
另外還必須做動畫,那因為到時候是針對角度變化來旋轉因此使用 Animatable 。
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
}
}
}
@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)
)
}
}
@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 = "旋轉"
)}
}
}
畫面的部分已經完成,那接著就是動畫的部分。那動畫就跟前面說的一樣因為要調整角度因此使用Animatable
的animateTo
來達成。
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 = "旋轉"
)}
}
}
初始畫面
點擊旋轉後
附上完整專案
GitHub