Compose Multiplatform 之旅 —做一个自己的项目(别踩白块)

1,463 阅读4分钟

通过之前的介绍,我们对Compose Multiplatform项目有了一个基本的了解,但是光了解有啥意思呐,今天我们就做个有意思的项目—别踩白块,想必很多人也玩过这个游戏。

想更多了解Compose Multiplatform,也可以看看其他文章

20241201212114.gif

项目设计

拆解别踩白块游戏,主要分为3点:

  • 方块绘制
  • 方块滚动
  • 方块点击

这3点也会涉及日常开发很常见场景。

方块绘制

思路:我们首先是绘制一行,这里我们固定一行四列,然后随机一个0-3随机一个数,绘制成黑块,其他绘制成白块。 然后重复一行的思路,绘制成无数行。

@Composable  
fun GameScreen() {  
  
    MaterialTheme {  

		// 使用LazyColumn 绘制多列,类似于Android 的RecyclerView
        LazyColumn(  
            Modifier.fillMaxWidth(),  
            horizontalAlignment = Alignment.CenterHorizontally,  
        ) {  
            item {  
	            //重复绘制多行
                repeat(200) {  
	                //随机一个黑色块的下标
                    val randomIndex by remember {   mutableStateOf((0..3).random())  }  
                    Row(  
                        modifier = Modifier.fillMaxWidth(),  
                        verticalAlignment = Alignment.CenterVertically,  
                    ) {  
                        repeat(4) { index ->  
	                        //weight 均分一行四列
                            Column(modifier = Modifier.weight(1f)) {  
                                Box(  
                                    modifier = Modifier  
                                        .fillMaxWidth()  
                                        .height(160.dp)  
                                        .background(  
		                                    //设置对应的黑色块和白色块
                                            if (index == randomIndex)   
                                                Color.Black  
                                            else   
                                                Color.White  
                                        )  
                                )  
                            }}}}}}}
}

通过上面的思路,一个简单的别踩白块布局就做好了,下图为桌面端效果。

Pasted image 20241201201439.png

不过这个效果看着怪怪的,我们再加个分割线和背景色

MaterialTheme {  
  
    val gradientColors = listOf(Color.Red, Color.Yellow, Color.Green)  
    val brush = Brush.linearGradient(  
        colors = gradientColors  
    )  
  
    LazyColumn(  
	    //设置一个渐变的brush背景
        Modifier.fillMaxWidth().background(brush),  
        horizontalAlignment = Alignment.CenterHorizontally,  
    ) {
	    //...
	    Box(  
		    modifier = Modifier  
			.background(  
			    if (index == randomIndex)  
			        Color(0x66222222)  
			    else  
			        Color(0x66ffffff)  
			)
			//设置一个边框
	        .border(  
	            width = 0.5.dp,  
	            color = Color(0x66222222),  
	            shape = RoundedCornerShape(1)  
	        )  
		)
		//...
    }
}

调整后的效果

Pasted image 20241201203859.png

方块滚动

使用LaunchedEffect 在Composable 中,使用协程执行定时任务,使用listState进行滚动。一般滚动是从上到下,我们先给一个足够大的距离,让他滚动到底部。


val listState = rememberLazyListState()

LaunchedEffect(key1 = true) {  
    launch {
	    //给一个足够大的距离,滚到到最底部  
        listState.scrollBy(2000000f)  
		delay(1000)  
		while (true) {  
			//每次慢慢往上移一点点,形成滚动的效果
		    listState.scrollBy(-4f)  
		    delay(20)  
		} 
    }  
}

LazyColumn(  
    state = listState  
){
	//...
}

当当当~ 滚动的效果就完成了

20241201205358.gif

方块点击

在点击,方块时,我们希望方块消失,并且会记录对应的分数。在最开始时,不进行滚动,点击开始方块时,才进行滚动计算分数。

var bgColor by remember {  
    mutableStateOf(  
        if (index == randomIndex)  
            Color(0x66222222)  
        else  
            Color(0x66ffffff)  
    )  
}  
  
Box(  
    modifier = Modifier   
        .background(  
            bgColor  
        )  
        .clickable {  
	        //点击第一个方块,进行游戏
            if (row == 199 && index == randomIndex) {  
                start = true  
                bgColor = Color(0x66ffffff)  
            } else if (index == randomIndex) {  
	            //背景色改变,分数增加
                bgColor = Color(0x66ffffff)  
                score++  
            }  
  
        }
)

最终效果展示

桌面端

20241201212114.gif

网页端(存在中文显示BUG)

20241201213536.gif

移动端(Android/ios)

b4388339-fe22-4bcf-b162-152424b33b72.gif b1fcff9b-3d3a-46a2-990b-d420d1bd5d3f.gif

完整代码

```
package com.example.composeApp

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@Composable
fun GameScreen() {

    val listState = rememberLazyListState()
    var start by remember { mutableStateOf(false) }
    var score by remember { mutableStateOf(0) }

    MaterialTheme {

        val gradientColors = listOf(Color.Red, Color.Yellow, Color.Green)
        val brush = Brush.linearGradient(
            colors = gradientColors
        )

        LaunchedEffect(key1 = true) {
            launch {
                //移动到最底部
                listState.scrollBy(2000000f)
            }
        }

        LaunchedEffect(start) {
            //点击开始时,才进行滚动
            if (start) {
                launch {
                    while (true) {
                        listState.scrollBy(-10f)
                        delay(16)
                    }
                }
            }
        }

        LazyColumn(
            Modifier.fillMaxWidth().background(brush),
            horizontalAlignment = Alignment.CenterHorizontally,
            state = listState
        ) {
            item {
                repeat(200) { row ->
                    val randomIndex by remember {
                        mutableStateOf((0..3).random())
                    }
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        verticalAlignment = Alignment.CenterVertically,
                    ) {
                        repeat(4) { index ->
                            Column(modifier = Modifier.weight(1f)) {

                                var bgColor by remember {
                                    mutableStateOf(
                                        if (index == randomIndex)
                                            Color(0x66222222)
                                        else
                                            Color(0x66ffffff)
                                    )
                                }

                                Box(
                                    modifier = Modifier
                                        .fillMaxWidth()
                                        .height(160.dp)
                                        .background(
                                            bgColor
                                        )
                                        .border(
                                            width = 0.5.dp,
                                            color = Color(0x66222222),
                                            shape = RoundedCornerShape(1)
                                        )
                                        .clickable {
                                            if (row == 199 && index == randomIndex) {
                                                start = true
                                                bgColor = Color(0x66ffffff)
                                            } else if (index == randomIndex) {
                                                bgColor = Color(0x66ffffff)
                                                score++
                                            }

                                        }
                                ) {
                                    if (row == 199 && index == randomIndex) {
                                        Text(
                                            "开始",
                                            style = TextStyle(
                                                color = Color.White,
                                                fontSize = 20.sp
                                            ),
                                            modifier = Modifier.align(Alignment.Center),
                                        )
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(10.dp)
        ) {
            Text(
                text = "分数:$score",
                modifier = Modifier
                    .align(Alignment.TopEnd)
                    .padding(10.dp),
                color = Color.Black
            )
        }

    }
}
```

结语

至此,我们就完成了别踩白块的开发,希望大家可以体会到Compose Multiplatform项目的开发乐趣。不过这次开发过程中还是有很多值得研究的地方,比如remember、LaunchedEffect、LazyColumn等等,后续希望可以和大家一起继续深入。