Compose学习项目:生成随机数组
项目背景与目的
随着Jetpack Compose的逐渐普及,现在Android Studio创建的新项目默认使用Compose实现UI。掌握Compose对于现代Android开发非常重要。这里通过一个简单的应用------生成随机数组,初体验Jetpack Compose的用法。
代码解析
我将逐一解析项目中的关键组件,包括MainViewModelFactory
、MainContent
、TopBar
、ShowAlertDialog
、HistoryList
和NumberLine
,更好地理解每个部分的作用。完整代码在最后~
MainViewModelFactory
作用
MainViewModelFactory
是用于提供Context
给ViewModel
的一个工厂类。由于ViewModel
本身不能直接访问Context
,因此我们通过这个工厂来传递Application
级别的Context
给MainViewModel
,确保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
}
}
}
}
}