1. 准备工作
Jetpack Compose 是一款新型工具包,旨在帮助简化界面开发。该工具包将响应式编程模型与简洁易用的 Kotlin 编程语言相结合,并采用完全声明式的代码编写方式,让您可以通过调用一系列函数来描述界面,这些函数会将数据转换为界面层次结构。当底层数据发生变化时,框架会自动重新执行这些函数,为您更新界面层次结构。
Compose 应用由可组合函数构成。可组合函数即带有 @Composable
标记的常规函数,这些函数可以调用其他可组合函数。使用一个函数就可以创建一个新的界面组件。该注解会告知 Compose 为函数添加特殊支持,以便后续更新和维护界面。借助 Compose,您可以将代码设计成多个小代码块。可组合函数通常简称为"可组合项"。
通过创建可重用的小型可组合项,您可以轻松构建应用中所用界面元素的库。每个可组合项对应于屏幕的一个部分,可以单独修改。
2. 启动新的 Compose 项目
kotlin
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
}
setContent
中使用的应用主题取决于项目名称。
3. Compose 使用入门
可组合函数
kotlin
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
可组合函数 是带有 @Composable
注解的常规函数。这类函数自身可以调用其他 @Composable
函数。
注意 :可组合函数是带有
@Composable
注解的 Kotlin 函数,如上述代码段所示。
Android 应用中的 Compose
使用 Compose 时,Activity
仍然是 Android 应用的入口点。在我们的项目中,用户打开应用时会启动 MainActivity
(如 AndroidManifest.xml
文件中所指定)。您可以使用 setContent
来定义布局,但不同于在传统 View 系统中使用 XML 文件,您将在该函数中调用可组合函数。
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
}
BasicsCodelabTheme
是为可组合函数设置样式的一种方式。
若要使用 Android Studio 预览,您只需使用 @Preview
注解标记所有无参数可组合函数或采用默认形参的函数,然后构建您的项目即可。现在 MainActivity.kt
文件中已经包含了一个 Preview Composable
函数。您可以在同一个文件中包含多个预览,并为它们指定名称。
kotlin
@Preview(showBackground = true, name = "Text preview")
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greeting(name = "Android")
}
}
注意:在此项目中导入与 Jetpack Compose 相关的类时,请从以下位置导入:
androidx.compose.*
,适用于编译器和运行时类androidx.compose.ui.*
,适用于界面工具包和库
4. 微调界面
kotlin
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(color = MaterialTheme.colorScheme.primary) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
}
嵌套在 Surface
内的组件将在该背景颜色之上绘制。
Material 组件(例如 androidx.compose.material3.Surface
)旨在提供应用中可能需要的常见功能(例如为文本选择适当的颜色),让您获得更好的体验。我们之所以说 Material 很实用,是因为它提供在大多数应用中都会用到的实用默认值和模式。Compose 中的 Material 组件是在其他基础组件(位于 androidx.compose.foundation
中)的基础上构建的。如果您需要更高的灵活性,也可以从您的应用组件中访问这些组件。
在这种情况下,Surface
会了解,当该背景设置为 primary
颜色后,其上的任何文本都应使用 onPrimary
颜色,此颜色也在主题中进行了定义。
注意 :如需查看 Compose 中 Material 组件的交互式列表,请查看 Compose Material Catalog 应用。
修饰符
大多数 Compose 界面元素(例如 Surface
和 Text
)都接受可选的 modifier
参数。修饰符会指示界面元素如何在其父布局中放置、显示或表现。
例如,padding
修饰符会在其修饰的元素周围应用一定的空间。您可以使用 Modifier.padding()
创建内边距修饰符。您还可串联多个修饰符以添加它们。
现在,为界面上的 Text
添加内边距:
kotlin
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.unit.dp
// ...
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(color = MaterialTheme.colorScheme.primary) {
Text(
text = "Hello $name!",
modifier = modifier.padding(24.dp)
)
}
}
有数十种修饰符可用于实现对齐、添加动画、设置布局、使可点击或可滚动以及转换等效果。有关完整列表,请查看 Compose 修饰符列表。
5. 重复使用可组合项
您添加到界面的组件越多,创建的嵌套层级就越多。如果函数变得非常大,可能会影响可读性。通过创建可重用的小型组件,可以轻松构建应用中所用界面元素的库。每个组件对应于屏幕的一个部分,可以单独修改。
最佳实践是,您的函数应包含一个修饰符参数,系统默认为该参数分配空修饰符。将此修饰符转发到您在函数内调用的第一个可组合项。这样,调用点就可以在可组合函数之外调整布局指令和行为了。
创建一个名为 MyApp
的可组合项,该组合项中包含问候语。
kotlin
@Composable
fun MyApp(modifier: Modifier = Modifier) {
Surface(
modifier = modifier,
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
这样一来,由于现在可以重复使用 MyApp
可组合项,您就可以省去 onCreate
回调和预览,从而避免重复编写代码。
在预览中,调用 MyApp
并移除预览的名称。
kotlin
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
MyApp()
}
}
6. 创建列和行
Compose 中的三个基本标准布局元素是 Column
、Row
和 Box
可组合项。

它们是接受可组合内容的可组合函数,因此您可以在其中放置项目。例如,Column
中的每个子级都将垂直放置。
kotlin
import androidx.compose.foundation.layout.Column
// ...
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(color = MaterialTheme.colorScheme.primary) {
Column(modifier = modifier.padding(24.dp)) {
Text(text = "Hello ")
Text(text = name)
}
}
}
Compose 和 Kotlin
可组合函数可以像 Kotlin 中的其他函数一样使用。这会使界面构建变得非常有效,因为您可以添加语句来影响界面的显示方式。
例如,您可以使用 for
循环向 Column
中添加元素:
kotlin
@Composable
fun MyApp(
modifier: Modifier = Modifier,
names: List<String> = listOf("World", "Compose")
) {
Column(modifier) {
for (name in names) {
Greeting(name = name)
}
}
}
您尚未设置可组合项的尺寸,也未对可组合项的大小添加任何限制,因此每一行仅占用可能的最小空间,预览时的效果也是如此。让我们更改预览效果,以模拟小屏幕手机的常见宽度 320dp。按如下所示向 @Preview
注解添加 widthDp
参数:
kotlin
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
MyApp()
}
}
请注意:
- 修饰符可以包含重载,因而具有相应的优势,例如您可以指定不同的方式来创建内边距。
- 若要向一个元素添加多个修饰符,您只需要将它们链接起来即可。
添加按钮

Button
是 material3 软件包提供的一种可组合项,它采用可组合项作为最后一个参数。由于尾随 lambda 可以移到括号之外,因此您可以向按钮添加任何内容作为子级,例如 Text
:
kotlin
Button(
onClick = { } // You'll learn about this callback later
) {
Text("Show less")
}
注意 :Compose 根据 Material Design 按钮规范提供了不同类型的
Button
:Button
、ElevatedButton
、FilledTonalButton
、OutlinedButton
和TextButton
。在本示例中,您将使用ElevatedButton
,它会封装Text
作为ElevatedButton
内容。
为了实现这一点,您需要学习如何在行尾放置可组合项。由于没有 alignEnd
修饰符,因此您需要在开始时为该可组合项赋予一定的 weight
。weight
修饰符会让元素填满所有可用空间,使其"具有弹性",也就是会推开其他没有权重的元素(即"无弹性"元素)。该修饰符还会使 fillMaxWidth
修饰符变得多余。
现在尝试添加该按钮,并按照上述图片中所示放置该按钮。
kotlin
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.ElevatedButton
// ...
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier.weight(1f)) {
Text(text = "Hello ")
Text(text = name)
}
ElevatedButton(
onClick = { /* TODO */ }
) {
Text("Show more")
}
}
}
}
7. Compose 中的状态
注意 :Compose 应用通过调用可组合函数将数据转换为界面。如果您的数据发生变化,Compose 会使用新数据重新执行这些函数,从而创建更新后的界面,此过程称为重组。Compose 还会查看各个可组合项需要哪些数据,以便只需重组数据发生了变化的组件,而避免重组未受影响的组件。
正如 Compose 编程思想一文中所述:
可组合函数可以按任意顺序频繁执行,因此您不能以代码的执行顺序或该函数的重组次数为判断依据。
如需向可组合项添加内部状态,可以使用 mutableStateOf
函数,该函数可让 Compose 重组读取该 State
的函数。
注意:
State
和MutableState
是两个接口,它们具有特定的值,每当该值发生变化时,它们就会触发界面更新(重组)。
但是,不能只是 将 mutableStateOf
分配给可组合项中的某个变量 。如前所述,重组可能会随时发生,这会再次调用可组合项,从而将状态重置为值为 false
的新可变状态。
如需在重组后保留状态,请使用 remember
记住可变状态。
kotlin
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
// ...
@Composable
fun Greeting(...) {
val expanded = remember { mutableStateOf(false) }
// ...
}
remember
可以起到保护作用,防止状态在重组时被重置。
请注意,如果从屏幕的不同部分调用同一可组合项,则会创建不同的界面元素,且每个元素都会拥有自己的状态版本。您可以将内部状态视为类中的私有变量。
可组合函数会自动"订阅"状态。如果状态发生变化,读取这些字段的可组合项将会重组以显示更新。
更改状态和响应状态更改
为了更改状态,Button
具有一个名为 onClick
的形参,但它不接受值,而接受函数。
注意 :您可能不熟悉以这种方式使用的函数,这其实就是一种在 Compose 中广泛使用的非常强大的 Kotlin 功能。函数是 Kotlin 中的首要元素,您可以将它们分配给某个变量,传递给其他函数,甚至可以从它们自身返回函数。您可以在此处了解 Compose 如何使用 Kotlin 功能。
如需详细了解如何定义和实例化函数,请参阅函数类型文档。
您可以通过为"onClick"指定 lambda 表达式,定义点击时将执行的操作。例如,切换展开状态的值,并根据该值显示不同的文本。
kotlin
ElevatedButton(
onClick = { expanded.value = !expanded.value },
) {
Text(if (expanded.value) "Show less" else "Show more")
}
点击该按钮后,expanded
会切换,从而触发对按钮内文本的重组。每个 Greeting
都具有自己的展开状态,因为它们属于不同的界面元素。
展开内容
现在,我们来根据请求实际展开内容。添加一个依赖于状态的额外变量:
kotlin
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
val expanded = remember { mutableStateOf(false) }
val extraPadding = if (expanded.value) 48.dp else 0.dp
// ...
您无需在重组后记住 extraPadding
,因为它仅执行简单的计算。
现在我们可以将新的内边距修饰符应用于 Column:
kotlin
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
val expanded = remember { mutableStateOf(false) }
val extraPadding = if (expanded.value) 48.dp else 0.dp
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(
modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding)
) {
Text(text = "Hello ")
Text(text = name)
}
ElevatedButton(
onClick = { expanded.value = !expanded.value }
) {
Text(if (expanded.value) "Show less" else "Show more")
}
}
}
}
8. 状态提升
在可组合函数中,被多个函数读取或修改的状态应位于共同祖先实体中,此过程称为状态提升。"提升"意为"提高"或"升级"。
使状态可提升,可以避免复制状态和引入 bug,有助于重复使用可组合项,并大大降低可组合项的测试难度。相反,不需要由可组合项的父级控制的状态则不应该被提升。可信来源属于该状态的创建者和控制者。
在 Compose 中,您不会隐藏界面元素,因为不会将它们添加到组合中,因此它们也不会添加到 Compose 生成的界面树中。
kotlin
@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by remember { mutableStateOf(true) }
Surface(modifier) {
if (shouldShowOnboarding) {
OnboardingScreen(/* TODO */)
} else {
Greetings()
}
}
}
我们还需要与初始配置屏幕共享 shouldShowOnboarding
,但我们不会直接传递它。与其让 OnboardingScreen
更改状态,不如让它在用户点击"Continue"按钮时通知我们。
如何向上传递事件?通过向下传递回调来传递。回调是这样一类函数,它们以实参的形式传递给其他函数,并在事件发生时执行。
尝试向初始配置屏幕添加定义为 onContinueClicked: () -> Unit
的函数参数,以便您可以从 MyApp
更改状态。
解决方案:
kotlin
@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by remember { mutableStateOf(true) }
Surface(modifier) {
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
}
@Composable
fun OnboardingScreen(
onContinueClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier
.padding(vertical = 24.dp),
onClick = onContinueClicked
) {
Text("Continue")
}
}
}
通过向 OnboardingScreen
传递函数而不是状态,可以提高该可组合项的可重用性,并防止状态被其他可组合项更改。一般而言,这可以让事情变得简单。一个很好的例子就是,现在需要如何修改初始配置屏幕预览来调用 OnboardingScreen
:
kotlin
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {}) // Do nothing on click.
}
}
将 onContinueClicked
分配给空 lambda 表达式就等于"什么也不做",这非常适合于预览。
shouldShowOnboarding
使用的是 by
关键字,而不是 =
。这是一个属性委托,可让您无需每次都输入 .value
。
我们首次使用了 by
属性委托,以避免每次都使用值。
9. 创建高效延迟列表
您可以设置列表的大小并使用其 lambda 中包含的值来填充列表(这里的 $it
代表列表索引):
kotlin
names: List<String> = List(1000) { "$it" }
为显示可滚动列,我们需要使用 LazyColumn
。LazyColumn
只会渲染屏幕上可见的内容,从而在渲染大型列表时提升效率。
注意 :
LazyColumn
和LazyRow
相当于 Android View 中的RecyclerView
。
在其基本用法中,LazyColumn
API 会在其作用域内提供一个 items
元素,并在该元素中编写各项内容的渲染逻辑:
kotlin
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
// ...
@Composable
private fun Greetings(
modifier: Modifier = Modifier,
names: List<String> = List(1000) { "$it" }
) {
LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
items(items = names) { name ->
Greeting(name = name)
}
}
}
注意 :请确保导入
androidx.compose.foundation.lazy.items
,因为 Android Studio 默认会选择另一个 items 函数。
注意 :LazyColumn
不会像RecyclerView
一样回收其子级。它会在您滚动它时发出新的可组合项,并保持高效运行,因为与实例化 AndroidViews
相比,发出可组合项的成本相对较低。
10. 保留状态
保留初始配置界面状态
如果您在设备上运行应用,点击按钮,然后旋转,系统会再次显示初始配置界面。remember
函数仅在可组合项包含在组合中时起作用。旋转屏幕后,整个 activity 都会重启,所有状态都将丢失。当发生任何配置更改或者进程终止时,也会出现这种情况。
您可以使用 rememberSaveable
,而不使用 remember
。这会保存每个在配置更改(如旋转)和进程终止后保留下来的状态。
现在,将 shouldShowOnboarding
中的 remember
替换为 rememberSaveable
:
kotlin
import androidx.compose.runtime.saveable.rememberSaveable
// ...
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
运行应用,旋转屏幕,更改为深色模式,或者终止进程。除非您之前退出了应用,否则系统不会显示初始配置界面。
保持列表项的展开状态
如果您展开某个列表项并滚动列表直至该项不在视野范围内,或者旋转设备并返回到展开的项,您会看到该项现已恢复为初始状态。
解决方法是也为展开状态使用 rememberSaveable:
kotlin
var expanded by rememberSaveable { mutableStateOf(false) }
11. 为列表添加动画效果
在 Compose 中,有多种方式可以为界面添加动画效果:从用于添加简单动画的高阶 API 到用于实现完全控制和复杂过渡的低阶方法,不一而足。您可以在该文档中了解相关信息。
为此,您将使用 animateDpAsState
可组合项。该可组合项会返回一个 State 对象,该对象的 value
会被动画持续更新,直到动画播放完毕。该可组合项需要一个类型为 Dp
的"目标值"。
创建一个依赖于展开状态的动画 extraPadding
。
kotlin
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp
)
animateDpAsState
接受可选的 animationSpec
参数供您自定义动画。
kotlin
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
spring
规范不接受任何与时间有关的参数。它仅依赖于物理属性(阻尼和刚度),使动画更自然。
使用 animate*AsState
创建的任何动画都是可中断的。这意味着,如果目标值在动画播放过程中发生变化,animate*AsState
会重启动画并指向新值。中断在基于弹簧的动画中看起来尤其自然:
如果您想探索不同类型的动画,请尝试为 spring
提供不同的参数,尝试使用不同的规范(tween
、repeatable
)和不同的函数(animateColorAsState
或不同类型的动画 API)。
12. 设置应用的样式和主题
MaterialTheme
是一个可组合函数,体现了 Material Design 规范中的样式设置原则。样式设置信息会逐级向下传递到位于其 content
内的组件,这些组件会读取该信息来设置自身的样式。您在界面中已经使用了 BasicsCodelabTheme
,如下所示:
kotlin
BasicsCodelabTheme {
MyApp(modifier = Modifier.fillMaxSize())
}
由于 BasicsCodelabTheme
将 MaterialTheme
包围在其内部,因此 MyApp
会使用该主题中定义的属性来设置样式。从任何后代可组合项中都可以检索 MaterialTheme
的三个属性:colorScheme
、typography
和 shapes
。使用它们设置其中一个 Text
的标题样式:
kotlin
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp))
) {
Text(text = "Hello, ")
Text(text = name, style = MaterialTheme.typography.headlineMedium)
}
上例中的 Text
可组合项会设置新的 TextStyle
。您可以创建自己的 TextStyle
,也可以使用 MaterialTheme.typography
检索由主题定义的样式(首选)。此结构支持您访问由 Material 定义的文本样式,例如 displayLarge, headlineMedium, titleSmall, bodyLarge, labelMedium
等。在本例中,您将使用主题中定义的 headlineMedium
样式。
通常来说,最好是将颜色、形状和字体样式放在 MaterialTheme
中。例如,如果对颜色进行硬编码,将会很难实现深色模式,并且需要进行大量修正工作,而这很容易造成错误。
不过,有时除了选择颜色和字体样式,您还可以基于现有的颜色或样式进行设置。
为此,您可以使用 copy
函数修改预定义的样式。将数字加粗:
kotlin
import androidx.compose.ui.text.font.FontWeight
// ...
Text(
text = name,
style = MaterialTheme.typography.headlineMedium.copy(
fontWeight = FontWeight.ExtraBold
)
)
这样一来,如果您需要更改 headlineMedium
的字体系列或其他任何属性,就不必担心出现细微偏差了。
设置深色模式预览
目前,我们的预览仅会显示应用在浅色模式下的显示效果。使用 UI_MODE_NIGHT_YES
向 GreetingPreview
添加额外的 @Preview
注解:
kotlin
import android.content.res.Configuration.UI_MODE_NIGHT_YES
@Preview(
showBackground = true,
widthDp = 320,
uiMode = UI_MODE_NIGHT_YES,
name = "GreetingPreviewDark"
)
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greetings()
}
}
微调应用的主题
您可以在 ui/theme
文件夹内的文件中找到与当前主题相关的所有内容。例如,我们到目前为止所使用的默认颜色均在 Color.kt
中定义。
您的预览将默认使用动态配色。您可以在 Theme.kt
中查看使用 dynamicColor
布尔值参数添加动态配色的逻辑。
如需查看非自适应版本的配色方案,请在 API 级别低于 31(对应引入了自适应配色的 Android S)的设备上运行您的应用。
13. 收尾!
用图标替换按钮
- 将
IconButton
可组合项与子级Icon
结合使用。 - 使用
material-icons-extended
制品中提供的Icons.Filled.ExpandLess
和Icons.Filled.ExpandMore
。将以下代码行添加到app/build.gradle.kts
文件中的依赖项中。
gradle
implementation("androidx.compose.material:material-icons-extended")
- 修改内边距以修正对齐问题。
- 为无障碍功能添加内容说明。
使用字符串资源
应该为"Show more"和"show less"提供内容说明,您可以通过简单的 if
语句进行添加:
kotlin
contentDescription = if (expanded) "Show less" else "Show more"
不过,硬编码字符串的方式并不可取,应该从 strings.xml
文件中获取字符串。
您可以通过对每个字符串使用"Extract string resource"(在 Android Studio 中的"Context Actions"中提供)来自动执行此操作。
或者,打开 app/src/res/values/strings.xml
并添加以下资源:
xml
<string name="show_less">Show less</string>
<string name="show_more">Show more</string>
展开
"Composem ipsum"文字会在显示后消失,触发每张卡片的大小变化。
- 将新的
Text
添加到Greeting
中当内容展开时显示的 Column 中。 - 移除
extraPadding
并改为将animateContentSize
修饰符应用于Row
。这会自动执行创建动画的过程,而手动执行该过程会很困难。此外,也不需要再使用coerceAtLeast
。
添加高度和形状
- 您可以结合使用
shadow
修饰符和clip
修饰符来实现卡片外观。不过,有一种 Material 可组合项也可以做到这一点:Card
。您可以通过调用CardDefaults.cardColors
并覆盖想要更改的颜色,以此来更改Card
的颜色。
14. 最终源码
kotlin
package com.example.basicscodelab
import android.content.res.Configuration
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.coerceAtLeast
import androidx.compose.ui.unit.dp
import com.example.basicscodelab.ui.theme.BasicsCodelabTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
BasicsCodelabTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
MyApp(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
)
}
}
}
}
}
@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
Surface(modifier, color = MaterialTheme.colorScheme.background) {
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
}
@Composable
fun OnboardingScreen(
modifier: Modifier = Modifier,
onContinueClicked: () -> Unit
) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Welcome to the Basics Codelab!")
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = {
onContinueClicked.invoke()
}
) {
Text(text = "Continue")
}
}
}
@Composable
private fun Greetings(
modifier: Modifier = Modifier,
names: List<String> = List(1000) { "$it" }
) {
LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
items(items = names) { name ->
Greeting(name = name)
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Card(
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primary),
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
CardContent(name)
}
}
@Composable
private fun CardContent(name: String) {
var expanded by rememberSaveable { mutableStateOf(false) }
Row(
modifier = Modifier
.padding(12.dp)
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
) {
Column(
modifier = Modifier
.weight(1f)
.padding(12.dp)
) {
Text(
text = "Hello",
)
Text(
text = "$name", style = MaterialTheme.typography.headlineMedium.copy(
fontWeight = FontWeight.ExtraBold
)
)
if (expanded) {
Text(
text = ("Composem ipsum color sit lazy, " +
"padding theme elit, sed do bouncy. ").repeat(4)
)
}
}
IconButton(onClick = {
expanded = !expanded
}) {
Icon(
imageVector = if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
contentDescription = if (expanded) {
stringResource(R.string.show_less)
} else {
stringResource(R.string.show_more)
}
)
}
}
}
@Preview
@Composable
fun MyAppPreview() {
BasicsCodelabTheme {
MyApp(Modifier.fillMaxSize())
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {})
}
}
@Preview(
showBackground = true,
widthDp = 320,
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "GreetingsPreviewDark"
)
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingsPreview() {
BasicsCodelabTheme {
Greetings()
}
}