Jetpack Compose 是用于构建原生 Android 界面的新工具包。 它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发。 在本教程中,您将使用声明性的函数构建一个简单的界面组件。 您无需修改任何 XML 布局,也不需要使用布局编辑器。 相反,您只需调用可组合函数来定义所需的元素,Compose 编译器即会完成后面的所有工作。
1.入门 1.1.Composable
@Composable
声明为组件
1 2 3 4 5 6 7 8 9 10 @Composable fun MessageBar (msg: String ) { Text(text = msg) } @Preview @Composable fun PreViewText () { MessageBar("Context" ) }
1.2.Material Design 1.Theme.kt
在ui/theme/theme.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private val DarkColorScheme = darkColorScheme( primary = Purple80, secondary = PurpleGrey80, tertiary = Pink80, background = Color.Gray, ) private val LightColorScheme = lightColorScheme( primary = Purple40, secondary = PurpleGrey40, tertiary = Pink40, background = Color.White, )
主题的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Composable fun MyTheme ( darkTheme: Boolean = isSystemInDarkTheme() , dynamicColor: Boolean = true , content: @Composable () -> Unit ) { val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } darkTheme -> DarkColorScheme else -> LightColorScheme } MaterialTheme( colorScheme = colorScheme, typography = Typography, content = content ) }
2.使用主题
MaterialTheme.colorScheme.background
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Row (Modifier.padding(8. dp). background(MaterialTheme.colorScheme.background )) { Image( painter = painterResource(id = R.drawable.test), contentDescription = null , modifier = Modifier .size(40. dp) .clip(CircleShape) ) Column(Modifier.padding(all = 8. dp)) { Text(text = msg) } }
1.3.列表与状态 1 2 3 4 5 6 7 8 @Composable fun Conversations (messages:List <String >) { LazyColumn { items(messages) {message -> MessageBar(msg = message) } } }
调用
1 2 val conversations = listOf("1" ,"2" ,"3" )Conversations(conversations)
点击Column改变变量值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var isExpand by remember { mutableStateOf(false ) } val mcolor:Color by animateColorAsState( targetValue = if (isExpand) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface) Column( Modifier .padding(all = 8. dp) .clickable { isExpand = !isExpand }) { Surface (color = mcolor){ Text(text = "hello and $msg " , style = TextStyle(Color.Blue), maxLines = if (isExpand) Int .MAX_VALUE else 1 ) } }
1.4.列表
通过repeat(100)创建100个列表项
items(100)也可以达到同等效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Composable fun Loop () { val scrollState = rememberScrollState() Column(modifier = Modifier.verticalScroll(scrollState)) { repeat(100 ){ Text(text = "$it times" , style = MaterialTheme.typography.titleMedium) } } } @Composable fun LazyLoop () { val scrollState = rememberLazyListState() LazyColumn(state = scrollState){ items(100 ){ Text(text = "$it times" , style = MaterialTheme.typography.titleMedium) } } }
协程的使用
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 @Composable fun ScrollList () { val scrollState = rememberLazyListState() val coroutineScope = rememberCoroutineScope() val listSize = 100 ; Column { Row { Button(modifier = Modifier.weight(1f ),onClick = { coroutineScope.launch { scrollState.animateScrollToItem(0 ) } }) { Text(text = "top" ) } Button(modifier = Modifier.weight(1f ),onClick = { coroutineScope.launch { scrollState.animateScrollToItem(listSize -1 ) } }) { Text(text = "end" ) } } LazyColumn(state = scrollState){ items(listSize){ ImageItem(it) } } } } @Composable fun ImageItem (Index:Int ) { Row (verticalAlignment = Alignment.CenterVertically){ Image(painter = painterResource(id = R.drawable.test), contentDescription = null ) Text(text = "item $Index " , style = MaterialTheme.typography.titleMedium) } }
1.5自定义仿修饰符
fun Modifier.xxxx = this.then()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 fun Modifier.firstToTop (BottomToTop: Dp ) = this .then( layout{measurable, constraints -> val placeable = measurable.measure(constraints) val firstBaseLine = placeable[FirstBaseline] val placeableY = BottomToTop.roundToPx() - firstBaseLine layout(placeable.width,placeable.height + placeableY){ placeable.placeRelative(0 ,placeableY) } } ) @Composable fun TestModifier () { Text(text = "heffff" , modifier = Modifier.firstToTop(100. dp).background(color = MaterialTheme.colorScheme.surface)) }
1.6自定义组件
使用Layout设置组件布局
通过layout设置每个物体的位置
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 @Composable fun MyOwnColumn ( modifier: Modifier = Modifier, content: @Composable () -> Unit , ) { Layout(content = content, modifier = modifier){measurables, constraints -> val placeable = measurables.map{measurable-> measurable.measure(constraints) } var yPosition = 0 layout(constraints.maxWidth,constraints.maxHeight){ placeable.forEach{ it.placeRelative(0 ,yPosition) yPosition += it.height } } } } @Composable fun MyOwnColumnTest () { MyOwnColumn(modifier = Modifier.padding(8. dp)) { Text(text = "test" ) Text(text = "test" ) } }
2.进阶使用 2.1constraintlayout
在build.gradle.kt中添加依赖
1 implementation("androidx.constraintlayout:constraintlayout-compose:1.1.1")
或在build.gradle中添加
1 implementation 'androidx.constraintlayout:constraintlayout-compose:1.1.1'
使用约束布局
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 @Composable fun MyConstraintLayout () { ConstraintLayout{ val (button,text) = createRefs() Button(onClick = {}, modifier = Modifier.constrainAs(button){ top.linkTo(parent.top, margin = 16. dp) }) { Text(text = "button" ) } Text(text = "Text" , Modifier.constrainAs(text) { top.linkTo(button.bottom, margin = 16. dp) centerHorizontallyTo(parent) }) } } @Composable fun MyTowConsLayout () { ConstraintLayout { val (buttono,buttont,text) = createRefs() Button(onClick = { }, modifier = Modifier.constrainAs(buttono){ top.linkTo(parent.top, margin = 16. dp) }) { Text(text = "Button o" ) } Text(text = "text" , modifier = Modifier.constrainAs(text){ top.linkTo(buttono.bottom, margin = 16. dp) centerAround(buttono.end) }) val barrier = createEndBarrier(buttono,text) Button(onClick = {}, modifier = Modifier.constrainAs(buttont) { top.linkTo(parent.top, margin = 16. dp) start.linkTo(barrier) }) { Text(text = "Button t" ) } } }
长文本设置换行
1 2 3 4 5 6 7 8 9 10 11 @Composable fun LargeConsLayout () { ConstraintLayout { val text = createRef() val guildline = createGuidelineFromStart(fraction = 0.5f ) Text(text = "this is a very very very very very very very very very long text" , modifier = Modifier.constrainAs(text) { linkTo(start = guildline,end = parent.end) width = Dimension.preferredWrapContent }) }
2.2解耦API
将约束定义在另一个函数,通过解耦API获得约束
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 @Composable fun Deconple2 () { BoxWithConstraints { val constraints = if (maxWidth < maxHeight){ decouple(16. dp) } else { decouple(160. dp) } ConstraintLayout(constraints) { Button(onClick = {}, modifier = Modifier.layoutId("button" )) { Text(text = "button" ) } Text(text = "Text" , Modifier.layoutId("text" )) } } } private fun decouple (margin: Dp ) : ConstraintSet{ return ConstraintSet{ val button = createRefFor("button" ) val text = createRefFor("text" ) constrain(button){ top.linkTo(parent.top, margin) } constrain(text){ top.linkTo(button.bottom, margin) } } }
2.3Intrinsics
获取子元素的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Composable fun TwoTexts (modifier: Modifier = Modifier) { Row (modifier = Modifier.height(IntrinsicSize.Min)){ Text(text = "Hi" ,modifier = Modifier .padding(1. dp) .weight(1f ) .wrapContentWidth(Alignment.Start)) Divider(color = Color.Black, modifier = Modifier .fillMaxHeight() .width(1. dp)) Text(text = "Android" ,modifier = Modifier .padding(1. dp) .weight(1f ) .wrapContentWidth(Alignment.End)) } }
实现效果
3.compose状态 3.1无状态组件
TodoItems
1 2 3 4 5 6 7 8 9 10 11 data class ToDoItems (val task:String,val icon:TodoIcon = TodoIcon.default,val id:UUID = UUID.randomUUID())enum class TodoIcon ( val imageVector:ImageVector, val desc:String ){ SQU(Icons.Default.Favorite,"喜欢" ); companion object { val default = SQU } }
组件
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 @Composable fun TwoTexts (modifier: Modifier = Modifier) { Row (modifier = Modifier.height(IntrinsicSize.Min)){ Text(text = "Hi" ,modifier = Modifier .padding(1. dp) .weight(1f ) .wrapContentWidth(Alignment.Start)) Divider(color = Color.Black, modifier = Modifier .fillMaxHeight() .width(1. dp)) Text(text = "Android" ,modifier = Modifier .padding(1. dp) .weight(1f ) .wrapContentWidth(Alignment.End)) } } @Composable fun TodoSreen (items:List <ToDoItems >) { Column{ LazyColumn(modifier = Modifier.weight(1f ), contentPadding = PaddingValues(10. dp)) { items(items){ TodoRow(it, modifier = Modifier.fillParentMaxWidth()) } } Button(onClick = {}, modifier = Modifier.padding(16. dp).fillMaxWidth()) { Text(text = "click me!!!" ) } } } @Composable fun TodoRow ( todo:ToDoItems , modifier: Modifier = Modifier, ) { Row (modifier = modifier.padding(vertical = 6. dp, horizontal = 16. dp), horizontalArrangement = Arrangement.SpaceBetween ){ Text(text = todo.task) Icon(imageVector = todo.icon.imageVector, contentDescription = todo.icon.desc) } }
3.2非结构化状态
在gradle.build.kt文件中添加
1 2 3 4 buildFeatures { compose = true viewBinding = true }
新建activity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class TestCom :AppCompatActivity (){ private val bindding by lazy { ActivityTestComBinding.inflate(layoutInflater) } override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(bindding.root) bindding.input.doAfterTextChanged{ text -> uploadText(text.toString()) } } private fun uploadText (text: String ) { bindding.text.text = text } }
使用ViewModel
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 class TestViewModel :ViewModel (){ private val _name = MutableLiveData("" ) val name: LiveData<String> = _name fun onNameChanged (newname:String ) { _name.value = newname } } class TestCom :AppCompatActivity (){ private val testmodel by viewModels<TestViewModel>(); private val bindding by lazy { ActivityTestComBinding.inflate(layoutInflater) } override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(bindding.root) bindding.input.doAfterTextChanged{ text -> testmodel.onNameChanged(text.toString()) } testmodel.name.observe(this ){ text-> bindding.text.text = text } } }
3.3状态与状态提升
将TodoItemList作为状管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class TodoItemModel : ViewModel (){ private var _todos = MutableLiveData(listOf<ToDoItems>()) val todos : LiveData<List<ToDoItems>> = _todos fun AddItem (todo:ToDoItems ) { _todos.value = _todos.value!! + listOf(todo) } fun DeleteItem (todo:ToDoItems ) { _todos.value = _todos.value!!.toMutableList().also { it.remove(todo) } } }
构建主界面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private val todoModel by viewModels<TodoItemModel>();override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) enableEdgeToEdge() setContent { val todolist:List<ToDoItems> by todoModel.todos.observeAsState(listOf()) Scaffold{ Column (modifier = Modifier.padding(it)){ TodoSreen(todolist, ItemAdd = {todoModel.AddItem(it)}, ItemDelete = {todoModel.DeleteItem(it)}) } } } }
3.4remember 1. 保留状态 当你需要在 Composable 函数中保存某个状态,并且希望在组件重组时保留这个状态,可以使用 remember
。
1 2 3 4 var count by remember { mutableStateOf(0 ) }Button(onClick = { count++ }) { Text("Clicked $count times" ) }
在这个例子中,count
的值在按钮点击时会增加,并且在组件重组时,count
的值会被保留。
2.避免重复计算 如果你有一个复杂的计算结果,不希望在每次重组时重新计算,可以使用 remember
来缓存计算结果。
1 2 3 val complexResult = remember { computeComplexResult() }
3. 带条件的 remember 你可以根据某些条件来决定是否重新计算值。remember
接收可变参数 keys
,当这些 keys
的值发生变化时,remember
会重新计算并保存新的值。
1 2 3 4 var text by remember { mutableStateOf("" ) }val reversedText = remember(text) { text.reversed() }
在这个例子中,reversedText
会在 text
发生变化时重新计算。
4. 结合 mutableStateOf 使用 remember
常与 mutableStateOf
一起使用,用于创建可变状态。
1 2 3 4 var isExpanded by remember { mutableStateOf(false ) }Button(onClick = { isExpanded = !isExpanded }) { Text("Expand: $isExpanded " ) }
5. 保存多个值 remember
可以保存多个值,或者保存一个包含多个值的对象。
1 2 3 4 5 data class User (val name: String, val age: Int )val user = remember { User("Alice" , 30 ) }
6. 与 ViewModel 结合使用 在 Jetpack Compose 中,remember
也可以与 ViewModel
结合使用,保存与 UI 相关的状态。
1 2 3 4 5 val viewModel = viewModel<MyViewModel>()var data by remember { mutableStateOf(emptyList<String>()) }LaunchedEffect(Unit ) { data = viewModel.loadData() }
7. rememberSaveable 如果你需要在配置更改(如屏幕旋转)时保留状态,可以使用 rememberSaveable
,它会在配置更改时自动保存和恢复状态。
1 2 3 4 var count by rememberSaveable { mutableStateOf(0 ) }Button(onClick = { count++ }) { Text("Clicked $count times" ) }
3.5mutableStateOf
创建可组合函数 :
1 2 3 4 @Composable fun StateExample () { }
创建可观察的状态 :
1 2 3 val (count,setCount) = remember { mutableStateOf(0 ) }
mutableStateOf(0)
:创建一个初始值为 0 的可观察状态。
remember
:确保在组件重新绘制时,状态不会被重置。
by
:使用委托语法,使 count
变成一个可读写的属性。
创建按钮并处理点击事件 :
1 2 3 Button(onClick = { count++ }) { Text("Clicked $count times" ) }
Button
:一个可组合的按钮组件。
onClick
:点击按钮时的回调函数,这里增加 count
的值。
工作原理
状态创建 :
mutableStateOf
创建一个 MutableState
对象,内部包含一个值和一些元数据。
remember
确保在组件重新绘制时,状态不会被重置。
状态观察 :
当 count
被使用时,Compose 运行时会自动将当前可组合函数注册为 count
的观察者。
状态更新 :
当 count
的值发生变化时(如点击按钮时 count++
),Compose 运行时会检测到状态变化。
触发重组 :
在下一个组合周期,Compose 运行时会重新执行使用了 count
的可组合函数,更新 UI。
注意事项
状态保存 :
mutableStateOf
本身并不保存状态,它只是创建一个可观察的状态对象。如果需要在组件重新绘制时保持状态,通常需要结合 remember
函数一起使用。
状态变化检测 :
Compose 的状态变化检测是基于引用的。对于复杂对象,如果只是修改了对象的内部属性,而没有改变对象本身,Compose 可能不会检测到状态的变化。这种情况下,可能需要手动通知状态变化。
性能优化 :
虽然 mutableStateOf
提供了强大的状态管理能力,但在实际开发中,应尽量减少不必要的状态更新,以提高应用的性能。
3.6输入框
小项目实战
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 @Composable fun InputEdit ( text:String , modifier: Modifier = Modifier, onTextChange:(String )->Unit ) { TextField(onValueChange = onTextChange, maxLines = 1 , value = text, modifier = modifier, colors = TextFieldDefaults.colors(unfocusedTextColor = Color.Magenta)) } @Composable fun InputButton ( onClick:()->Unit , text: String , modifier: Modifier = Modifier, enable : Boolean = true ) { TextButton(onClick = onClick, enabled = enable, modifier = modifier, shape = CircleShape, colors = ButtonDefaults.buttonColors()) { Text(text = text) } } @Composable fun AnimateIconRow ( icon:TodoIcon , OnIconChange:(TodoIcon )->Unit , modifier: Modifier = Modifier, visiable:Boolean = true ) { val enter = remember { fadeIn(animationSpec = TweenSpec(200000 , easing = FastOutSlowInEasing)) } val exit = remember { fadeOut(animationSpec = TweenSpec(200000 , easing = FastOutSlowInEasing)) } Box(modifier = modifier.defaultMinSize(minHeight = 16. dp)){ AnimatedVisibility(visiable,enter = enter , exit = exit) { IconRow(icon = icon,OnIconChange = OnIconChange,modifier = modifier) } } } @Composable fun SelectableButton ( modifier:Modifier , icon: ImageVector , desc: String , OnIconSelect: () -> Unit , isSelected: Boolean ) { val tint = if (isSelected) Color.Magenta else MaterialTheme.colorScheme.surface.copy(0.6f ) TextButton(onClick = {OnIconSelect()}, shape = CircleShape, modifier = modifier) { Column { Icon(imageVector = icon, contentDescription = desc) if (isSelected){ Box (modifier = Modifier .padding(top = 3. dp) .height(1. dp) .width(icon.defaultWidth) .background(color = tint)){ } }else { Spacer(modifier = Modifier.height(4. dp)) } } } } @Composable fun IconRow (icon: TodoIcon , OnIconChange: (TodoIcon ) -> Unit ,modifier : Modifier = Modifier) { Row (modifier) { for (todoicon in TodoIcon.entries){ SelectableButton( modifier = Modifier, icon = todoicon.imageVector, desc = todoicon.desc, OnIconSelect = {OnIconChange(todoicon)}, isSelected = todoicon == icon ) } } } @Composable fun InputList () { val (text,setText) = remember { mutableStateOf("test" ) } val (icon,setIcon) = remember { mutableStateOf(TodoIcon.default) } val iconVisable = text.isNotBlank() Column { Row (modifier = Modifier.padding(10. dp)){ InputEdit( text = text, onTextChange = setText, modifier = Modifier .padding(end = 8. dp) ) InputButton(onClick = {} , enable = text.isNotBlank(), text = "提交" , modifier = Modifier .align(Alignment.CenterVertically) .fillMaxWidth()) } if (iconVisable){ AnimateIconRow(icon,setIcon, modifier = Modifier.padding(8. dp)) } } }
3.7状态恢复 1.Parcelize 在 Kotlin 中,@Parcelize
是一个注解,用于简化实现 Parcelable
接口的过程。Parcelable
是 Android 中用于序列化对象的接口,以便对象可以在不同组件之间传递,例如在 Intent
中传递或者保存在 Bundle
中。
使用步骤
添加插件 在项目的 build.gradle.kt
文件中添加 kotlin-parcelize
插件:
1 2 3 plugins { id("kotlin-parcelize" ) }
注解数据类 使用 @Parcelize
注解你的数据类,并让它继承 Parcelable
接口:
1 2 @Parcelize data class City (val name: String, val country: String) : Parcelable
使用对象 现在,你可以在需要传递对象的地方使用这个数据类,例如在 Intent
中:
1 2 3 val intent = Intent(this , AnotherActivity::class .java)intent.putExtra("city" , City("Beijing" , "China" )) startActivity(intent)
或者在 rememberSaveable
中保存状态:
1 2 3 4 5 6 7 8 9 10 @Composable fun CityScreen () { var city by rememberSaveable { mutableStateOf(City("London" , "UK" )) } Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(10. dp)) { TextButton(onClick = { city = City("Beijing" , "CHINA" ) }) { Text(text = "Click to change" ) } Text(text = "${city.country} -${city.name} " ) } }
注意事项
所有属性必须可序列化 :@Parcelize
注解的数据类的所有属性必须是可序列化的,即它们本身要么是基本类型,要么也是实现了 Parcelable
或 Serializable
的类。
**自定义 Saver
**:如果 @Parcelize
不适合你的场景,你还可以使用 mapSaver
或 listSaver
自定义存储规则。
通过这种方式,你可以轻松地在 Android 应用中实现对象的序列化和传递。
mapSaver的作用
定义保存规则 :mapSaver用于定义如何将对象转换为Map<String, Any>
的结构进行保存。它允许开发者自定义保存和恢复的规则,通过指定键值对来存储对象的属性。
保存复杂对象 :当对象的属性较多或结构复杂时,使用mapSaver可以更清晰地指定每个属性的保存方式。
示例代码 :
kotlin复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 data class City (val name: String, val country: String)val CitySaver = run { val nameKey = "Name" val countryKey = "Country" mapSaver( save = { mapOf(nameKey to it.name, countryKey to it.country) }, restore = { City(it[nameKey] as String, it[countryKey] as String) } ) } @Composable fun CityScreen () { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid" , "Spain" )) } }
listSaver的作用
mapSaver和listSaver的区别
数据结构不同 :mapSaver将对象转换为Map
结构,而listSaver将对象转换为List
结构。
使用场景不同 :当需要明确的键来标识属性时,mapSaver更合适;而当数据结构简单且顺序固定时,listSaver更方便。
灵活性不同 :mapSaver在处理复杂对象时更灵活,因为它允许开发者自定义每个属性的键;而listSaver在处理简单对象时更简洁。
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 val citySaver = run { val nameKey = "Name" val countryKey = "Country" mapSaver(save = { mapOf(nameKey to it.name,countryKey to it.country)}, restore = {MainActivity.OCity(it[nameKey] as String,it[countryKey] as String)}) } val citySavert = listSaver( save = { listOf(it.name,it.country)}, restore = {MainActivity.OCity(it[0 ] as String,it[1 ] as String)} ) val (city,setCity) = rememberSaveable(stateSaver = citySavert) { mutableStateOf(MainActivity.OCity("Madrid" ,"Spain" )) } Row (verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(10. dp)){ TextButton(onClick = { setCity(MainActivity.OCity("Beijing" ,"China" )) }) { Text(text = "Change" ) } Text(text = city.toString()) }
3.8CompositionLocalProvider 1 2 3 4 5 6 7 8 9 10 11 12 13 MaterialTheme { Column { Text(text = "1" ) CompositionLocalProvider(LocalContentColor provides LocalContentColor.current.copy(alpha = 0.6f )) { Text(text = "2" ) Text(text = "3" ) } CompositionLocalProvider(LocalContentColor provides LocalContentColor.current.copy(alpha = 0.38f )) { Descendant() } FruitTitle(size = 1 ) } }
string.xml文件
1 2 3 4 5 6 7 <resources > <string name ="app_name" > Kcom</string > <plurals name ="fruit_title" > <item quantity ="one" > fruit</item > <item quantity ="other" > fruits</item > </plurals > </resources >
如果size为1则匹配one
size为2,3….则匹配other
1 2 3 4 5 6 7 8 9 FruitTitle(size = 1 ) @Composable fun FruitTitle (size:Int ) { val resource = LocalContext.current.resources val fruit = resource.getQuantityString(R.plurals.fruit_title,size) Text(text = fruit) }
自定义compositionLocal
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 data class Elevations (val cradDp: Dp = 0. dp)val LocalElevations = compositionLocalOf { Elevations() } object CardElevation{ val high:Elevations get () = Elevations(10. dp) val low:Elevations get () = Elevations(5. dp) } @Composable fun Test () { Column { CompositionLocalProvider(LocalElevations provides CardElevation.low) { MyCard(background = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f )) { } } MyCard(background = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f )) { } } } @Composable fun MyCard ( elevation: Dp = LocalElevations.current.cradDp, background: Color , content: @Composable ColumnScope .()->Unit ) { Card(elevation = CardDefaults.cardElevation(defaultElevation = elevation),modifier = Modifier.size(200. dp), colors = CardDefaults.cardColors(containerColor = background), content = content) }
4.主题 1.定义主题 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 private val DarkColorScheme = darkColorScheme( primary = Purple80, secondary = PurpleGrey80, tertiary = Pink80, background = Color(0xFFFFFBFE ), ) private val LightColorScheme = lightColorScheme( primary = Purple40, secondary = PurpleGrey40, tertiary = Pink40, background = Color(0xFFFFFBFE ), surface = Color(0xFFFFFBFE ), onPrimary = Color.White, onSecondary = Color.White, onTertiary = Color.White, onBackground = Color(0xFF1C1B1F ), onSurface = Color(0xFF1C1B1F ), ) @Composable fun NrcTheme ( darkTheme: Boolean = isSystemInDarkTheme() , dynamicColor: Boolean = true , content: @Composable () -> Unit ) { val colorScheme = when { dynamicColor -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } darkTheme -> DarkColorScheme else -> LightColorScheme } MaterialTheme( colorScheme = colorScheme, typography = Typography, content = content ) }
2.字体的使用 1 2 3 4 5 6 7 val text = buildAnnotatedString { append("text" ) withStyle(MaterialTheme.typography.titleMedium.toSpanStyle()){ append("this is a test" ) } }
5.动画 1.简单值动画 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Composable fun MainWin () { val (num,setNum) = remember { mutableIntStateOf(0 ) } val backgroundColor by animateColorAsState(if (num == 0 ) Purple80 else Color.Green,label = "" ) NrcTheme { Scaffold(modifier = Modifier.fillMaxSize(), containerColor = backgroundColor) { innerPadding -> Row(modifier = Modifier.padding(innerPadding)) { Button(modifier = Modifier.weight(1f ),onClick = {setNum(0 )}) { Text(text = "HomePage" ) } Button(modifier = Modifier.weight(1f ),onClick = {setNum(1 )}) { Text(text = "TabBar" ) } } } } }
2.AnimatedVisibility AnimatedVisibility
是 Jetpack Compose 中的一个组件,用于在组件的可见性发生变化时添加进入和退出动画。它的主要作用是通过动画效果增强用户体验,使界面的变化更加流畅和自然。
具体作用
控制组件的可见性
通过 visible
或 visibleState
参数,可以动态控制子组件是否显示。
当 visible
为 true
时,子组件显示;为 false
时,子组件隐藏。
添加进入动画
当组件从不可见到可见时,可以通过 enter
参数定义进入动画。
常见的进入动画包括 fadeIn
(淡入)、slideIn
(滑入)等。
添加退出动画
当组件从可见到不可见时,可以通过 exit
参数定义退出动画。
常见的退出动画包括 fadeOut
(淡出)、slideOut
(滑出)等。
灵活组合动画效果
可以将多种动画效果组合使用,例如同时使用 fadeIn
和 slideInVertically
。
示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 var isVisible by remember { mutableStateOf(false ) }Button(onClick = { isVisible = !isVisible }) { Text("Toggle Visibility" ) } AnimatedVisibility( visible = isVisible, enter = fadeIn() + slideInVertically(), exit = fadeOut() + slideOutVertically() ) { Text("Hello, Compose!" , modifier = Modifier.padding(16. dp)) }
通过animateContentSize可实现内容大小变化时的动画
1 modifier = Modifier.padding(innerPadding).animateContentSize()
3.多值动画 1. 使用 updateTransition
创建转场效果 updateTransition
根据状态变化创建转场,返回的 Transition
对象可用来声明动画值。
1 2 3 4 5 6 7 8 private sealed class BoxState (val color: Color, val size: Dp) { operator fun not () = if (this is Small) Large else Small object Small : BoxState(Blue, 64. dp) object Large : BoxState(Red, 128. dp) } var boxState: BoxState by remember { mutableStateOf(BoxState.Small) }val transition = updateTransition(targetState = boxState)
2. 针对每个属性定义动画 使用 Transition
对象上的扩展函数(如 animateRect
、animateDp
等)为每个属性定义动画。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 val color by transition.animateColor(label = "Transition Tab1" ){ it-> it.color } val size by transition.animateDp(transitionSpec = { if (targetState == BoxState.Large) { spring(stiffness = Spring.StiffnessVeryLow) } else { spring(stiffness = Spring.StiffnessHigh) } }, label = "Transition Tab2" ) {it -> it.size }
3. 在可组合项中应用动画值 在可组合项中使用动画值,使界面随状态变化而平滑过渡。
1 2 3 4 5 6 7 8 9 10 11 Button( onClick = { boxState = !boxState } ) { Text("Change Color and size" ) } Spacer(Modifier.height(16. dp)) Box( Modifier .size(size) .background(color) )
4. 自定义动画行为 通过 transitionSpec
参数自定义动画行为,如动画时长、缓动函数等。
1 2 3 4 5 6 7 8 9 val size by transition.animateDp(transitionSpec = { if (targetState == BoxState.Large) { spring(stiffness = Spring.StiffnessVeryLow) } else { spring(stiffness = Spring.StiffnessHigh) } }, label = "Transition Tab2" ) {it -> it.size }
示例:多值动画完整代码 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 private sealed class BoxState (val color: Color, val size: Dp) { operator fun not () = if (this is Small) Large else Small object Small : BoxState(Blue, 64. dp) object Large : BoxState(Red, 128. dp) } @Composable fun UpdateTransitionDemo () { var boxState: BoxState by remember { mutableStateOf(BoxState.Small) } val transition = updateTransition(targetState = boxState) Column(Modifier.padding(16. dp)) { Spacer(Modifier.height(16. dp)) val color by transition.animateColor(label = "Transition Tab1" ){ it-> it.color } val size by transition.animateDp(transitionSpec = { if (targetState == BoxState.Large) { spring(stiffness = Spring.StiffnessVeryLow) } else { spring(stiffness = Spring.StiffnessHigh) } }, label = "Transition Tab2" ) {it -> it.size } Button( onClick = { boxState = !boxState } ) { Text("Change Color and size" ) } Spacer(Modifier.height(16. dp)) Box( Modifier .size(size) .background(color) ) } }
4.重复动画 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 val infiniteTransition = rememberInfiniteTransition(label = "infiniteTransition" )val alpha by infiniteTransition.animateFloat( initialValue = 0f , targetValue = 1f , animationSpec = infiniteRepeatable( animation = tween(1000 , easing = LinearOutSlowInEasing), repeatMode = RepeatMode.Reverse ), label = "Test" ) Box( modifier = Modifier .size(100. dp) .background(Color.Blue) .alpha(alpha) ) { Text( text = "Multi-Property Animation" , modifier = Modifier.align(Alignment.Center) ) }
5.手势动画 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 63 64 65 val list = mutableStateListOf(1 ,2 ,3 ,4 )@Composable fun MainWIn () { val onRemove:(Int )->Unit = { a -> list.remove(a) } Column { for (index in list){ Box(modifier = Modifier.swipe(ondismiss = { onRemove(index) }).height(100. dp).fillMaxWidth().padding(start = 10. dp, top = 10. dp, end = 10. dp).background(color = Color.Green)){ Text(text = "this is $index " ) } } } } private fun Modifier.swipe ( ondismiss: () -> Unit ) :Modifier = composed{ val offsetX = remember { Animatable(0f ) } pointerInput(Unit ){ val decay = splineBasedDecay<Float >(this ) coroutineScope { while (true ){ val pointerId = awaitPointerEventScope { awaitFirstDown().id } val velocityTracker = VelocityTracker() awaitPointerEventScope { horizontalDrag(pointerId){ change-> val horizontalDragOffset = offsetX.value + change.positionChange().x launch { offsetX.snapTo(horizontalDragOffset) } velocityTracker.addPosition(change.uptimeMillis,change.position) } } val velocity = velocityTracker.calculateVelocity().x val targetPosition = decay.calculateTargetValue(offsetX.value,velocity) launch { if (targetPosition.absoluteValue <= size.width){ offsetX.animateTo(0f , initialVelocity = velocity) }else { offsetX.animateDecay(velocity,decay) ondismiss() } } } } }.offset { IntOffset(offsetX.value.roundToInt(),0 ) } }
函数定义
1 2 3 private fun Modifier.swipe ( ondismiss: () -> Unit ) : Modifier = composed { ... }
定义了一个私有的 Modifier 扩展函数 swipe
,接收一个回调函数 ondismiss
。
使用 composed
构建一个可组合的 Modifier,以便在其中使用可组合函数和状态。
2. 动画状态初始化 1 2 3 val offsetX = remember { Animatable(0f ) }
使用 remember
创建一个可动画化的状态 offsetX
,初始值为 0,用于跟踪水平偏移量。
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 25 26 pointerInput(Unit ) { val decay = splineBasedDecay<Float >(this ) coroutineScope { while (true ) { val pointerId = awaitPointerEventScope { awaitFirstDown().id } val velocityTracker = VelocityTracker() awaitPointerEventScope { horizontalDrag(pointerId) { change -> val horizontalDragOffset = offsetX.value + change.positionChange().x launch { offsetX.snapTo(horizontalDragOffset) } velocityTracker.addPosition(change.uptimeMillis, change.position) } } val velocity = velocityTracker.calculateVelocity().x val targetPosition = decay.calculateTargetValue(offsetX.value, velocity) launch { if (targetPosition.absoluteValue <= size.width) { offsetX.animateTo(0f , initialVelocity = velocity) } else { offsetX.animateDecay(velocity, decay) ondismiss() } } } } }
**pointerInput
**:处理触摸事件。
**splineBasedDecay
**:创建一个基于样条函数的减速计算对象,用于计算滑动结束时的减速效果。
**coroutineScope
**:启动一个协程作用域,用于处理异步任务。
**while (true)
**:无限循环,持续监听触摸事件。
**awaitFirstDown
**:等待第一个触摸点按下,获取其 pointerId
。
**velocityTracker
**:记录滑动速度。
**horizontalDrag
**:监听水平拖动事件,更新 offsetX
的值,并记录滑动位置和时间。
**calculateVelocity
**:计算滑动速度。
**decay.calculateTargetValue
**:根据当前偏移量和速度,计算滑动结束时的目标位置。
动画逻辑 :
如果滑动距离小于屏幕宽度,则动画回到初始位置。
否则,执行减速动画并调用 ondismiss
回调。
4. 应用偏移量 1 2 3 .offset { IntOffset(offsetX.value.roundToInt(), 0 ) }
使用 offset
修改器,根据 offsetX
的值对组件进行水平偏移。
6.手势 1.点击手势 1 2 3 4 5 6 7 8 Modifier.pointerInput(Unit ) { detectTapGestures( onTap = {}, onPress = {}, onDoubleTap = {}, onLongPress = {} ) }
onTap
作用 :处理单击事件。
触发条件 :当用户在组件上快速点击一次时触发。
适用场景 :常用于实现按钮点击、选择项等功能。
onDoubleTap
作用 :处理双击事件。
触发条件 :当用户在组件上快速连续点击两次时触发。
适用场景 :常用于实现双击放大图片、双击刷新等功能。
onLongPress
作用 :处理长按事件。
触发条件 :当用户在组件上按住一段时间(通常为500ms以上)时触发。
适用场景 :常用于实现长按弹出菜单、长按拖动元素等功能。
onPress
作用 :处理按下事件。
触发条件 :当用户在组件上按下时触发,无论是否释放。
适用场景 :常用于实现按下时的视觉反馈、按下时开始某些操作等功能。
2.滚动修饰符
verticalScroll
1 2 3 4 5 6 7 8 9 10 11 12 @Composable fun Scroll () { val state = rememberScrollState() LaunchedEffect(Unit ) { state.animateScrollTo(100 ) } Column(modifier = Modifier.background(Color.LightGray).padding(2. dp).size(100. dp).verticalScroll(state)) { repeat(10 ){ Text(text = "this is $it " , modifier = Modifier.padding(2. dp)) } } }
3.可滚动修饰符
Scrollable,可以检测滑动手势,而不改变界面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Composable fun ScrollAble () { var offset by remember { mutableStateOf(0f ) } Box(modifier = Modifier .size(200. dp) .scrollable( orientation = Orientation.Vertical, state = rememberScrollableState { delta -> offset += delta delta } ), contentAlignment = Alignment.Center){ Text(text = offset.toString()) } }
4.嵌套滚动
嵌套滚动机制允许一个可滚动的组件在其内部包含另一个可滚动的组件,并且两者可以协调滚动行为,使得用户体验更加流畅和自然。
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 @Composable fun CloumTest () { val gradient = Brush.verticalGradient( 0f to Color.Green,1000f to Color.Yellow ) Box(modifier = Modifier .size(200. dp) .background(Color.LightGray) .verticalScroll( rememberScrollState() ) .padding(5. dp)){ Column { repeat(6 ){ Text( text = "this is $it " , modifier = Modifier .height(120. dp).background(gradient).fillMaxWidth() .border(12. dp, Color.DarkGray) .padding(24. dp) .verticalScroll(rememberScrollState()) ) } } } }
构造渐变
1 2 3 val gradient = Brush.verticalGradient( 0f to Color.Green,1000f to Color.Yellow )
5.拖动
实现拖动单一方向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Composable fun DragWin () { var offsetX by remember { mutableStateOf(0f ) } Text(modifier = Modifier .offset { IntOffset(offsetX.roundToInt(), 0 ) } .size(150. dp) .draggable( orientation = Orientation.Horizontal, state = rememberDraggableState { delta -> offsetX += delta } ),text = "dragable" ) }
任意位置拖动
使用pointerInput的detectDragGestures检测拖动手势
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 @Composable fun DragEveryWhere () { Box(modifier = Modifier.fillMaxSize()){ var offsetX by remember { mutableStateOf(0f ) } var offsetY by remember { mutableStateOf(0f ) } Box( modifier = Modifier .background(Color.Blue) .size(50. dp) .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) } .pointerInput(Unit ) { detectDragGestures { change, dragAmount -> offsetX += dragAmount.x offsetY += dragAmount.y } }){ Text(text = "drag" ) } } }
6.多点触控
平移,旋转,缩放
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 @Composable fun GraSample () { var scaleoffset by remember { mutableStateOf(1f ) } var rotation by remember { mutableStateOf(0f ) } var offset by remember { mutableStateOf(Offset.Zero) } val state = rememberTransformableState { zoomChange, panChange, rotationChange -> scaleoffset += zoomChange rotation += rotationChange offset += panChange } Box(modifier = Modifier.background(Color.Yellow).size(200. dp) .graphicsLayer { scaleX = scaleoffset scaleY = scaleoffset rotationZ = rotation rotationX = offset.x rotationY = offset.y } .transformable(state)) { } }
7.View&Compose 1.View中使用Compose 1 2 3 4 <androidx.compose.ui.platform.ComposeView android:id ="@+id/tv_main" android:layout_width ="match_parent" android:layout_height ="match_parent" />
在代码中定义获取实例
1 2 3 4 5 6 7 8 val composeView = findViewById<ComposeView>(R.id.tv_main)composeView.setContent { Text("Hello, Compose!" ) }
2.Compose中使用View 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Composable fun AndroidDesc (desc:String ) { val htmldesc = remember(desc) { HtmlCompat.fromHtml(desc, HtmlCompat.FROM_HTML_MODE_COMPACT) } AndroidView(factory = {context -> TextView(context).apply { movementMethod = LinkMovementMethod.getInstance() } }, update = { textView -> textView.text = htmldesc }) }
3.Navigation 1.添加依赖项 确保在你的 build.gradle
文件中添加了以下依赖项:
1 2 3 4 dependencies { val nav_version = "2.8.9" implementation("androidx.navigation:navigation-compose:$nav_version " ) }
1 implementation(libs.androidx.navigation.compose)
2. 创建 NavController
和 NavHost
在你的可组合项中,创建一个 NavController
实例,并使用 NavHost
来定义导航图。
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 @Composable fun MyApp () { val navController = rememberNavController() NavHost( navController = navController, startDestination = "home" ) { composable("home" ) { HomeScreen(navController) } composable("details" ) { DetailsScreen(navController) } } } @Composable fun HomeScreen (navController: NavController ) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text("Home Screen" ) Button(onClick = { navController.navigate("details" ) }) { Text("Go to Details" ) } } } @Composable fun DetailsScreen (navController: NavController ) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text("Details Screen" ) Button(onClick = { navController.navigateUp() }) { Text("Back to Home" ) } } }
点击按钮切换
1 navController.navigate(name)
3.参数的传递 1 2 3 4 5 6 7 8 composable("home/{name}" , arguments = listOf( navArgument("name" ){ type = NavType.StringType } )){entry-> val text = entry.arguments?.getString("name" ) Text(text = "test1 $text " ) }
4.深层链接 1. 定义深层链接 在 composable
函数中,使用 deepLinks
参数定义深层链接。deepLinks
参数接受一系列 NavDeepLink
对象,可以使用 navDeepLink
方法快速创建。
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 @Composable fun MyApp () { val navController = rememberNavController() NavHost(navController = navController, startDestination = "home" ) { composable( route = "home" , deepLinks = listOf( navDeepLink { uriPattern = "https://www.example.com/home" } ) ) { HomeScreen(navController) } composable( route = "details/{userId}" , arguments = listOf(navArgument("userId" ) { type = NavType.StringType }), deepLinks = listOf( navDeepLink { uriPattern = "https://www.example.com/details/{userId}" } ) ) { backStackEntry -> DetailsScreen( navController, userId = backStackEntry.arguments?.getString("userId" ) ?: "" ) } } }
2. 配置 AndroidManifest.xml
为了使应用能够处理深层链接,需要在 AndroidManifest.xml
中为活动添加 <intent-filter>
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <activity android:name =".MainActivity" android:exported ="true" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > <intent-filter > <action android:name ="android.intent.action.VIEW" /> <category android:name ="android.intent.category.DEFAULT" /> <category android:name ="android.intent.category.BROWSABLE" /> <data android:scheme ="https" android:host ="www.example.com" /> </intent-filter > </activity >
3. 处理深层链接 在目标可组合项中,可以通过 backStackEntry.arguments
获取传递的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 @Composable fun DetailsScreen (navController: NavController , userId: String ) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text("User ID: $userId " ) Button(onClick = { navController.navigateUp() }) { Text("Back to Home" ) } } }
4. 测试深层链接 你可以通过以下方式测试深层链接:
使用 adb 命令 :
bash复制
1 adb shell am start -d "https://www.example.com/details/123" -a android.intent.action.VIEW
在浏览器中打开链接 : 打开浏览器并访问 https://www.example.com/details/123
,应用应该会启动并导航到详情页面。
高级用法 如果你需要更复杂的深层链接处理,可以使用类型安全的深层链接。这需要定义一个数据类来表示深层链接的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Serializable data class Profile (val userId: String)@Composable fun MyApp () { val navController = rememberNavController() NavHost(navController = navController, startDestination = "home" ) { composable<Profile>( deepLinks = listOf( navDeepLink<Profile>(basePath = "https://www.example.com/profile" ) ) ) { backStackEntry -> ProfileScreen(userId = backStackEntry.toRoute<Profile>().userId) } } }
8.附带效果 LaunchedEffect
第一次调用Compose函数时被调用
DisposableEffect
页面退出时调用
sideEffect
compose函数每次执行都会调用该方法
1.LaunchedEffect
LaunchedEffect
的 key1
参数绑定到 showDialog
状态。当 showDialog
的值发生变化时,LaunchedEffect
会重新执行。
在 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 @Composable fun MessagesBox () { var showDialog by remember { mutableStateOf(true ) } LaunchedEffect(key1 = showDialog) { if (showDialog) { delay(3000 ) showDialog = false } } Scaffold( topBar = { TopAppBar(title = { Text(text = "rc" ) }) }, content = { padding -> Box( modifier = Modifier .fillMaxSize() .padding(padding), contentAlignment = Alignment.Center ) { Button(onClick = { showDialog = true }) { Text(text = "Click Me" ) } } } ) if (showDialog) { AlertDialog( onDismissRequest = { showDialog = false }, title = { Text("Dialog Title" ) }, text = { Text("This is a dialog message." ) }, confirmButton = { TextButton(onClick = { showDialog = false }) { Text("OK" ) } } ) } }
2.rememberCoroutineScope 主要作用:
生命周期绑定 :
rememberCoroutineScope
创建的 CoroutineScope
会与组合的生命周期绑定。当组合被销毁时,该作用域内的所有协程都会被自动取消,从而避免内存泄漏。
启动协程 :
在 Compose 中,你可以使用 rememberCoroutineScope
获取的 CoroutineScope
来启动协程,执行异步或耗时操作。
状态管理 :
通常与可变状态(如 mutableStateOf
)结合使用,通过协程更新 UI 状态,触发界面重组。
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 @Composable fun MyBoxed () { val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) val scope = rememberCoroutineScope() var showDialog by remember { mutableStateOf(false ) } ModalNavigationDrawer( drawerState = drawerState, drawerContent = { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Text(text = "test" , fontSize = 24. sp) } } ) { Scaffold( topBar = { TopAppBar( title = { Text(text = "rc" ) }, navigationIcon = { IconButton(onClick = { scope.launch { drawerState.open () } }) { Icon( imageVector = Icons.Default.List, contentDescription = "Open drawer" ) } } ) }, content = { padding -> Box( modifier = Modifier .fillMaxSize() .padding(padding), contentAlignment = Alignment.Center ) { Button(onClick = { showDialog = true }) { Text(text = "Click Me" ) } } if (showDialog) { AlertDialog( onDismissRequest = { showDialog = false }, title = { Text("Dialog Title" ) }, text = { Text("This is a dialog message." ) }, confirmButton = { TextButton(onClick = { showDialog = false }) { Text("OK" ) } } ) } }, floatingActionButton = { ExtendedFloatingActionButton( onClick = { }, icon = { Icon(Icons.Filled.Edit, "Extended FAB" ) }, text = { Text(text = "Extended FAB" ) }, ) }, ) } }
3.rememberUpdatedState
保证被使用时是最新值,参数被改变时不重启effect
捕获最新状态 :
在组合过程中,状态值可能会发生变化。rememberUpdatedState
会捕获最新的状态值,并在协程或其他异步操作中使用这个最新值。
避免状态不一致 :
如果直接在协程中使用组合时的状态值,可能会导致状态不一致的问题。rememberUpdatedState
确保协程中使用的是最新的状态值。
简化状态管理 :
它提供了一种简单的方式在协程中管理状态的更新,而不需要手动处理状态的捕获和更新。
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 @Composable fun RememberUpdatedStateExample () { var count by remember { mutableStateOf(0 ) } val currentCount = rememberUpdatedState(count) val coroutineScope = rememberCoroutineScope() Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = "Count: $count " , fontSize = 24. sp) Button( onClick = { coroutineScope.launch { delay(1000 ) println("Current count in coroutine: ${currentCount.value} " ) } count++ } ) { Text("Increment and Launch Coroutine" ) } } }
9.项目管理 占位符 为了更优雅的完成上述的问题,我们可以在strings.xml文件里,使用%s %1$s %d %1$d这些占位符,在页面设置展示的时候我们只需要将真实展示的文字或者数据替换掉就OK了。
占位符 类型 %s 替换String类型 %1$s 替换String类型 %d 替换int类型 %1$d 替换int类型
注:%s 、%d为缩写方式,只替换一个位置,可以这么写
单一占位符使用 在strings.xml定义字符串<string name="str">截止目前你已经消费了人民币%s元</string>
多个占位符使用 在strings.xml中定义<string name="str">大家好,我叫%1$s,我的爱好是%2$s</string>
在类中使用 String.format(mActivity.getResources().getString(R.string. str), "Rc","code"))
最终的输出结果:大家好,我叫Rc,我的爱好是code
在Compose中使用
在string.xml文件中定义<string name="item">this is a %1$s and %2$d</string>
1 Text(text = String.format(stringResource(id = R.string.item),"test" ,30 ))
显示到状态栏 //允许显示到状态栏内,文字显示为白色 enableEdgeToEdge(statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT))