Compose 学习

布局组件

Jetpack Compose 中的布局是通过组合函数和组件来构建的,而不是使用传统的 XML 布局文件。Compose 提供了一系列用于构建用户界面的组件和布局函数。以下是一些常见的 Jetpack Compose 布局相关的内容:

Column

Column 是 Jetpack Compose 中用于垂直排列子元素的布局组件。它允许你按照从上到下的顺序垂直排列各个子元素。以下是一些关于 Column 的详细信息和使用示例:

创建 Column:

你可以通过简单地在 Column 中放置子元素来创建垂直布局。

1
2
3
4
5
Column {
// 子元素 1
// 子元素 2
// ...
}

使用 Modifier 定制 Column:

你可以使用 ModifierColumn 进行定制,例如设置大小、填充等。

1
2
3
4
5
6
7
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// 子元素
}

子元素的定位和对齐:

可以使用 verticalArrangementhorizontalAlignment 参数来调整子元素在 Column 中的垂直和水平排列方式。

1
2
3
4
5
6
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
// 子元素
}

设置子元素之间的间距:

你可以使用 Modifier.paddingColumn 中的子元素设置间距。

1
2
3
4
5
6
7
Column(
modifier = Modifier.padding(8.dp)
) {
// 子元素 1
// 子元素 2
// ...
}

使用 weight 分配空间:

你可以使用 weight 参数来为子元素分配相对空间,实现灵活的布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Column {
Box(
modifier = Modifier
.height(100.dp)
.fillMaxWidth()
.background(Color.Blue)
.weight(1f) // 占据剩余空间的一半
) {
// 子元素 1
}
Box(
modifier = Modifier
.height(100.dp)
.fillMaxWidth()
.background(Color.Red)
.weight(2f) // 占据剩余空间的两倍
) {
// 子元素 2
}
}

Row

Row 是基本的水平布局组件。它们允许您按水平顺序排列其子组件。

基本使用:

1
2
3
4
5
6
Row {
// Row 中的每个子元素
Text("Element 1")
Text("Element 2")
Text("Element 3")
}

在这个简单的示例中,Row 包含了三个 Text 组件,它们会水平排列在一行中。

使用 Modifier 调整布局:

1
2
3
4
5
6
7
8
9
10
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
// Row 中的每个子元素
Text("Element 1")
Text("Element 2")
Text("Element 3")
}

通过使用 Modifier,你可以调整 Row 的外观和行为。在这个例子中,Modifier.fillMaxWidth() 使得 Row 的宽度充满父容器的宽度,而 Modifier.padding(16.dp) 添加了内边距。

对齐和垂直对齐:

1
2
3
4
5
6
7
8
9
10
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
// Row 中的每个子元素
Text("Element 1")
Text("Element 2")
Text("Element 3")
}

通过使用 horizontalArrangementverticalAlignment 参数,你可以控制子元素在水平和垂直方向上的排列方式。在这个例子中,Arrangement.SpaceBetween 使得子元素在水平方向上平均分布,并且 Alignment.CenterVertically 使得它们在垂直方向上居中对齐。

使用 Spacer 创建间隔:

1
2
3
4
5
6
7
Row {
Text("Element 1")
Spacer(modifier = Modifier.width(16.dp))
Text("Element 2")
Spacer(modifier = Modifier.width(16.dp))
Text("Element 3")
}

Spacer 可以用来创建子元素之间的间隔。在这个例子中,每个 Text 元素之间都有一个宽度为 16.dp 的间隔。

使用 weight 分配空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Row {
Box(
modifier = Modifier
.height(50.dp)
.fillMaxWidth()
.background(Color.Blue)
.weight(1f) // 占据剩余空间的一半
) {
// 子元素 1
}
Box(
modifier = Modifier
.height(50.dp)
.fillMaxWidth()
.background(Color.Red)
.weight(2f) // 占据剩余空间的两倍
) {
// 子元素 2
}
}

通过使用 weight 参数,你可以为 Row 中的子元素分配相对空间,实现灵活的布局。

总体来说,Row 是 Jetpack Compose 中用于水平排列子元素的常用布局组件,它提供了丰富的参数和选项,使得在水平方向上创建灵活的界面布局变得简单而直观。

Box

Box 是 Jetpack Compose 中用于创建容器的组件,它可以包含其他组件,并允许你对这些组件进行定位、叠加和对齐。以下是一些关于 Box 的详细信息和使用示例:

基本使用:

1
2
3
4
5
6
7
8
9
10
@Composable
fun BasicBoxExample() {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.primary)
) {
// Box 中的内容
}
}

在这个基本的示例中,Box 组件充满整个父容器的大小,并设置了背景颜色为 MaterialTheme.colorScheme.primary

包含其他组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Composable
fun BoxWithContentExample() {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.primary)
) {
// Box 中包含其他组件
Text(
text = "Hello, Box!",
modifier = Modifier
.size(200.dp)
.background(Color.White)
)
}
}

在这个例子中,Box 中包含了一个 Text 组件,并设置了 sizebackground 属性。

对齐和定位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Composable
fun BoxAlignmentExample() {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.primary)
) {
// 对齐和定位子元素
Box(
modifier = Modifier
.size(100.dp)
.background(Color.White)
.offset(50.dp, 30.dp)
) {
// 子元素
}
}
}

在这个例子中,Box 包含一个子 Box,并使用 offset 属性对其进行定位。

叠加多个组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Composable
fun OverlayExample() {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.primary)
) {
// 叠加多个组件
Text(
text = "Text 1",
modifier = Modifier
.size(200.dp)
.background(Color.White)
)
Text(
text = "Text 2",
modifier = Modifier
.size(150.dp)
.background(Color.Red)
)
}
}

在这个例子中,Box 中叠加了两个 Text 组件,它们分别有不同的背景颜色和大小。

Box 是一个非常灵活的容器,可以用于创建各种布局结构,包括对齐、定位和叠加多个组件。通过使用不同的修饰符和嵌套不同的组件,你可以轻松地实现各种复杂的界面布局。

ConstraintLayout

ConstraintLayout 提供了更灵活的布局,允许您通过设置约束条件来确定子组件的位置。

1
2
3
ConstraintLayout {
// 子组件和约束条件
}

Surface

在 Jetpack Compose 中,Surface 是一个用于绘制内容的基本容器。它可以用作背景,也可以包含其他组件,以形成更复杂的用户界面。以下是一些关于 Surface 的详细信息和使用示例:

基本使用:

主要还是在内部只要一个元素的时候使用,这样就可以不用像 Card 一样需要再加上 Column 来添加背景颜色

1
2
3
4
5
6
7
8
9
10
11
12
Surface(
shape = RoundedCornerShape(50.dp),
color = colorResource(id = R.color.danDanZi), // 设置圆角
) {
Text(
text = "学习",
color = colorResource(id = R.color.zi),
modifier = Modifier
.padding(horizontal = 10.dp)
.padding(vertical = 4.dp)
)
}

在这个例子中,Surface 用作一个背景,填充整个可用空间,并设置了填充和颜色属性。Text 组件位于 Surface 中,形成了一个简单的界面。

Surface 嵌套使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Surface(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
color = Color.Blue
) {
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
color = Color.Green
) {
// 内部 Surface 中的内容
Text("Nested Surface Content", color = Color.White)
}
}

Surface 组件可以嵌套使用,形成更为复杂的界面层次结构。在这个例子中,外部 Surface 设置为蓝色,内部 Surface 设置为绿色,并包含一个文本内容。

使用 Material 风格的 Surface:

1
2
3
4
5
6
7
8
9
Surface(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
color = MaterialTheme.colorScheme.primary
) {
// Material 风格的 Surface 中的内容
Text("Material Surface Content", color = Color.White)
}

当你在应用中使用 Material Design 风格时,可以直接使用 MaterialTheme.colorScheme 中定义的颜色。

添加边框和形状:

1
2
3
4
5
6
7
8
9
10
11
Surface(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
color = Color.Blue,
shape = RoundedCornerShape(8.dp),
border = BorderStroke(2.dp, Color.Gray)
) {
// Surface 中的内容
Text("Surface with Border and Shape", color = Color.White)
}

通过设置 shape 属性,可以为 Surface 指定不同的形状,例如圆角。通过设置 border 属性,可以为 Surface 添加边框。

自定义绘制内容:

1
2
3
4
5
6
7
8
9
10
Surface(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
color = Color.Blue,
shape = CircleShape
) {
// 自定义绘制的内容
DrawCircle(color = Color.Yellow)
}

你还可以使用 Draw 函数来在 Surface 中自定义绘制内容。在这个例子中,通过 DrawCircle 函数绘制了一个黄色的圆形。

总体来说,Surface 是 Jetpack Compose 中用于绘制内容和创建容器的基础组件之一。它提供了许多属性,允许你设置背景颜色、形状、边框等,以实现各种界面样式。

Spacer

Spacer 是一个用于添加空白间隔的组件,可以在布局中调整组件之间的距离。

1
Spacer(modifier = Modifier.height(16.dp))

LazyColumn

LazyColumn 是 Jetpack Compose 中用于显示垂直滚动列表的组件。与传统的 RecyclerView 不同,LazyColumn 是一种懒加载的方式,仅在需要时才会加载和绘制可见的项目,这有助于提高性能。以下是一些关于 LazyColumn 的详细信息和使用示例:

基本使用:

1
2
3
4
5
6
LazyColumn {
items(100) { index ->
// 列表中的每个项目
Text("Item $index")
}
}

在这个简单的示例中,LazyColumn 包含了一个包含 100 个项目的列表。items 函数用于定义列表的项目,它接受一个项目总数和一个 lambda 表达式,用于定义每个项目的内容。

懒加载:

LazyColumn 仅在需要时才会加载和绘制可见的项目。这意味着当你滚动列表时,只有当前可见的项目才会被创建和渲染,这有助于提高性能,特别是当列表中有大量项目时。

项目的交互性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LazyColumn {
items(10) { index ->
// 列表中的每个项目
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clickable {
// 处理项目的点击事件
// 可以进行页面导航等操作
// 例如:navController.navigate("detail/$index")
}
) {
Text("Item $index", fontWeight = FontWeight.Bold)
}
}
}

在这个例子中,每个项目被包装在一个 Row 中,并添加了 clickable 修饰符,以便处理项目的点击事件。你可以在 clickable lambda 中执行任何你想要的操作,例如页面导航。

使用不同类型的项目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
data class ListItem(val title: String, val subtitle: String)

LazyColumn {
items(
listOf(
ListItem("Item 1", "Subtitle 1"),
ListItem("Item 2", "Subtitle 2"),
// ...
)
) { item ->
// 使用不同类型的项目
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(text = item.title, fontWeight = FontWeight.Bold)
Text(text = item.subtitle, color = Color.Gray)
}
}
}

在这个例子中,每个项目是一个包含标题和子标题的数据类 ListItemitems 函数接受一个包含不同类型项目的列表,并根据每个项目的类型来定义不同的界面。

分隔线:

1
2
3
4
5
6
7
LazyColumn {
items(10) { index ->
// 列表中的每个项目
Text("Item $index")
Divider(color = Color.Gray, thickness = 1.dp)
}
}

通过在每个项目之后添加 Divider 组件,可以在列表中添加分隔线。

LazyColumn 是构建垂直滚动列表的常用组件,特别适用于大量数据的情况,因为它采用懒加载的方式,只加载当前可见的项目,从而提高性能。

ScrollableColumn

允许在超过屏幕空间的情况下滚动显示内容。

1
2
3
ScrollableColumn {
// 可滚动的垂直列
}

ScrollableRow

1
2
3
ScrollableRow {
// 可滚动的水平行
}

Scaffold

Scaffold 是 Jetpack Compose 中用于创建应用程序屏幕结构的组件。它提供了一个标准的应用程序结构,包括应用栏(TopAppBar)、底部导航栏(BottomAppBar)以及主要内容区域。以下是一些关于 Scaffold 的详细信息和使用示例:

基本使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Composable
fun BasicScaffoldExample() {
Scaffold(
topBar = {
// 顶部应用栏
TopAppBar(
title = { Text(text = "My App") },
navigationIcon = {
IconButton(onClick = { /* Handle navigation icon click */ }) {
Icon(Icons.Default.Menu, contentDescription = null)
}
},
actions = {
// 顶部应用栏的操作按钮
IconButton(onClick = { /* Handle action click */ }) {
Icon(Icons.Default.Favorite, contentDescription = null)
}
}
)
},
content = {
// 主要内容区域
Text(text = "Hello, Compose!")
},
bottomBar = {
// 底部导航栏
BottomAppBar(
content = {
// 底部导航栏的内容
IconButton(onClick = { /* Handle bottom bar action click */ }) {
Icon(Icons.Default.Home, contentDescription = null)
}
},
backgroundColor = MaterialTheme.colorScheme.primary
)
},
floatingActionButton = {
// 浮动操作按钮
FloatingActionButton(
onClick = { /* Handle FAB click */ },
contentColor = MaterialTheme.colorScheme.primary
) {
Icon(Icons.Default.Add, contentDescription = null)
}
},
isFloatingActionButtonDocked = true
)
}

在这个基本的示例中,Scaffold 包含了一个顶部应用栏、主要内容区域、底部导航栏和一个浮动操作按钮。你可以通过在相应的参数中设置不同的组件和操作来自定义 Scaffold

自定义主题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Composable
fun CustomThemedScaffold() {
MaterialTheme(
colorScheme = ColorScheme(
primary = Color.Green,
secondary = Color.Yellow,
background = Color.Gray,
surface = Color.DarkGray,
onPrimary = Color.Black,
onSecondary = Color.Black,
onBackground = Color.White,
onSurface = Color.White
)
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = "Custom Theme") },
backgroundColor = MaterialTheme.colorScheme.primary
)
},
content = {
Text(text = "Hello, Compose!", color = MaterialTheme.colorScheme.onBackground)
},
bottomBar = {
BottomAppBar(
content = {
IconButton(onClick = { /* Handle bottom bar action click */ }) {
Icon(Icons.Default.Home, contentDescription = null)
}
},
backgroundColor = MaterialTheme.colorScheme.primary
)
},
floatingActionButton = {
FloatingActionButton(
onClick = { /* Handle FAB click */ },
contentColor = MaterialTheme.colorScheme.primary
) {
Icon(Icons.Default.Add, contentDescription = null)
}
},
isFloatingActionButtonDocked = true
)
}
}

在这个例子中,通过在 MaterialTheme 中设置自定义的颜色方案,可以自定义应用程序的主题。Scaffold 会根据主题中的颜色来渲染应用栏、底部导航栏和浮动操作按钮。

Scaffold 提供了一个简便的方式来组织应用程序的基本结构,使得你可以专注于开发主要内容。你可以通过设置不同的参数和组件来满足应用程序的特定需求。

ModalNavigationDrawer

在 Jetpack Compose 中,ModalDrawer 是一个用于实现模态导航抽屉的组件。模态导航抽屉通常用于在屏幕的一侧显示应用程序的导航菜单。以下是关于 ModalDrawer 的一些详细信息和基本使用示例:

基本使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun ModalDrawerExample() {
var drawerState by rememberDrawerState(DrawerValue.Closed)
var text by remember { mutableStateOf("Text in main content") }

ModalDrawer(
drawerState = drawerState,
gesturesEnabled = drawerState == DrawerValue.Open,
drawerContent = {
// 抽屉内容
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text("Drawer Content")
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
// 关闭抽屉
drawerState = DrawerValue.Closed
}) {
Text("Close Drawer")
}
}
},
content = {
// 主要内容
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(text)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
// 打开抽屉
drawerState = DrawerValue.Open
}) {
Text("Open Drawer")
}
}
}
)
}

@Preview(showBackground = true)
@Composable
fun ModalDrawerExamplePreview() {
ModalDrawerExample()
}

在这个基本的示例中,通过使用 ModalDrawer,在主要内容区域的一侧实现了一个模态抽屉。通过设置 drawerState 来控制抽屉的打开和关闭状态。抽屉的内容通过在 drawerContent 参数中提供的 Column 中定义。

自定义抽屉内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomDrawerContent(
onDrawerClose: () -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text("Custom Drawer Content")
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
// 关闭抽屉
onDrawerClose()
}) {
Text("Close Drawer")
}
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomDrawerExample() {
var drawerState by rememberDrawerState(DrawerValue.Closed)
var text by remember { mutableStateOf("Text in main content") }

ModalDrawer(
drawerState = drawerState,
gesturesEnabled = drawerState == DrawerValue.Open,
drawerContent = {
// 自定义抽屉内容
CustomDrawerContent(onDrawerClose = {
drawerState = DrawerValue.Closed
})
},
content = {
// 主要内容
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(text)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
// 打开抽屉
drawerState = DrawerValue.Open
}) {
Text("Open Drawer")
}
}
}
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Preview(showBackground = true)
@Composable
fun CustomDrawerExamplePreview() {
CustomDrawerExample()
}

在这个例子中,通过定义一个自定义的抽屉内容 CustomDrawerContent,使抽屉的内容更具体地展示了自定义的 UI 元素。在 drawerContent 参数中传递 CustomDrawerContent,并在按钮的点击事件中关闭抽屉。

ModalDrawer 提供了简便的方式来实现模态抽屉,它可以灵活地适应各种应用程序的需求。通过在 drawerContent 中使用 Compose 的布局和组件,你可以轻松定制抽屉的外观和功能。

基础组件

Icon

使用 painterResource 来获取相关的资源,contentDescription是无障碍中对于 Icon 的描述

以下是一些基本的 Icon 使用示例:

1
2
3
4
5
6
7
8
Icon(imageVector = ImageVector.vectorResource(
id = R.drawable.ic_svg, contentDescription = "矢量图资源")

Icon(bitmap = ImageBitmap.imageResource(
id = R.drawable.ic_png), contentDescription = "图片资源")

Icon(painter = painterResource(
id = R.drawable.ic_both), contentDescription = "任意类型资源")

Image

在 Jetpack Compose 中,Image 是用于显示图像的组件。它可以显示来自不同来源的图像,例如矢量图形、位图、网络图像等。以下是一些关于 Image 的详细信息和使用示例:

显示本地资源图像:

1
2
3
4
5
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "App Icon",
modifier = Modifier.size(100.dp)
)

这个例子中,painterResource 函数用于从本地资源中加载图像,contentDescription 提供了对图像的文本描述,而 modifier 则用于设置图像的大小。

显示网络图像:

1
2
3
4
5
6
7
8
9
10
11
12
Image(
painter = rememberImagePainter(
data = "https://example.com/image.jpg",
builder = {
crossfade(true)
placeholder(R.drawable.placeholder)
error(R.drawable.error)
}
),
contentDescription = "Network Image",
modifier = Modifier.size(200.dp)
)

在这个例子中,rememberImagePainter 函数用于从网络加载图像。它支持一些额外的选项,如交叉淡入淡出效果、占位符和错误图像。这样的构建器提供了更多的控制权。

使用不同的 ContentScale:

1
2
3
4
5
6
Image(
painter = painterResource(id = R.drawable.image),
contentDescription = "Scaled Image",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth().height(200.dp)
)

ContentScale 参数用于指定图像的缩放方式。ContentScale.Crop 会裁剪图像以填充目标框,而 ContentScale.FillBounds 会将图像缩放以填充目标框,保持其宽高比。

使用不同的加载方式:

1
2
3
4
5
Image(
painter = rememberCoilPainter(request = "https://example.com/image.jpg"),
contentDescription = "Coil Image",
modifier = Modifier.size(150.dp)
)

在这个例子中,rememberCoilPainter 函数用于从网络加载图像,它是 Coil 图片加载库的一部分。Jetpack Compose 与 Coil 集成良好,可以通过 rememberCoilPainter 等函数方便地加载网络图像。

总体而言,Image 是 Jetpack Compose 中用于显示图像的主要组件,它支持各种来源的图像,并提供了多种选项,以满足不同应用场景的需求。

Card

在 Jetpack Compose 中,Card 是用于创建卡片样式的组件。卡片通常用于将相关的内容组织在一起,并提供一种清晰的界面结构。以下是一些关于 Card 的详细信息和使用示例:

基本使用:

注意 card 和 suface 不同的是背景颜色设置在内置的元素,而 suface 需要设置在自己的 color 的属性里面,以及它们两个的布局逻辑也不太一样

注意下面的 elevation 踩坑了,设置之后没有任何的阴影出现,而且发现 elevation 的功能少,只能用黑色阴影,相反 .shadow 能够设置的内容就更多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Card(
// elevation = CardDefaults.cardElevation(defaultElevation = 5.dp), //注意 elevation 的写法,现在好了不用注意了,直接送走这位属性
modifier = Modifier
.padding(top = 20.dp)
.padding(horizontal = 10.dp)
//.clip(shape = MaterialTheme.shapes.large)
.shadow(25.dp,RoundedCornerShape(20.dp))
.clip(shape = RoundedCornerShape(20.dp))
.fillMaxWidth()
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.background(color = colorResource(id = R.color.danHui))
.padding(10.dp)
.fillMaxWidth()
) {

}
}

这个例子中,Card 包裹了一个 Text 组件,通过设置 modifier 可以调整卡片的大小和边距,elevation 参数用于指定卡片的海拔高度,以添加阴影效果。clip 参数实现圆角效果。

卡片与图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
elevation = 8.dp
) {
Box(
modifier = Modifier
.height(200.dp)
.fillMaxWidth()
) {
Image(
painter = painterResource(id = R.drawable.image),
contentDescription = "Card Image",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
Text(
text = "Image Title",
color = Color.White,
fontSize = 20.sp,
modifier = Modifier
.padding(16.dp)
.align(Alignment.BottomStart)
)
}
}

在这个例子中,Card 包含一个带有图片的 Box,通过调整 modifier 设置图片的高度、填充整个宽度。通过在 Box 中添加 Text,可以在图片上叠加标题。

Card 嵌套使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
elevation = 8.dp
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
elevation = 4.dp
) {
// 内部卡片的内容
Text("Nested Card Content")
}
}

Card 组件可以嵌套使用,这样你可以创建更为复杂的布局结构,例如卡片内部包含另一个卡片。

使用点击事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var isCardExpanded by remember { mutableStateOf(false) }

Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clickable { isCardExpanded = !isCardExpanded },
elevation = 8.dp
) {
// 卡片内容
if (isCardExpanded) {
Text("Expanded Card Content")
} else {
Text("Collapsed Card Content")
}
}

通过在 Cardmodifier 中添加 clickable,你可以为卡片添加点击事件。在上面的例子中,点击卡片时切换卡片内容的展开和折叠状态。

总的来说,Card 是 Jetpack Compose 中用于创建卡片样式的主要组件,它提供了许多选项,以满足不同应用场景下的需求,同时可以通过嵌套使用和添加点击事件等方式实现更为复杂的交互和布局。

Text

Text 是 Jetpack Compose 中用于显示文本的组件。它允许你以声明式的方式定义和呈现文本内容。以下是一些关于 Text 的详细信息和使用示例:

基本使用:

1
Text("Hello, Compose!")

在这个基本的示例中,Text 组件显示了一个简单的文本内容:”Hello, Compose!”。

设置文本样式:

1
2
3
4
5
6
Text(
"Styled Text",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.Blue
)

Text 组件允许你设置多种文本样式,包括字体大小 (fontSize)、字体粗细 (fontWeight)、颜色 (color) 等。在这个例子中,文本的字体大小为 20sp,字体粗细为粗体,颜色为蓝色。

使用字符串模板:

1
2
val name = "John"
Text("Hello, $name!")

你可以使用字符串模板来在文本中插入变量。在这个例子中,变量 name 的值会被插入到文本中。

设置对齐方式:

当我们在 Text 中设置了 fillMaxWidth() 之后?,我们可以指定 Text 的对齐方式

1
2
3
4
Text(
"Centered Text",
textAlign = TextAlign.Center
)

通过 textAlign 属性,你可以设置文本的对齐方式。在这个例子中,文本被居中对齐。

多行文本:

1
2
3
4
5
6
Text(
"This is a long text that may span multiple lines. " +
"It demonstrates how to use the Text component for displaying multiline text.",
maxLines = 3,
overflow = TextOverflow.Ellipsis
)

如果文本内容很长,你可以使用 maxLines 属性限制显示的最大行数,并通过 overflow 属性设置溢出文本的处理方式。在这个例子中,文本最多显示 3 行,多余的文本用省略号表示。

使用 AnnotatedString 进行富文本:

也就是一行字的样式不同,我觉得还是手写两个 text 比较好

1
2
3
4
5
6
7
8
9
10
11
val styledText = buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold, color = Color.Red)) {
append("Bold and Red")
}
append(" Regular Text ")
withStyle(style = SpanStyle(textDecoration = TextDecoration.LineThrough)) {
append("Strikethrough")
}
}

Text(text = styledText)

通过使用 AnnotatedString,你可以实现富文本效果。在这个例子中,一部分文本被设置为粗体红色,一部分被设置为带有删除线的文本。

总体来说,Text 是 Jetpack Compose 中用于显示文本的核心组件。它提供了丰富的属性,使得你可以轻松地控制文本的样式、对齐方式等,同时支持富文本效果。

FloatingActionButton

FloatingActionButton 是 Jetpack Compose 中用于创建浮动操作按钮的组件。浮动操作按钮通常用于显示应用中的主要操作,并且通常位于屏幕的底部右侧。以下是一些关于 FloatingActionButton 的详细信息和使用示例:

基本使用:

1
2
3
4
5
6
7
8
9
@Composable
fun BasicFABExample() {
FloatingActionButton(
onClick = { /* 处理点击事件 */ },
contentColor = MaterialTheme.colorScheme.primary
) {
Icon(imageVector = Add, contentDescription = null)
}
}

在这个基本的示例中,FloatingActionButton 包含了一个点击事件的处理程序,并且包含了一个加号图标。

自定义颜色和形状:

1
2
3
4
5
6
7
8
9
10
11
@Composable
fun CustomFABExample() {
FloatingActionButton(
onClick = { /* 处理点击事件 */ },
contentColor = MaterialTheme.colorScheme.secondary,
backgroundColor = MaterialTheme.colorScheme.primary,
shape = MaterialTheme.shapes.medium
) {
Icon(imageVector = Add, contentDescription = null)
}
}

通过 contentColorbackgroundColor 属性,你可以自定义浮动操作按钮的前景和背景颜色。shape 属性用于定义按钮的形状。

在底部应用栏中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Composable
fun FABInBottomAppBarExample() {
BottomAppBar(
fabConfiguration = BottomAppBar.FabConfiguration(
position = FabPosition.End
)
) {
// 底部应用栏的内容
}

FloatingActionButton(
onClick = { /* 处理点击事件 */ },
contentColor = MaterialTheme.colorScheme.primary
) {
Icon(imageVector = Add, contentDescription = null)
}
}

在底部应用栏中使用 FloatingActionButton 时,你可以通过 fabConfiguration 属性设置按钮的位置。在这个例子中,按钮位于底部应用栏的右侧。

使用动画效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Composable
fun AnimatedFABExample() {
val fabTransition = transitionDefinition<Boolean> {
state(false) {
this[translationY] = 0.dp
}
state(true) {
this[translationY] = (-16).dp
}
transition {
translationY using tween(300)
}
}

val fabState by transitionAsState(fabTransition, targetState = true)

Box {
FloatingActionButton(
onClick = { /* 处理点击事件 */ },
contentColor = MaterialTheme.colorScheme.primary,
modifier = Modifier
.translationY(fabState[translationY])
) {
Icon(imageVector = Add, contentDescription = null)
}
}
}

在这个例子中,通过使用 transitionDefinition 和动画效果,你可以在 FloatingActionButton 上应用一个简单的上下动画。

FloatingActionButton 提供了很多自定义的选项,包括颜色、形状、位置等,使得你可以根据你的设计需求轻松定制。在实际应用中,根据设计规范和用户体验需求选择合适的样式和位置。

Divider

Divider 是 Jetpack Compose 中用于创建分隔线的组件。它通常用于在布局中分隔不同的部分或列表项。以下是一些关于 Divider 的详细信息和使用示例:

基本使用:

1
2
3
4
5
6
@Composable
fun BasicDividerExample() {
Divider(
modifier = Modifier.fillMaxWidth()
)
}

在这个基本的示例中,Divider 组件被用于创建一条横跨整个宽度的分隔线。

设置颜色和高度:

1
2
3
4
5
6
7
8
@Composable
fun CustomDividerExample() {
Divider(
modifier = Modifier.fillMaxWidth(),
color = Color.Gray,
thickness = 2.dp
)
}

你可以通过 color 属性设置分隔线的颜色,通过 thickness 属性设置分隔线的厚度。

垂直分隔线:

1
2
3
4
5
6
7
8
9
10
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.material3.Divider
import androidx.compose.runtime.Composable

@Composable
fun VerticalDividerExample() {
Divider(
modifier = Modifier.fillMaxHeight()
)
}

如果你需要垂直的分隔线,可以使用 fillMaxHeight() 修饰符。

自定义分隔线样式:

1
2
3
4
5
6
7
8
9
10
@Composable
fun CustomStyleDividerExample() {
Divider(
modifier = Modifier.fillMaxWidth(),
color = MaterialTheme.colorScheme.secondary,
thickness = 4.dp,
startIndent = 16.dp,
endIndent = 16.dp
)
}

通过设置 startIndentendIndent 属性,你可以指定分隔线的起始和结束缩进。

总体来说,Divider 是一个简单但功能强大的组件,用于在 Compose 中创建分隔线,提供了丰富的属性来满足不同场景下的分隔需求。

CheckBox

CheckBox 是 Jetpack Compose 中用于创建复选框的组件。复选框允许用户选择或取消选择一个或多个项目。以下是一些关于 CheckBox 的详细信息和使用示例:

基本使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Composable
fun BasicCheckBoxExample() {
var checked by remember { mutableStateOf(false) }

Checkbox(
checked = checked,
onCheckedChange = { isChecked ->
checked = isChecked
},
modifier = Modifier.padding(16.dp),
colors = CheckboxDefaults.colors(checkmarkColor = MaterialTheme.colorScheme.secondary)
)
}

在这个基本的示例中,Checkbox 组件用于创建一个基本的复选框。通过 checked 属性和 onCheckedChange 回调,你可以控制复选框的状态。

使用样式和颜色:

1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun StyledCheckBoxExample() {
Checkbox(
checked = true,
onCheckedChange = { /* Handle checked state */ },
modifier = Modifier.padding(16.dp),
colors = CheckboxDefaults.colors(
checkedColor = MaterialTheme.colorScheme.secondary,
uncheckedColor = MaterialTheme.colorScheme.primary
)
)
}

通过使用 colors 属性,你可以自定义复选框的颜色,包括选中状态和未选中状态的颜色。

禁用状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Composable
fun DisabledCheckBoxExample() {
Checkbox(
checked = false,
onCheckedChange = { /* Handle checked state */ },
enabled = false,
modifier = Modifier.padding(16.dp),
colors = CheckboxDefaults.colors(
checkedColor = MaterialTheme.colorScheme.secondary,
uncheckedColor = MaterialTheme.colorScheme.primary
)
)
}

通过设置 enabled 属性为 false,你可以禁用复选框,使其变为不可点击状态。

配合文本使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Composable
fun CheckBoxWithTextExample() {
var checked by remember { mutableStateOf(false) }

Row(
modifier = Modifier.clickable {
checked = !checked
}
) {
Checkbox(
checked = checked,
onCheckedChange = { isChecked ->
checked = isChecked
},
modifier = Modifier.padding(16.dp),
colors = CheckboxDefaults.colors(checkmarkColor = MaterialTheme.colorScheme.secondary)
)
Text(text = "Agree to terms and conditions", modifier = Modifier.padding(16.dp))
}
}

在这个例子中,CheckboxText 组件一起使用,创建了一个带有文本标签的复选框。通过在外部容器上使用 clickable 修饰符,你可以使整个行可点击。

这些示例展示了 Checkbox 在 Jetpack Compose 中的基本用法和一些自定义选项。你可以根据具体的需求来配置颜色、样式以及与其他组件的结合使用。

AnimatedVisibility

在 Jetpack Compose 中,使用动画效果可以为用户界面增添生动感和交互性。以下是一个简单的例子,演示如何使用动画效果来创建一个基本的淡入淡出效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Composable
fun AnimatedExample() {
var visible by remember { mutableStateOf(true) }

Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// 切换按钮
Button(
onClick = { visible = !visible },
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Text(if (visible) "Hide" else "Show")
}

// 使用动画效果的图标
AnimatedVisibility(visible = visible) {
Icon(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = null,
modifier = Modifier
.size(100.dp)
.padding(8.dp)
.graphicsLayer(
alpha = if (visible) 1f else 0f, // 透明度动画
scaleX = if (visible) 1f else 0.5f, // X轴缩放动画
scaleY = if (visible) 1f else 0.5f // Y轴缩放动画
)
)
}

// 使用动画效果的文本
AnimatedVisibility(
visible = visible,
enter = fadeIn() + slideInVertically(),
exit = fadeOut() + slideOutVertically()
) {
Text(
text = "Hello, Compose!",
style = MaterialTheme.typography.h5,
modifier = Modifier
.padding(8.dp)
.background(Color.Gray, RoundedCornerShape(8.dp))
.padding(16.dp)
)
}
}
}

在这个例子中,通过 AnimatedVisibility 自定义了一个带有动画效果的可见性组件,并使用了一些基本的动画效果函数,如 fadeIn()fadeOut()slideInVertically()slideOutVertically()expandIn()shrinkOut()

这个例子包含了以下动画效果:

  • 图标的淡入淡出和缩放效果。
  • 文本的淡入淡出、上下滑动和伸缩效果。

你可以根据自己的需求使用不同的动画效果函数,也可以创建自定义的动画效果来实现更复杂的交互效果。Jetpack Compose 提供了丰富的动画支持,让你能够轻松地为应用添加生动和引人注目的用户界面。

CircularProgressIndicator

CircularProgressIndicator 是 Jetpack Compose 中用于显示圆形进度指示器的组件。它通常用于表示某个任务正在进行中。以下是一些关于 CircularProgressIndicator 的详细信息和使用示例:

基本使用:

1
2
3
4
5
6
7
8
9
@Composable
fun BasicCircularProgressIndicator() {
CircularProgressIndicator(
modifier = Modifier
.size(50.dp)
.padding(16.dp),
color = MaterialTheme.colorScheme.primary
)
}

在这个基本的示例中,CircularProgressIndicator 显示一个默认样式的圆形进度指示器。通过设置 modifier 属性,你可以调整指示器的大小和位置,通过设置 color 属性,你可以自定义指示器的颜色。

指定进度值:

1
2
3
4
5
6
7
8
9
10
@Composable
fun DeterminateCircularProgressIndicator(progress: Float) {
CircularProgressIndicator(
progress = progress,
modifier = Modifier
.size(50.dp)
.padding(16.dp),
color = MaterialTheme.colorScheme.primary
)
}

如果你希望显示特定进度的圆形进度指示器,可以使用 progress 属性。该属性接受一个介于 0 到 1 之间的浮点数,表示进度的百分比。

1
2
// 在 Composable 中使用
DeterminanteCircularProgressIndicator(progress = 0.5f)

不确定进度:

1
2
3
4
5
6
7
8
9
10
@Composable
fun IndeterminateCircularProgressIndicator() {
CircularProgressIndicator(
modifier = Modifier
.size(50.dp)
.padding(16.dp),
color = MaterialTheme.colorScheme.primary,
isIndeterminate = true
)
}

如果你不知道任务的确切进度,可以将 isIndeterminate 属性设置为 true,以显示不确定进度的圆形进度指示器。这时,progress 属性将被忽略。

自定义样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Composable
fun CustomCircularProgressIndicator() {
Surface(
modifier = Modifier
.fillMaxSize()
.background(Color.Gray),
contentColor = Color.White
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator(
modifier = Modifier
.size(50.dp)
.padding(16.dp),
color = MaterialTheme.colorScheme.secondary
)
CircularProgressIndicator(
modifier = Modifier
.size(50.dp)
.padding(16.dp),
color = MaterialTheme.colorScheme.secondary,
progress = 0.5f
)
CircularProgressIndicator(
modifier = Modifier
.size(50.dp)
.padding(16.dp),
color = MaterialTheme.colorScheme.secondary,
isIndeterminate = true
)
}
}
}

在这个例子中,通过设置不同的 colorprogressisIndeterminate 属性,展示了不同样式的圆形进度指示器。

CircularProgressIndicator 是一个非常灵活的组件,可以根据需求进行定制。你可以调整大小、颜色、进度和样式,以满足设计和用户体验的要求。

基础属性

Modifier

在Jetpack Compose中,Modifier 是一种用于修改组件(如布局或绘制元素)行为或外观的强大工具。通过 Modifier,你可以指定诸如大小、颜色、填充、点击处理等属性,从而定制组件的外观和交互。

以下是一些常见的 Modifier 的使用示例:

  1. 设置大小:

    1
    Modifier.size(50.dp)

    这将组件的大小设置为 50.dp

  2. 设置边距:

    在 Compose 中,Margin 和 Padding 都用 Modifier.padding() 来设置。在 padding 和 margin 的区别最主要的就是在于背景色的设置。

    边距需要注意,compose 并不存在 margin 属性 ,当只使用一个 padding 时,这个padding就是外边距,在background 后面的 padding 会作为内布局

    并且一定要写在height和width前面不然会不起作用

    1
    2
    Modifier.padding(16.dp)
    Modifier.padding(vertical = 16.dp)

    这将在组件周围添加 16.dp 的内边距以及竖直方向添加 16.dp

  3. 设置颜色:

    1
    Modifier.background(colorResource(id = R.color.zi))

    这将为组件设置蓝色的背景。

  4. 设置点击处理:

    1
    Modifier.clickable { /* 处理点击事件的逻辑 */ }

    这将使组件可点击,并在点击时执行指定的逻辑。

  5. 设置居中对齐:

    1
    Modifier.align(Alignment.Center)

    这将使组件在其父组件中居中对齐。

  6. 设置边框:

    1
    Modifier.border(1.dp, Color.Gray)

    这将为组件添加一个 1.dp 宽度、灰色的边框。

  7. 设置偏移:

    在 Jetpack Compose 中,Offset 是一个用于表示二维平面上的偏移量的数据类。它通常用于指定在屏幕上或在父组件内的位置。Offset 有两个属性,分别是 xy,分别表示水平和垂直方向上的偏移。

    以下是一个简单的示例,演示如何在 Compose 中使用 Offset

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Composable
    fun OffsetExample() {
    Box(
    modifier = Modifier
    .size(100.dp)
    .background(MaterialTheme.colorScheme.primary)
    .offset(50.dp, 30.dp)
    ) {
    // Box 中的内容
    }
    }

    在这个例子中,Box 组件的 offset 属性被用来指定这个 Box 相对于其正常位置的偏移量。在这里,x 设置为 50.dp,表示向右偏移 50 个设备独立像素(Density Independent Pixels,dp),而 y 设置为 30.dp,表示向下偏移 30 个 dp。

    需要注意的是,在 Compose 中使用 offset 可以接受两个参数,分别是水平方向和垂直方向的偏移量。这使得在组合中轻松实现元素的相对位置调整成为可能。

    如果你需要在不同的屏幕密度下使用像素而不是 dp,你可以使用 with(LocalDensity.current) 来获取当前的 Density 并进行转换:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Composable
    fun OffsetWithPixelExample() {
    val density = LocalDensity.current.density
    val offsetX = with(LocalDensity.current) { 50.dp.toPx() / density }
    val offsetY = with(LocalDensity.current) { 30.dp.toPx() / density }

    Box(
    modifier = Modifier
    .size(100.dp)
    .background(MaterialTheme.colorScheme.primary)
    .offset(offsetX.dp, offsetY.dp)
    ) {
    // Box 中的内容
    }
    }

    在这个例子中,offsetXoffsetY 表示在像素中的偏移量,而 toPx() 函数用于将 dp 转换为像素。在设置 offset 时,使用 dp 单位即可。

  8. 设置占比:

    Modifier.weight 是用于在Jetpack Compose中分配相对空间的功能。它主要用于 ColumnRow 这样的布局组件中,允许你指定子元素在可用空间中的相对权重。这样,你可以创建动态的、响应式的布局,使其中一个子元素占据更多或更少的空间。

    使用示例:

    以下是一个简单的示例,演示如何在 Column 中使用 Modifier.weight

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    Column {
    Box(
    modifier = Modifier
    .height(100.dp)
    .fillMaxWidth()
    .background(Color.Blue)
    .weight(1f) // 子元素 1 占据剩余空间的一半
    ) {
    // 子元素 1
    }
    Box(
    modifier = Modifier
    .height(100.dp)
    .fillMaxWidth()
    .background(Color.Red)
    .weight(2f) // 子元素 2 占据剩余空间的两倍
    ) {
    // 子元素 2
    }
    }

    在这个例子中,Column 包含两个 Box,通过 weight 设置它们的相对权重。第一个 Box 的权重为1,第二个 Box 的权重为2,因此第二个 Box 将占据比第一个 Box 更多的空间。

    适用场景:

    • 动态布局: 在需要根据数据或屏幕尺寸动态调整子元素大小的情况下,使用 Modifier.weight 是很有用的。

    • 比例分配: 当你希望在父布局中以一定比例分配空间给子元素时,可以使用权重来定义这些比例。

    • 响应式设计: 在设计响应式用户界面时,Modifier.weight 可以帮助你实现不同屏幕尺寸下的灵活布局。

  9. 设置圆角:

    1
    Modifier.clip(RoundedCornerShape(16.dp))

    这将为组件添加一个 16.dp 的圆角。

  10. 设置其他自定义属性:

1
2
3
4
Modifier
.background(Color.Yellow)
.padding(8.dp)
.size(100.dp)

这个例子演示了如何通过链式调用多个 Modifier 方法来设置多个属性。

LaunchedEffect

LaunchedEffect 是 Jetpack Compose 中用于启动协程并在协程完成时执行操作的效果(effect)。它通常用于执行一次性的异步任务,例如启动网络请求、执行动画等。以下是一些关于 LaunchedEffect 的详细信息和使用示例:

基本使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun LaunchedEffectExample() {
var isLoading by remember { mutableStateOf(false) }
var result by remember { mutableStateOf("") }

val context = LocalContext.current

Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// 输入框
OutlinedTextField(
value = result,
onValueChange = {
result = it
},
label = { Text("Type something...") },
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
// 启动协程执行异步任务
LaunchedEffect(Unit) {
isLoading = true
delay(2000) // 模拟异步任务,比如网络请求
result = "Task completed"
isLoading = false
}
}
),
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
)

// 显示结果或加载指示器
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier
.size(50.dp)
.padding(16.dp),
color = MaterialTheme.colorScheme.primary
)
} else {
Spacer(modifier = Modifier.height(16.dp))
Text(text = result)
}
}
}

在这个例子中,LaunchedEffect 用于在用户完成输入并点击键盘上的 “Done” 按钮时启动一个异步任务。在任务执行期间,显示一个加载指示器。一旦任务完成,显示任务结果。

使用 LaunchedEffect 处理生命周期事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun LaunchedEffectWithLifecycleExample() {
var result by remember { mutableStateOf("") }

val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current

// 使用 LaunchedEffect 处理生命周期事件
LaunchedEffect(lifecycleOwner.lifecycle) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_STOP) {
// 在 Activity 停止时执行的异步任务
result = "Activity stopped"
}
}

lifecycleOwner.lifecycle.addObserver(observer)

// 在 LaunchedEffect 结束时移除生命周期观察者
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}

Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// 显示结果
Spacer(modifier = Modifier.height(16.dp))
Text(text = result)
}
}

在这个例子中,LaunchedEffect 用于处理 Lifecycle.Event.ON_STOP 事件,当 Activity 停止时,

注解

@Composable

在Jetpack Compose中,@Composable是一个注解,用于标记函数或方法,表示这是一个可组合的函数。可组合函数是Compose框架中的基本构建块,用于定义UI界面的一部分。使用@Composable注解的函数被称为可组合函数,因为它们可以组合在一起,形成复杂的用户界面。

使用@Composable注解有几个关键的作用:

  1. 声明可组合函数: 通过使用@Composable注解,您告诉Compose框架这个函数是一个可组合的函数,它用于构建UI组件。Compose框架会根据需要调用这些可组合函数来构建UI。

    1
    2
    3
    4
    @Composable
    fun MyComposableFunction() {
    // UI 构建逻辑
    }
  2. 自动更新UI: 当Compose框架检测到可组合函数内部状态的变化时,它会重新调用相应的可组合函数以更新UI。这使得在Compose中构建动态UI变得简单,因为您只需要管理数据的变化,而不必手动操作UI元素。

  3. 组合性: 可组合函数可以嵌套和组合在一起,形成更复杂的UI层次结构。这种组合性是Compose框架的一个重要特征,允许您构建具有高度可复用性和模块化的UI组件。

    1
    2
    3
    4
    5
    6
    7
    @Composable
    fun MyScreen() {
    // 组合多个可组合函数
    MyHeader()
    MyContent()
    MyFooter()
    }

总的来说,@Composable注解是Compose框架的核心概念之一,它标志着函数是用于构建UI的可组合函数,并且具有自动更新UI和高度组合性的特性。

@DrawableRes

在Jetpack Compose中,@DrawableRes 注解通常用于标记一个参数应该接受一个 Drawable 资源的引用。Drawable 资源是 Android 中用于表示可绘制图形的资源,例如图像、形状等。在 Jetpack Compose 中,你可以使用 @DrawableRes 注解来明确指定某个参数应该接受一个指向 Drawable 资源的引用。

例如,考虑以下示例:

1
2
3
4
5
6
7
@Composable
fun MyImage(@DrawableRes imageResId: Int) {
Image(
painter = painterResource(id = imageResId),
contentDescription = null // 为了简化示例,这里省略了contentDescription
)
}

在上面的例子中,@DrawableRes 注解用于说明 imageResId 参数应该是一个 Drawable 资源的引用。这有助于提高代码的可读性,并让开发者知道这个参数期望接收一个 Drawable 资源的 ID。

需要注意的是,@DrawableRes 注解本身并没有在 Compose 中引入新的功能,它只是一个标记注解,用于帮助开发者正确使用 Drawable 资源。当你使用这个注解时,编译器会在编译时进行一些检查,以确保你传递给标记有 @DrawableRes 注解的参数的值是一个有效的 Drawable 资源。

Kotlin基础

记忆变量

在Jetpack Compose中,by mutableStateOfremember { mutableStateOf() } 都用于创建可变状态,主要是因为

  1. by mutableStateOf

    通常在ViewModel类下面进行使用

    1
    var count by mutableStateOf(0)

    这是一种直接在组件内部声明和使用可变状态的方式。这种声明方式通常用于短生命周期的组件,例如对话框或临时组件。当 count 的值发生变化时,相关的组件会重新绘制以反映新的状态。

  2. remember { (mutableStateOf)() }

    通常在Screen类下面进行使用

    1
    2
    3
    4
    // val 代表这个属性只可读,所以直接使用 = 连接
    val password = remember { mutableStateOf("123456") }
    // var 代表这个属性是可变的,通过 by 关键字将属性的委托给 remember { mutableStateOf(false) } 表达式返回的可变状态。这样,变量的值可以随着时间的推移而改变
    var passwordVisibility by remember { mutableStateOf(false) }
  3. rememberUpdatedState{ mutableStateOf() }

    通常用于确保在不同组合之间保持引用相等性

    在下面的代码中,你直接使用 remember 的话是不会和 viewModel.ifShowTarget 的值同步的

    通过使用普通变量 ifShowTarget 的方式的对比,发现是因为 viewModel 都是同一个对象,所以能够实现这样同时变化的效果,使用 remember 无法实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // 使用 viewModel 方式
    @Composable
    fun targetMenuList(viewModel: TimerViewModel) {
    val visibility by rememberUpdatedState(newValue = viewModel.ifShowTarget)
    //val visibility by remember{ mutableStateOf(viewModel.ifShowTarget) }

    AnimatedVisibility(
    visible = visibility,
    enter = fadeIn(),
    exit = fadeOut()
    ) {

    }
    }

    // 使用普通变量 ifShowTarget 的方式
    @Composable
    fun targetMenuList(ifShowTarget: Boolean) {
    // val visibility by rememberUpdatedState(newValue = viewModel.ifShowTarget)
    val visibility by rememberUpdatedState(newValue = ifShowTarget)

    AnimatedVisibility(
    visible = visibility,
    enter = fadeIn(),
    exit = fadeOut()
    ) {

    }
    }

综合而言,by mutableStateOf 用于直接在组件内部声明和使用可变状态,而 remember { mutableStateOf() } 则在组件生命周期内保持状态,并通过 remember 函数来确保初始化只在组件首次创建时进行。选择使用哪种方式取决于组件的生命周期和状态管理需求。

ViewModel

ViewModel 是一种在 Android 架构组件中用于存储和管理与用户界面相关的数据的类。它的目的是存储和管理与界面相关的持久化数据,以便在配置更改(如屏幕旋转)等情况下,数据仍然可以保持不变。在 Jetpack Compose 中,ViewModel 仍然是一个有用的概念,但需要结合 viewModel()viewModelProvider 使用。

以下是有关 ViewModel 的一些关键概念:

创建 ViewModel:

在 Jetpack Compose 中,通常使用 viewModel() 函数来获取或创建 ViewModel。该函数位于 androidx.lifecycle.viewmodel.compose 包中。

1
2
3
4
5
6
7
8
9
class MyViewModel : ViewModel() {
// 在此处定义与界面相关的数据
}

@Composable
fun MyComposable() {
val viewModel: MyViewModel = viewModel()
// 使用 viewModel 中的数据
}

通过 ViewModelProvider 创建 ViewModel:

在某些情况下,可能需要使用 ViewModelProvider 来手动创建 ViewModel,特别是在 Compose 中使用 Jetpack Navigation 时。

1
val viewModel: MyViewModel = viewModelProvider()

使用 ViewModel 存储数据:

ViewModel 可以用于存储需要在配置更改时保留的数据,例如网络请求的结果、用户输入等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyViewModel : ViewModel() {
private val _data = mutableStateOf("Initial Value")
val data: State<String> get() = _data

fun updateData(newData: String) {
_data.value = newData
}
}

@Composable
fun MyComposable() {
val viewModel: MyViewModel = viewModel()
val data: State<String> = viewModel.data

// 使用 data 中的值
}

观察 LiveData(可选):

在传统的 Android 架构组件中,ViewModel 通常与 LiveData 结合使用。在 Compose 中,可以使用 observeAsState() 函数来观察 LiveData。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyViewModel : ViewModel() {
private val _data = MutableLiveData("Initial Value")
val data: LiveData<String> get() = _data

fun updateData(newData: String) {
_data.value = newData
}
}

@Composable
fun MyComposable() {
val viewModel: MyViewModel = viewModel()
val data: State<String> = viewModel.data.observeAsState("Initial Value")

// 使用 data 中的值
}

ViewModel 的使用有助于将数据管理与 UI 层分离,确保数据在配置更改等情况下得以保留。Jetpack Compose 中的 ViewModel 与传统 Android 架构组件中的用法相似,但需要使用相应的 Compose 函数。

项目结构

Jetpack Compose是一种用于构建Android用户界面的现代工具包。在创建Jetpack Compose项目时,一个良好的项目结构可以使代码组织更清晰、易于维护。以下是一个常见的Jetpack Compose项目结构的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
app
|-- src
| |-- main
| |-- java/com/example/mycomposeapp
| |-- di // 依赖注入相关
| |-- AppContainer.kt
| |-- AppModule.kt
| |-- model // 数据模型
| |-- User.kt
| |-- ui
| |-- theme // 主题定义
| |-- Color.kt
| |-- Typography.kt
| |-- components // 可重用组件
| |-- ButtonComponent.kt
| |-- TextFieldComponent.kt
| |-- screens // 各个屏幕或功能的Compose界面
| |-- HomeScreen.kt
| |-- ProfileScreen.kt
| |-- navigation // 导航相关
| |-- Navigation.kt
| |-- viewmodel // ViewModel层
| |-- HomeViewModel.kt
| |-- ProfileViewModel.kt
| |-- MainActivity.kt // 主Activity
| |-- res
| |-- drawable
| |-- layout
| |-- mipmap
| |-- values
| |-- colors.xml
| |-- strings.xml
| |-- themes.xml
|-- build.gradle
|-- proguard-rules.pro
|-- settings.gradle
|-- gradle.properties
|-- local.properties

解释一下各个目录:

  • di: 依赖注入相关的代码,例如使用Dagger Hilt进行依赖注入的相关类。

  • model: 数据模型类,用于表示应用程序中使用的数据结构。

  • ui/theme: 包含应用程序的主题定义,例如颜色、字体等。

  • ui/components: 包含可重用的Compose组件,例如自定义的按钮、文本字段等。

  • ui/screens: 包含应用程序的不同屏幕或功能的Compose界面。每个屏幕或功能应该有自己的文件。

  • ui/navigation: 导航相关的代码,例如定义应用程序的导航图。

  • ui/viewmodel: 包含ViewModel层的类,负责处理业务逻辑并提供数据给UI。

  • MainActivity.kt: 应用程序的主Activity,负责设置Compose的根界面。

  • res: 包含资源文件,例如布局文件、颜色、字符串等。

  • build.gradle: 项目的构建配置文件。

  • proguard-rules.pro: ProGuard规则文件,用于混淆和优化代码。

  • settings.gradle: 包含项目的设置,例如模块的配置。

  • gradle.properties: 包含项目的全局Gradle属性。

  • local.properties: 包含本地开发环境的配置,例如SDK路径。

这只是一个示例结构,具体的项目结构可以根据团队的偏好和项目的规模进行调整。使用这样的结构可以使项目更有组织性,易于理解和维护。

快捷键

使用 option + command + M 可以将选中的布局代码提取成一个函数

输出 prev 自动生成预览模版

实现效果

网络请求

1、封装一个网络请求单例工具类

基本不需要改什么东西,只需要根据后端地址改SERVER_URL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
object RequestUtils {
private const val SERVER_URL = "http://42.192.90.134:8181/"

private suspend fun sendRequest(
path: String,
rawBody: String = "",
method: RequestMethod
) = withContext(Dispatchers.IO) {
val url = "${SERVER_URL}${path}"
Log.i("PostUrl", url)
Log.i("RequestBody", rawBody)
// 构造 OkHttpClient
val client = OkHttpClient()
val mediaType = "application/json".toMediaTypeOrNull()
val requestBody = rawBody.toRequestBody(mediaType)

// 创建POST请求
val requestBuilder = Request.Builder()
requestBuilder.url(url)
if (method == RequestMethod.POST) {
requestBuilder.post(requestBody)
} else {
requestBuilder.get()
}
val request = requestBuilder.build()

// 发送请求并获取响应
client.newCall(request).execute().use {
if (it.isSuccessful) {
val responseBody = it.body?.string().toString()
Log.i("ResponseBody", responseBody)
if ("\"code\": 0," in responseBody) throw IOException(responseBody)
return@withContext responseBody
} else {
val errorMessage = it.body?.string().toString()
Log.e("RequestFailed", errorMessage)
throw IOException(errorMessage)
}
}
}

suspend fun sendPost(path: String, rawBody: String = "") = withContext(Dispatchers.IO) {
return@withContext sendRequest(path, rawBody, RequestMethod.POST)
}


suspend fun sendGet(path: String) = withContext(Dispatchers.IO) {
sendRequest(path, method = RequestMethod.GET)
}
}

enum class RequestMethod {
POST, GET
}

2、编写 Model 类,作为返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Response类
data class Response<T>(
val code: String?,
val message: String?,
val data: T
)
// TargetModel类
data class TargetModel(
var id: Int?,
var userId: Int?,
var userEmail: String?,
var targetName: String?,
var targetDescribe: String?,
var targetColor: Int?,
var targetPoint: String?,
var deadline: String?,
var status: String?,
var deadlineString: String?,
var ifPoints: Int?,
var ifTargetNull: String?,
var ifTargetUpdate: Int?,
var targetId: String?
)

3、再进一步封装一个专门为 target 提供请求方法的工具单例类

注意这里需要改的东西很多,包括

请求的参数(userEmail、ifTargetUpdate)

网络请求的返回值(success:(model: 返回值的格式)-> Unit)

网络请求具体地址(“target/get”)

请求参数封装类(GetTargetList)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 单例对象 `Target`,包含一个用于获取目标数据的函数。
*/
object Target {

/**
* 用于获取目标数据的函数。
*
* @param userEmail 请求目标数据的用户电子邮件地址。
* @param ifTargetUpdate 标志,指示是否更新目标数据。
* @param scope 用于启动协程的 [CoroutineScope]。
* @param success 在成功检索到目标数据时调用的回调函数。
* @param failed 在过程中发生异常时调用的回调函数。
* @param complete 在成功或失败后调用的回调函数,用于执行任何清理或附加操作。
*/
fun fetchTargetData(
userEmail: String,
ifTargetUpdate: Int,
scope: CoroutineScope,
success: (model: List<TargetModel>) -> Unit,
failed: (e: Exception) -> Unit,
complete: () -> Unit = {}
) = scope.launch(Dispatchers.Main) {
try {
// 使用指定的用户电子邮件地址和更新标志创建 GetTargetList 对象
val postRequest = GetTargetList(userEmail, ifTargetUpdate)

// 使用 Gson 将 GetTargetList 对象转换为 JSON
val requestJson = Gson().toJson(postRequest)

// 发送带有目标数据请求的 POST 请求到服务器
val response = RequestUtils.sendPost("target/get", requestJson)

// 使用 Gson 解析 JSON 响应并提取目标数据
val gson = Gson()
val type = object : TypeToken<Response<List<TargetModel>>>() {}.type
val targetModel = gson.fromJson<Response<List<TargetModel>>>(response, type).data

// 使用检索到的目标数据调用成功回调
success(targetModel)
} catch (e: Exception) {
// 在发生异常时调用失败回调
failed(e)
}

// 调用完成回调执行任何清理或附加操作
complete()
}
}

//请求参数封装类(GetTargetList)
data class GetTargetList(
val userEmail: String,
val ifTargetUpdate: Int
)

4、将封装好的 target 方法在 ViewModel进行定义,方便后续使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class TimerViewModel:ViewModel() {
var ifShowTarget:Boolean by mutableStateOf(false)

fun getTargetMenuList() {
Target.fetchTargetData(
userEmail = "3489044730@qq.com",
ifTargetUpdate = 1,
scope = viewModelScope,
success = {
Log.i("getTargetMenuList请求成功", it.toString())
},
failed = {
Log.e("getTargetMenuList请求失败", it.message.toString())
}
)
}
}

5、在 Screen 中使用

1
2
3
4
5
6
7
8
9
Image(
painter = painterResource(id = R.drawable.arrowdown),
contentDescription = null,
modifier = Modifier
.clickable {
viewModel.ifShowTarget = !viewModel.ifShowTarget
viewModel.getTargetMenuList()
}
)

变量传递

这块挺基础的,就是主要是注意 ViewModel 类不能用单例对象,应该直接使用类

以及如何正确使用mutableStateOf

在Compose中,通常不建议将ViewModel设计为单例对象,因为ViewModel的设计目的是为了存储与界面相关的数据,并且可以在配置更改(如屏幕旋转)等情况下保留这些数据。如果将ViewModel设计为单例对象,它将在整个应用的生命周期中存在,并且在配置更改后不会被销毁和重新创建。

1、ViewModel 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class TimerViewModel:ViewModel() {
var ifShowTarget:Boolean by mutableStateOf(false)

fun getTargetMenuList() {
Target.fetchTargetData(
userEmail = "3489044730@qq.com",
ifTargetUpdate = 1,
scope = viewModelScope,
success = {
Log.i("getTargetMenuList请求成功", it.toString())
},
failed = {
Log.e("getTargetMenuList请求失败", it.message.toString())
}
)
}
}

2、Screen 使用

注意 TimerScreen 的参数 viewModel,这个基本上任何一个 Screen 都会用到

Preview 中参数 viewModel 使用 TimerViewModel()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@Composable
fun TimerScreen(
viewModel: TimerViewModel
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(15.dp)
) {
topNav()
targetMenu(viewModel)
Box {
Card(

) {
// 省略部分代码...
}

targetMenuList(viewModel)
}

}
}

@Composable
fun targetMenu(viewModel: TimerViewModel) {
Card(
// 省略部分代码...
) {
Row(
// 省略部分代码...
) {
Text(
// 省略部分代码...
)
if (viewModel.ifShowTarget) {
Image(
// 省略部分代码...
)
} else {
Image(
// 省略部分代码...
)
}
}
}
}


@Preview(showBackground = true)
@Composable
fun GreetingPreview2() {
TimerScreen(TimerViewModel())
}

3、组件里面使用

注意 visibility 的创建,实际上visibility

1
2
3
4
5
6
7
8
9
10
11
12
13
@Composable
fun targetMenuList(viewModel: TimerViewModel) {
// 注意下面的使用方法
val visibility by rememberUpdatedState(newValue = viewModel.ifShowTarget)

AnimatedVisibility(
visible = visibility,
enter = fadeIn(),
exit = fadeOut()
) {
// 省略部分代码...
}
}

LazyColumn 具体实现

1、首先创建 ViewModel 的相关方法

注意这里的 targetMenuList 属性的创建方法

以及如何赋值给 targetMenuList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class TimerViewModel:ViewModel() {
// 注意创建方法,这里使用 listOf() 创建了一个空的数组作为初始值
var targetMenuList by mutableStateOf<List<TargetModel>>(listOf())

fun getTargetMenuList() {
Target.fetchTargetData(
userEmail = "3489044730@qq.com",
ifTargetUpdate = 1,
scope = viewModelScope,
success = {
targetMenuList=it
Log.i("getTargetMenuList请求成功", it.toString())
},
failed = {
Log.e("getTargetMenuList请求失败", it.message.toString())
}
)
}
}

2、其次通过网络请求获取到相关数据,再将数据传入组件 targetMenuList 中

1
2
3
4
5
6
7
8
9
10
AnimatedVisibility(
visible = visibility,
enter = fadeIn(),
exit = fadeOut()
) {
// 调用 TimerViewModel 中的 getTargetMenuList 方法
viewModel.getTargetMenuList()
// 然后传入组件中供 LazyColumn 使用
targetMenuList(viewModel.targetMenuList)
}

3、最后编写组件 targetMenuList 的相应布局

使用箭头函数的方式,获取到 targetMenuList 中的每个元素,再使用字符串的插值语法${}写入获取到的元素的相关属性值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Composable
fun targetMenuList(targetMenuList: List<TargetModel>) {

Card(
// 省略代码
) {
LazyColumn {
items(targetMenuList) { target ->
Row(
// 省略代码
) {
Text(
text = "${target.targetName}",
fontSize = 18.sp,
color = colorResource(id = R.color.hui)
)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(id = R.drawable.coin),
contentDescription = null,
modifier = Modifier
)
Text(
text = "X${target.targetPoint}",
fontSize = 18.sp,
color = colorResource(id = R.color.hui),
fontWeight = FontWeight.Bold
)
}
}
}
}
}
}

侧边栏

Bug

  1. 在 card 中使用阴影报错Type mismatch: inferred type is Dp but CardElevation was expected

    解决如下

    不能直接使用 5.dp 的方式,需要加上外面这层包装

    1
    elevation = CardDefaults.cardElevation(defaultElevation = 5.dp) 
  2. 在 Card 中使用阴影踩坑了,不能使用 elevation 来指定阴影

    原因在于:设置之后没有任何的阴影出现,以及 elevation 的功能少,只能用黑色阴影,相反 .shadow 能够设置的内容就更多

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    Card(
    // elevation = CardDefaults.cardElevation(defaultElevation = 5.dp), //注意 elevation 的写法,现在好了不用注意了,直接送走这位属性
    modifier = Modifier
    .padding(top = 20.dp)
    .padding(horizontal = 10.dp)

    .shadow(25.dp,RoundedCornerShape(20.dp))

    //.clip(shape = MaterialTheme.shapes.large)
    .clip(shape = RoundedCornerShape(20.dp))
    .fillMaxWidth()
    ) {
    Row(
    horizontalArrangement = Arrangement.SpaceBetween,
    verticalAlignment = Alignment.CenterVertically,
    modifier = Modifier
    .background(color = colorResource(id = R.color.danHui))
    .padding(10.dp)
    .fillMaxWidth()
    ) {

    }
    }
  3. 使用动画效果时,发现内部的阴影展示有问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    @Composable
    fun targetMenuList() {
    val visibility by rememberUpdatedState(newValue = TimerViewModel.ifShowTarget)

    AnimatedVisibility(
    visible = visibility,
    enter = fadeIn(),
    exit = fadeOut()
    ) {
    Card(
    modifier = Modifier
    //.padding(top = 20.dp)
    .padding(vertical = 30.dp)

    .padding(horizontal = 10.dp)

    .shadow(25.dp, RoundedCornerShape(20.dp))
    .clip(shape = RoundedCornerShape(20.dp))
    .fillMaxWidth(),
    ) {

    }
    }
    }

    可恶还没有解决!!!!!🥲

    解决了,发现是 padding 的下面不相同,导致阴影被截断

  4. 网络请求这块遇到CLEARTEXT communication to ************* not permitted by network security policy报错

    解决方法:

    AndroidManifest.xml中加入android:usesCleartextTraffic="true"以及<uses-permission android:name="android.permission.INTERNET" />

    分别是允许 http 访问(在安卓9 往上只允许 https 访问)以及获取用户网络请求的权限

    1
    2
    3
    4
    5
    6
    <uses-permission android:name="android.permission.INTERNET" />
    <application
    android:usesCleartextTraffic="true"
    tools:targetApi="31" >

    </application>
  5. 在网络请求获取targetMenuList时,想要打印其数据,但是打印出来的 targetMenuList 为空:

    原因是在打印 targetMenuList 时,它的数据还没有被获取到,这一点从打印日志中可以推断出来

    解决方法:

    通过LaunchedEffect再去打印

    LaunchedEffect(viewModel.targetMenuList) {
        // 当 targetMenuList 变化时,此块将被执行
        Log.i("targetMenuList", "targetMenuList:${viewModel.targetMenuList}")
    }
    
    AnimatedVisibility(
        visible = visibility,
        enter = fadeIn(),
        exit = fadeOut()
    ) {
        targetMenuList(viewModel.targetMenuList)
    }