Compose学习项目:生成随机数组

Compose学习项目:生成随机数组

项目背景与目的

随着Jetpack Compose的逐渐普及,现在Android Studio创建的新项目默认使用Compose实现UI。掌握Compose对于现代Android开发非常重要。这里通过一个简单的应用------生成随机数组,初体验Jetpack Compose的用法。

代码解析

我将逐一解析项目中的关键组件,包括MainViewModelFactoryMainContentTopBarShowAlertDialogHistoryListNumberLine,更好地理解每个部分的作用。完整代码在最后~


MainViewModelFactory

作用

MainViewModelFactory是用于提供ContextViewModel的一个工厂类。由于ViewModel本身不能直接访问Context,因此我们通过这个工厂来传递Application级别的ContextMainViewModel,确保ViewModel可以在不违反架构原则的情况下使用Context相关的功能。

代码解析

kotlin 复制代码
class MainViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return MainViewModel(context.applicationContext) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

MainContent

作用

MainContent是应用程序的主要内容布局,它包含了顶部栏(TopBar)、历史记录列表(HistoryList)以及弹出的对话框(ShowAlertDialog)。这里使用了Column布局来垂直排列这些元素,并且根据viewModel.showDialog的状态决定是否显示对话框。

代码解析

kotlin 复制代码
@Composable
fun MainContent(viewModel: MainViewModel) {
    Column(modifier = Modifier.fillMaxSize()) {
        TopBar(viewModel)
        Spacer(modifier = Modifier.height(8.dp))
        HistoryList(viewModel.getAllEntries())

        if (viewModel.showDialog) {
            ShowAlertDialog(generatedNumbers = viewModel.luckyNumbers.split("\n")) {
                viewModel.showDialog = false
            }
        }
    }
}

TopBar

作用

TopBar实现了页面顶部的操作栏,展示当前日期,并提供一个按钮用于触发随机数的生成。点击按钮后会调用viewModel.generateLuckyNumbers()方法来生成新的幸运号码。

代码解析

kotlin 复制代码
@Composable
fun TopBar(viewModel: MainViewModel) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        Text(text = viewModel.date, fontWeight = FontWeight.Bold)
        Button(onClick = {
            viewModel.generateLuckyNumbers()
        }) {
            Text("生成幸运数组")
        }
    }
}

ShowAlertDialog

作用

当用户生成了一组新的幸运号码时,ShowAlertDialog会被调用来显示一个对话框,列出新生成的号码,并允许用户确认或取消。

代码解析

kotlin 复制代码
@Composable
fun ShowAlertDialog(generatedNumbers: List<String>, onDismiss: () -> Unit) {
    AlertDialog(
        onDismissRequest = onDismiss,
        text = {
            Column(modifier = Modifier.padding(horizontal = 20.dp)) {
                generatedNumbers.forEach { number ->
                    NumberLine(number)
                    Spacer(modifier = Modifier.height(8.dp))
                }
            }
        },
        confirmButton = {
            TextButton(onClick = onDismiss) {
                Text("确定")
            }
        },
        dismissButton = {
            TextButton(onClick = onDismiss) {
                Text("取消")
            }
        }
    )
}

HistoryList

作用

HistoryList负责展示之前保存的所有幸运号码的历史记录。它使用LazyColumn来高效地渲染可能非常长的列表,并为每一条记录添加分隔线。

代码解析

kotlin 复制代码
@Composable
fun HistoryList(entries: List<Pair<String, String>>) {
    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
            .padding(horizontal = 20.dp)
    ) {
        items(entries) { entry ->
            NumberLine(entry.first)
            Divider()
        }
    }
}

NumberLine

作用

NumberLine用于格式化并展示单行幸运号码,将号码分为两部分(前五个数字和最后两个数字),并且对它们进行了颜色上的区分(蓝色和橙色),中间以黑色加号连接。

代码解析

kotlin 复制代码
@Composable
fun NumberLine(line: String) {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 4.dp)
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .align(Alignment.CenterStart)
        ) {
            // Code to display numbers with colors and spacing
        }
    }
}

完整代码

kotlin 复制代码
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LuckyApplicationTheme {
                Surface(color = MaterialTheme.colorScheme.background) {
                    MainContent(viewModel = viewModel(factory = MainViewModelFactory(this)))
                }
            }
        }
    }
}

// ViewModel factory to provide the context
class MainViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return MainViewModel(context.applicationContext) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

class MainViewModel(private val context: Context) : ViewModel() {
    private val prefs: SharedPreferences
        get() = context.getSharedPreferences("LuckyPrefs", Context.MODE_PRIVATE)

    var luckyNumbers by mutableStateOf("")
        private set

    var date by mutableStateOf(getCurrentDate())
        private set

    var showDialog by mutableStateOf(false)

    fun generateLuckyNumbers(): List<String> {
        val generatedNumbers = mutableListOf<String>()
        repeat(5) {
            val firstFive = generateUniqueRandomNumbers(1, 35, 5).sorted().joinToString(" ") { "%02d".format(it) }
            val lastTwo = generateUniqueRandomNumbers(1, 12, 2).joinToString(" ") { "%02d".format(it) }

            with(prefs.edit()) {
                putString("luckyNumbers_$it", "$firstFive + $lastTwo")
                putString("date_$it", getCurrentDate())
                apply()
            }

            // Update UI state with the latest generated numbers
            generatedNumbers.add(formatNumberLine("$firstFive + $lastTwo"))
        }

        Toast.makeText(context, "生成了5组幸运号码", Toast.LENGTH_SHORT).show()
        luckyNumbers = generatedNumbers.joinToString("\n")
        showDialog = true
        return generatedNumbers
    }

    private fun formatNumberLine(line: String): String {
        // Ensure the line is exactly 13 characters long, padding spaces where necessary.
        val parts = line.split("+").map { it.trim() }
        val formattedFirstPart = parts[0].padEnd(10, ' ') // 5 two-digit numbers + 4 spaces
        val formattedSecondPart = parts[1].padStart(6, ' ') // 2 two-digit numbers + 1 space
        return "$formattedFirstPart+ $formattedSecondPart"
    }

    private fun generateUniqueRandomNumbers(min: Int, max: Int, count: Int): List<Int> {
        val random = Random()
        return buildSet {
            while (size < count) {
                add(random.nextInt(max - min + 1) + min)
            }
        }.toList()
    }

    private fun getCurrentDate(): String {
        val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
        return sdf.format(Date())
    }

    fun getAllEntries(): List<Pair<String, String>> {
        val allEntries = mutableListOf<Pair<String, String>>()
        val allKeys = prefs.all.entries.filter { it.key.startsWith("luckyNumbers_") }

        for ((key, value) in allKeys) {
            val entry = value.toString().split("+")
            if (entry.size == 2) {
                allEntries.add(Pair(entry[0].trim(), entry[1].trim()))
            }
        }
        return allEntries.map { Pair(formatNumberLine(it.first + " + " + it.second), "") }
    }
}

@Composable
fun MainContent(viewModel: MainViewModel) {
    Column(modifier = Modifier.fillMaxSize()) {
        TopBar(viewModel)
        Spacer(modifier = Modifier.height(8.dp))
        HistoryList(viewModel.getAllEntries())

        if (viewModel.showDialog) {
            ShowAlertDialog(generatedNumbers = viewModel.luckyNumbers.split("\n")) {
                viewModel.showDialog = false
            }
        }
    }
}

@Composable
fun TopBar(viewModel: MainViewModel) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        Text(text = viewModel.date, fontWeight = FontWeight.Bold)
        Button(onClick = {
            viewModel.generateLuckyNumbers()
        }) {
            Text("生成幸运数组")
        }
    }
}

@Composable
fun ShowAlertDialog(generatedNumbers: List<String>, onDismiss: () -> Unit) {
    AlertDialog(
        onDismissRequest = onDismiss,
        text = {
            Column(modifier = Modifier.padding(horizontal = 20.dp)) {
                generatedNumbers.forEach { number ->
                    NumberLine(number)
                    Spacer(modifier = Modifier.height(8.dp))
                }
            }
        },
        confirmButton = {
            TextButton(onClick = onDismiss) {
                Text("确定")
            }
        },
        dismissButton = {
            TextButton(onClick = onDismiss) {
                Text("取消")
            }
        }
    )
}

@Composable
fun HistoryList(entries: List<Pair<String, String>>) {
    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
            .padding(horizontal = 20.dp)
    ) {
        items(entries) { entry ->
            NumberLine(entry.first)
            Divider()
        }
    }
}

@Composable
fun NumberLine(line: String) {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 4.dp)
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .align(Alignment.CenterStart)
        ) {
            val parts = line.split("+").map { it.trim() }
            val firstPartNumbers = parts[0].split(" ").filter { it.isNotEmpty() }
            val secondPartNumbers = parts[1].split(" ").filter { it.isNotEmpty() }

            // Display first part (blue)
            firstPartNumbers.forEachIndexed { index, num ->
                Text(
                    text = num,
                    fontWeight = FontWeight.Bold,
                    color = Color.Blue,
                    modifier = Modifier.weight(1f)
                )
                if (index < firstPartNumbers.lastIndex) {
                    Spacer(modifier = Modifier.width(1.dp)) // Space between numbers
                }
            }

            // Display plus sign (black)
            Text(
                text = "+",
                fontWeight = FontWeight.Bold,
                color = Color.Black,
                modifier = Modifier.weight(1f)
            )

            // Display second part (orange)
            secondPartNumbers.forEachIndexed { index, num ->
                Text(
                    text = num,
                    fontWeight = FontWeight.Bold,
                    color = Color.Blue,
                    modifier = Modifier.weight(1f)
                )
                if (index < secondPartNumbers.lastIndex) {
                    Spacer(modifier = Modifier.width(1.dp)) // Space between numbers
                }
            }
        }
    }
}
相关推荐
...mzx2 小时前
XXE-Lab靶场漏洞复现
android
Hui_Hong_TaiLang3 小时前
C#.NET使用multipart/form-data方式上传文件及其他数据
android·c#·.net
Patience to do4 小时前
安卓课设版算法计算器
android·jvm·算法
云计算老王5 小时前
MySQL 数据类型
android·mysql·adb
jiet_h6 小时前
Android Compose Modifier
android
火柴就是我6 小时前
compositionLocalOf 与 staticCompositionLocalOf 的简单理解与使用
android
drebander9 小时前
MySQL EXPLAIN 详解:一眼看懂查询计划
android·数据库·mysql
留白的云9 小时前
Android app反编译 攻与防
android·安全性测试
KpLn_HJL10 小时前
leetcode - 1530. Number of Good Leaf Nodes Pairs
android·java·leetcode