使用 Compose 创建一款交互式 Dice Roller Android 应用。
完成:
- 定义可组合函数。
- 使用组合创建布局。
- 使用
Button
可组合项创建按钮。 - 导入
drawable
资源。 - 使用
Image
可组合项显示图片。 - 使用可组合项构建交互式界面。
- 使用
remember
可组合项将组合中的对象存储到内存中。 - 使用
mutableStateOf()
函数刷新界面以创建可观察对象。
一 创建项目
- 在 Android Studio 中,依次点击 File > New > New Project。
- 在 New Project 对话框中,选择 Empty Activity,然后点击 Next
- 在 Name 字段中,输入
Dice Roller
。 - 在 Minimum SDK 字段中,从菜单中选择最低 API 级别 24 (Nougat),然后点击 Finish。
二 创建布局基础架构
主布局初始代码如下:
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {MyApplicationTheme {// A surface container using the 'background' color from the themeSurface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colorScheme.background) {Greeting("Android")}}}}
}@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {Text(text = "Hello $name!",modifier = modifier)
}@Preview(showBackground = true)
@Composable
fun GreetingPreview() {MyApplicationTheme {Greeting("Android")}
}
仿真运行,可以看到 Hello Andriod输出。
1 重构示例代码
您需要更改一些生成的代码,使其更贴近 Dice Roller 应用的主题。
如您在最终应用的屏幕截图中看到的那样,应用中有骰子的图片和用于掷骰子的按钮。您将构建可组合函数以反映此架构。
如需重构示例代码,请执行以下操作:
- 移除 DefaultPreview() 函数。
- 创建一个带有 @Composable 注解的 DiceWithButtonAndImage() 函数。
该可组合函数代表布局的界面组件,还包含按钮点击和图片显示逻辑。
- 移除 Greeting(name: String) 函数。
- 创建一个带有 @Preview 和 @Composable 注解的 DiceRollerApp() 函数。
由于该应用仅包含一个按钮和一张图片,因此不妨将该可组合函数视为应用本身。因此,我们称之为 DiceRollerApp() 函数。
@Preview
@Composable
fun DiceRollerApp() {}@Composable
fun DiceWithButtonAndImage() {}
由于移除了 Greeting() 函数,因此 DiceRollerTheme() lambda 正文中对 Greeting("Android") 的调用会突出显示为红色。这是因为编译器无法再找到对该函数的引用。
删除 onCreate() 方法中的 setContent{} lambda 内的所有代码。
- 在 setContent{} lambda 正文中,调用 DiceRollerTheme{} lambda,然后在 DiceRollerTheme{} lambda 内调用 DiceRollerApp() 函数。
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {DiceRollerTheme {DiceRollerApp()}}}
}
- 在
DiceRollerApp()
函数中,调用DiceWithButtonAndImage()
函数。
@Preview
@Composable
fun DiceRollerApp() {DiceWithButtonAndImage()
}
2 添加修饰符
Compose 使用 Modifier 对象,该对象是用于修饰或修改 Compose 界面元素行为的元素的集合。您将使用该对象来设置 Dice Roller 应用组件的界面组件样式。
如需添加修饰符,请执行以下操作:
- 将 DiceWithButtonAndImage() 函数修改为接受 Modifier 类型的 modifier 实参,并为其分配默认值 Modifier。
@Composable
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
}
该函数允许传入 modifier 形参。modifier 形参的默认值为 Modifier 对象,因此是方法签名的 = Modifier 部分。借助某个形参的默认值,未来调用该方法的任何调用方都可以决定是否为该形参传递值。如果它们传递自己的 Modifier 对象,则可以自定义界面的行为和装饰。如果它们选择不传递 Modifier 对象,系统会假定使用默认值,即普通的 Modifier 对象。您可以将这种做法应用于任何形参。如需详细了解默认实参,请参阅默认实参。
- 现在,DiceWithButtonAndImage() 可组合项具有修饰符参数,请在调用该可组合项时传递修饰符。由于 DiceWithButtonAndImage() 函数的方法签名已更改,因此在调用该方法时,应传入包含所需装饰的 Modifier 对象。Modifier 类负责对 DiceRollerApp() 函数中的可组合对象进行装饰或向其添加行为。在本例中,需要向传递到 DiceWithButtonAndImage() 函数的 Modifier 对象添加一些重要的装饰。
- 将 fillMaxSize() 方法链接到
Modifier
对象,以便让布局填充整个屏幕。 - 将
wrapContentSize()
方法链接到Modifier
对象,然后传递Alignment.Center
作为实参以将组件居中。Alignment.Center
会指定组件同时在水平和垂直方向上居中。
这样,fun DiceRollerApp()函数的最终代码如下:
@Preview
@Composable
fun DiceRollerApp() {DiceWithButtonAndImage(modifier = Modifier.fillMaxSize().wrapContentSize(Alignment.Center))
}
注意:
- import androidx.compose.ui.Modifier 语句会导入 androidx.compose.ui.Modifier 软件包,以便您引用 Modifier 对象。
- fllMaxSize() 和 wrapContentSize() 的 import 语句分别是 import androidx.compose.foundation.layout.fillMaxSize 和 import androidx.compose.foundation.layout.wrapContentSize。
- Alignment 对象的 import 语句为 import androidx.compose.ui.Alignment。
三 创建垂直布局
在 Compose 中,垂直布局是使用 Column()
函数创建的。
Column() 函数是一种可组合项布局,它会按垂直序列放置其子级。在预期的应用设计中,骰子图片应该垂直显示在掷骰子按钮的上方。
如需创建垂直布局,请执行以下操作:
- 在 DiceWithButtonAndImage() 函数中,添加一个 Column() 函数。
- 将 modifier 实参从 DiceWithImageAndButton() 方法签名传递给 Column() 的修饰符实参。modifier 实参可确保 Column() 函数中的可组合项遵守对 modifier 实例调用的约束条件。
- 将 horizontalAlignment 实参传递给 Column() 函数,然后将其值设为 Alignment.CenterHorizontally。这可以确保列中的子项相对于宽度在设备屏幕上居中。
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {Column (modifier = modifier,horizontalAlignment = Alignment.CenterHorizontally) {}
}
四 添加按钮
- 在 strings.xml 文件中,添加一个字符串并将其设为 Roll 值。
- 在 Column() 的 lambda 正文中,添加 Button() 函数。
- 在 MainActivity.kt 文件中,将 Text() 函数添加到函数 lambda 正文中的 Button()。
- 将 roll 字符串的字符串资源 ID 传递到 stringResource() 函数,并将结果传递到 Text 可组合项。
strings.xml 文件:
<string name="roll">Roll</string>
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
Column(modifier = modifier,horizontalAlignment = Alignment.CenterHorizontally) {Button(onClick = { /*TODO*/ }) {Text(stringResource(R.string.roll))}}
}
五 添加图片
应用的另一个重要组件是骰子图片,在用户点按 Roll 按钮后,该图片会显示结果。
1 导入图片
预先准备好 6 个骰子图片文件,都是200*250dp,其骰子点数分别为 1 到 6。
将其放到项目目录\app\src\main\res\drawable下。
2 添加 Image 可组合项
骰子图片应显示在 Roll 按钮上方。Compose 本身会依序放置界面组件。也就是说,哪个可组合项声明在先,就会先行显示。这意味着,首先声明的可组合项会显示在之后声明的可组合项的上面或前面。Column 可组合项中的可组合项会在设备上按上下顺序显示。在该应用中,您将使用 Column 来垂直堆叠可组合项,因此首先在 Column() 函数中声明的可组合项会先于同一 Column() 函数中之后才声明的可组合项显示。
如需添加 Image 可组合项,请执行以下操作:
- 在 Column() 函数正文中,在 Button() 函数前面创建一个 Image() 函数。
- 向 Image() 函数传递一个 painter 实参,然后为其分配接受可绘制资源 ID 实参的 painterResource 值。目前,请传递以下资源 ID:R.drawable.dice_1 实参。
- 每当您在应用中创建图片时,都应该提供所谓的“内容说明”。内容说明是 Android 开发的重要组成部分。内容说明能够向各界面组件添加说明,以提高无障碍性。如需详细了解内容说明,请参阅描述每个界面元素。您可以将内容说明作为形参传递到图片。
这样,fun DiceWithButtonAndImage()函数体就变为
Column(modifier = modifier,horizontalAlignment = Alignment.CenterHorizontally
) {Image(painter = painterResource(R.drawable.dice_1),contentDescription = "1")Button(onClick = { /*TODO*/ }) {Text(stringResource(R.string.roll))}
}
预览画面为:
注意:
- Image 可组合项的 import 语句为 import androidx.compose.foundation.Image。
六 构建掷骰子逻辑
现在所有必要的可组合项均已存在,接下来您可以修改应用,以便用户能点按按钮来掷出骰子
1 将 Button 设为可交互
- 在 DiceWithButtonAndImage() 函数中的 Column() 函数前面,创建一个 result 变量并将其值设为等于 1。
- 查看 Button 可组合项。您会发现,系统正在向它传递一个 onClick 形参,该形参已设为内含 /*TODO*/ 注释的一对大括号。在本例中,大括号代表所谓的“lambda”,大括号内的区域是 lambda 正文。将函数作为实参进行传递时,相应过程也可称为回调。
- 在 Button() 函数中,从 onClick 形参的 lambda 正文的值中移除 /*TODO*/ 注解。
- 掷骰子是随机的。为了在代码中反映这一点,您需要使用正确的语法来生成随机数字。在 Kotlin 中,您可以对一个数字范围使用 random() 方法。在 onClick lambda 正文中,将 result 变量的范围设为 1 到 6,然后针对该范围调用 random() 方法。请注意,在 Kotlin 中,范围由范围中的第一个数字与范围中的最后一个数字之间的两个句点指定。
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {var result = 1Column(modifier = modifier,horizontalAlignment = Alignment.CenterHorizontally) {Image(painter = painterResource(imageResource), contentDescription = result.toString())Button(onClick = { result = (1..6).random() }) {Text(stringResource(R.string.roll))}}
}
现在,该按钮可点按了,但点按该按钮并不会产生任何可见的变化,因为您仍需构建该功能。
2 为 Dice Roller 应用添加条件
- 将 result 变量设为 remember 可组合项。remember 可组合项需要传递函数。
- 在 remember 可组合项正文中,传入 mutableStateOf() 函数,然后向该函数传递 1 实参。mutableStateOf() 函数会返回一个可观察对象。稍后,您会详细了解可观察对象,但目前这基本上意味着,当 result 变量的值变化时,系统会触发重组、反映结果值并刷新界面。
- 在 result 变量实例化的下方,创建一个不可变的 imageResource 变量,并将其设为接受 result 变量的 when 表达式,然后将每个可能的结果设为其可绘制对象。
- 将传递到
Image
可组合项的painterResource
形参的 ID 从R.drawable.dice_1
可绘制对象更改为imageResource
变量。 - 通过将
result
变量转换为包含toString()
的字符串并将其作为contentDescription
传递,更改Image
可组合项的contentDescription
形参以反映result
变量的值。
这样,fun DiceWithButtonAndImage()函数的最终代码如下:
@Composable
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {var result by remember {mutableStateOf(1)}Column (modifier = modifier,horizontalAlignment = Alignment.CenterHorizontally) {val imageResource = when (result) {1 -> R.drawable.dice_12 -> R.drawable.dice_23 -> R.drawable.dice_34 -> R.drawable.dice_45 -> R.drawable.dice_5else -> R.drawable.dice_6}Image(painter = painterResource(id = imageResource),contentDescription = result.toString())Button(onClick = { result = (1..6).random() }) {Text(stringResource(R.string.roll))}}
}
七 运行应用。
现在,Dice Roller 应用应该完全可以正常运行了!