Compose 学习 布局组件 Jetpack Compose 中的布局是通过组合函数和组件来构建的,而不是使用传统的 XML 布局文件。Compose 提供了一系列用于构建用户界面的组件和布局函数。以下是一些常见的 Jetpack Compose 布局相关的内容:
Column Column
是 Jetpack Compose 中用于垂直排列子元素的布局组件。它允许你按照从上到下的顺序垂直排列各个子元素。以下是一些关于 Column
的详细信息和使用示例:
创建 Column:
你可以通过简单地在 Column
中放置子元素来创建垂直布局。
使用 Modifier 定制 Column:
你可以使用 Modifier
对 Column
进行定制,例如设置大小、填充等。
1 2 3 4 5 6 7 Column( modifier = Modifier .fillMaxSize() .padding(16. dp) ) { }
子元素的定位和对齐:
可以使用 verticalArrangement
和 horizontalAlignment
参数来调整子元素在 Column
中的垂直和水平排列方式。
1 2 3 4 5 6 Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { }
设置子元素之间的间距:
你可以使用 Modifier.padding
为 Column
中的子元素设置间距。
1 2 3 4 5 6 7 Column( modifier = Modifier.padding(8. dp) ) { }
使用 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 ) ) { } Box( modifier = Modifier .height(100. dp) .fillMaxWidth() .background(Color.Red) .weight(2f ) ) { } }
Row Row
是基本的水平布局组件。它们允许您按水平顺序排列其子组件。
基本使用:
1 2 3 4 5 6 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) ) { 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 ) { Text("Element 1" ) Text("Element 2" ) Text("Element 3" ) }
通过使用 horizontalArrangement
和 verticalAlignment
参数,你可以控制子元素在水平和垂直方向上的排列方式。在这个例子中,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 ) ) { } Box( modifier = Modifier .height(50. dp) .fillMaxWidth() .background(Color.Red) .weight(2f ) ) { } }
通过使用 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
组件充满整个父容器的大小,并设置了背景颜色为 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) ) { Text( text = "Hello, Box!" , modifier = Modifier .size(200. dp) .background(Color.White) ) } }
在这个例子中,Box
中包含了一个 Text
组件,并设置了 size
和 background
属性。
对齐和定位:
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
提供了更灵活的布局,允许您通过设置约束条件来确定子组件的位置。
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 ) { 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 ) { 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) ) { 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 { } ) { 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) } } }
在这个例子中,每个项目是一个包含标题和子标题的数据类 ListItem
。items
函数接受一个包含不同类型项目的列表,并根据每个项目的类型来定义不同的界面。
分隔线:
1 2 3 4 5 6 7 LazyColumn { items(10 ) { index -> Text("Item $index " ) Divider(color = Color.Gray, thickness = 1. dp) } }
通过在每个项目之后添加 Divider
组件,可以在列表中添加分隔线。
LazyColumn
是构建垂直滚动列表的常用组件,特别适用于大量数据的情况,因为它采用懒加载的方式,只加载当前可见的项目,从而提高性能。
允许在超过屏幕空间的情况下滚动显示内容。
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 = { }) { Icon(Icons.Default.Menu, contentDescription = null ) } }, actions = { IconButton(onClick = { }) { Icon(Icons.Default.Favorite, contentDescription = null ) } } ) }, content = { Text(text = "Hello, Compose!" ) }, bottomBar = { BottomAppBar( content = { IconButton(onClick = { }) { Icon(Icons.Default.Home, contentDescription = null ) } }, backgroundColor = MaterialTheme.colorScheme.primary ) }, floatingActionButton = { FloatingActionButton( onClick = { }, 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 = { }) { Icon(Icons.Default.Home, contentDescription = null ) } }, backgroundColor = MaterialTheme.colorScheme.primary ) }, floatingActionButton = { FloatingActionButton( onClick = { }, 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( modifier = Modifier .padding(top = 20. dp) .padding(horizontal = 10. dp) .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" ) } }
通过在 Card
的 modifier
中添加 clickable
,你可以为卡片添加点击事件。在上面的例子中,点击卡片时切换卡片内容的展开和折叠状态。
总的来说,Card
是 Jetpack Compose 中用于创建卡片样式的主要组件,它提供了许多选项,以满足不同应用场景下的需求,同时可以通过嵌套使用和添加点击事件等方式实现更为复杂的交互和布局。
Text Text
是 Jetpack Compose 中用于显示文本的组件。它允许你以声明式的方式定义和呈现文本内容。以下是一些关于 Text
的详细信息和使用示例:
基本使用:
在这个基本的示例中,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
是 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 ) } }
通过 contentColor
和 backgroundColor
属性,你可以自定义浮动操作按钮的前景和背景颜色。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.fillMaxHeightimport androidx.compose.material3.Dividerimport 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 ) }
通过设置 startIndent
和 endIndent
属性,你可以指定分隔线的起始和结束缩进。
总体来说,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 = { }, 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 = { }, 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)) } }
在这个例子中,Checkbox
与 Text
组件一起使用,创建了一个带有文本标签的复选框。通过在外部容器上使用 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 , scaleY = if (visible) 1f else 0.5f ) ) } 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 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 ) } } }
在这个例子中,通过设置不同的 color
、progress
和 isIndeterminate
属性,展示了不同样式的圆形进度指示器。
CircularProgressIndicator
是一个非常灵活的组件,可以根据需求进行定制。你可以调整大小、颜色、进度和样式,以满足设计和用户体验的要求。
基础属性 Modifier 在Jetpack Compose中,Modifier
是一种用于修改组件(如布局或绘制元素)行为或外观的强大工具。通过 Modifier
,你可以指定诸如大小、颜色、填充、点击处理等属性,从而定制组件的外观和交互。
以下是一些常见的 Modifier
的使用示例:
设置大小:
这将组件的大小设置为 50.dp
。
设置边距:
在 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
。
设置颜色:
1 Modifier.background(colorResource(id = R.color.zi))
这将为组件设置蓝色的背景。
设置点击处理:
这将使组件可点击,并在点击时执行指定的逻辑。
设置居中对齐:
1 Modifier.align(Alignment.Center)
这将使组件在其父组件中居中对齐。
设置边框:
1 Modifier.border(1. dp, Color.Gray)
这将为组件添加一个 1.dp
宽度、灰色的边框。
设置偏移:
在 Jetpack Compose 中,Offset
是一个用于表示二维平面上的偏移量的数据类。它通常用于指定在屏幕上或在父组件内的位置。Offset
有两个属性,分别是 x
和 y
,分别表示水平和垂直方向上的偏移。
以下是一个简单的示例,演示如何在 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
组件的 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) ) { } }
在这个例子中,offsetX
和 offsetY
表示在像素中的偏移量,而 toPx()
函数用于将 dp 转换为像素。在设置 offset
时,使用 dp
单位即可。
设置占比:
Modifier.weight
是用于在Jetpack Compose中分配相对空间的功能。它主要用于 Column
或 Row
这样的布局组件中,允许你指定子元素在可用空间中的相对权重。这样,你可以创建动态的、响应式的布局,使其中一个子元素占据更多或更少的空间。
使用示例:
以下是一个简单的示例,演示如何在 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 ) ) { } Box( modifier = Modifier .height(100. dp) .fillMaxWidth() .background(Color.Red) .weight(2f ) ) { } }
在这个例子中,Column
包含两个 Box
,通过 weight
设置它们的相对权重。第一个 Box
的权重为1,第二个 Box
的权重为2,因此第二个 Box
将占据比第一个 Box
更多的空间。
适用场景:
动态布局: 在需要根据数据或屏幕尺寸动态调整子元素大小的情况下,使用 Modifier.weight
是很有用的。
比例分配: 当你希望在父布局中以一定比例分配空间给子元素时,可以使用权重来定义这些比例。
响应式设计: 在设计响应式用户界面时,Modifier.weight
可以帮助你实现不同屏幕尺寸下的灵活布局。
设置圆角:
1 Modifier.clip(RoundedCornerShape(16. dp))
这将为组件添加一个 16.dp
的圆角。
设置其他自定义属性:
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(lifecycleOwner.lifecycle) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_STOP) { result = "Activity stopped" } } lifecycleOwner.lifecycle.addObserver(observer) 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
注解有几个关键的作用:
声明可组合函数: 通过使用@Composable
注解,您告诉Compose框架这个函数是一个可组合的函数,它用于构建UI组件。Compose框架会根据需要调用这些可组合函数来构建UI。
1 2 3 4 @Composable fun MyComposableFunction () { }
自动更新UI: 当Compose框架检测到可组合函数内部状态的变化时,它会重新调用相应的可组合函数以更新UI。这使得在Compose中构建动态UI变得简单,因为您只需要管理数据的变化,而不必手动操作UI元素。
组合性: 可组合函数可以嵌套和组合在一起,形成更复杂的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 ) }
在上面的例子中,@DrawableRes
注解用于说明 imageResId
参数应该是一个 Drawable 资源的引用。这有助于提高代码的可读性,并让开发者知道这个参数期望接收一个 Drawable 资源的 ID。
需要注意的是,@DrawableRes
注解本身并没有在 Compose 中引入新的功能,它只是一个标记注解,用于帮助开发者正确使用 Drawable 资源。当你使用这个注解时,编译器会在编译时进行一些检查,以确保你传递给标记有 @DrawableRes
注解的参数的值是一个有效的 Drawable 资源。
Kotlin基础 记忆变量 在Jetpack Compose中,by mutableStateOf
和 remember { mutableStateOf() }
都用于创建可变状态,主要是因为
by mutableStateOf
:
通常在ViewModel
类下面进行使用
1 var count by mutableStateOf(0 )
这是一种直接在组件内部声明和使用可变状态的方式。这种声明方式通常用于短生命周期的组件,例如对话框或临时组件。当 count
的值发生变化时,相关的组件会重新绘制以反映新的状态。
remember { (mutableStateOf)() }
:
通常在Screen
类下面进行使用
1 2 3 4 val password = remember { mutableStateOf("123456" ) }var passwordVisibility by remember { mutableStateOf(false ) }
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 @Composable fun targetMenuList (viewModel: TimerViewModel ) { val visibility by rememberUpdatedState(newValue = viewModel.ifShowTarget) AnimatedVisibility( visible = visibility, enter = fadeIn(), exit = fadeOut() ) { } } @Composable fun targetMenuList (ifShowTarget: Boolean ) { 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() }
通过 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 }
观察 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" ) }
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) val client = OkHttpClient() val mediaType = "application/json" .toMediaTypeOrNull() val requestBody = rawBody.toRequestBody(mediaType) 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 data class Response <T >( val code: String?, val message: String?, val data : T ) 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 object Target { fun fetchTargetData ( userEmail: String , ifTargetUpdate: Int , scope: CoroutineScope , success: (model : List <TargetModel >) -> Unit , failed: (e : Exception ) -> Unit , complete: () -> Unit = {} ) = scope.launch(Dispatchers.Main) { try { val postRequest = GetTargetList(userEmail, ifTargetUpdate) val requestJson = Gson().toJson(postRequest) val response = RequestUtils.sendPost("target/get" , requestJson) 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() } } 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 () { 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() ) { viewModel.getTargetMenuList() 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
在 card 中使用阴影报错Type mismatch: inferred type is Dp but CardElevation was expected
解决如下
不能直接使用 5.dp 的方式,需要加上外面这层包装
1 elevation = CardDefaults.cardElevation(defaultElevation = 5. dp)
在 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( modifier = Modifier .padding(top = 20. dp) .padding(horizontal = 10. dp) .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() ) { } }
使用动画效果时,发现内部的阴影展示有问题
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(vertical = 30. dp) .padding(horizontal = 10. dp) .shadow(25. dp, RoundedCornerShape(20. dp)) .clip(shape = RoundedCornerShape(20. dp)) .fillMaxWidth(), ) { } } }
可恶还没有解决!!!!!🥲
解决了,发现是 padding 的下面不相同,导致阴影被截断
网络请求这块遇到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 >
在网络请求获取targetMenuList
时,想要打印其数据,但是打印出来的 targetMenuList 为空:
原因是在打印 targetMenuList 时,它的数据还没有被获取到,这一点从打印日志中可以推断出来
解决方法:
通过LaunchedEffect
再去打印
LaunchedEffect(viewModel.targetMenuList) {
// 当 targetMenuList 变化时,此块将被执行
Log.i("targetMenuList", "targetMenuList:${viewModel.targetMenuList}")
}
AnimatedVisibility(
visible = visibility,
enter = fadeIn(),
exit = fadeOut()
) {
targetMenuList(viewModel.targetMenuList)
}