Android Jetpack Compose总结
简介
Jetpack Compose 是用于构建native UI的新方式,写法跟Flutter非常相似,对Flutter有了解的同学可以很快上手。
官网:https://developer.android.com/jetpack/compose
官方demo :https://github.com/android/compose-samples
官方的介绍: https://developer.android.com/jetpack/compose/setup
环境及版本
最低支持Android API 21,即5.0版本,必须使用kotlin语言,最低使用Android Studio 4.0 版本。
Jetpack Compose 目前处于实验阶段,现在是0.1-dev2,到1.0正式版估计还要一年时间。
后续版本可能会加入更多kotlin的特性,丰富动画等其他性能问题。
关于如何在现有项目中使用:
https://developer.android.com/jetpack/compose/setup#add-compose
怎么使用?
在AS 4.0中直接新建一个空的Compose项目,会有一个示例代码:
在函数前加@Compose
注解,就可以返回一个类似Flutter中的Widget的UI
加@Compose
注解的函数可以相互调用,这些函数会被插件编译处理,所以如果一个函数不是生成UI的,那么不要用此注解。
@Preview
注解,可以在右边实时预览,改动函数后,刷新一个预览即可,添加该注解的外层函数不能有参数,但是里面可以嵌套一个带参数的函数来预览。可以在@Preview
后面添加一个名字,如:@Preview("Text preview")
关于Column 和Row 的概念跟Flutter中一样,包括主轴和副轴的概念大小如mainAxisSize
和对齐方式如crossAxisAlignment
,一段代码示例:
@Composablefun MyScreenContent( names: List<String> = listOf("Android", "there"), counterState: CounterState = CounterState()) { Column(crossAxisAlignment = CrossAxisAlignment.Center crossAxisSize = LayoutSize.Expand, mainAxisSize = LayoutSize.Expand) { for (name in names) { Greeting(name = name) Divider(color = Color.Black) } Divider(color = Color.Transparent, height = 32.dp) Counter(counterState) }}@Preview("MyScreen preview")@Composablefun DefaultPreview() { MyApp { MyScreenContent() }}
可以使用HeightSpacer(24.dp)
或者WeightSpacer(24.dp)
来直接添加一个宽高间隔
按照官方的建议,我们可以把UI拆分成多个小的Compose函数,每个函数其实最终会被插件编译生成一个View,然后可以复用这些Compose函数,
@Composablefun MyScreenContent( names: List<String> = listOf("Android", "there"), counterState: CounterState = CounterState()) { Column(modifier = ExpandedHeight, crossAxisAlignment = CrossAxisAlignment.Center) { Column(modifier = Flexible(1f), crossAxisAlignment = CrossAxisAlignment.Center) { for (name in names) { Greeting(name = name) Divider(color = Color.Black) } } Counter(counterState) }}
在Column中,可以对参数modifier设置ExpandedHeight,类似于设置高度match_parent的意思,宽度同理。
关于如何使用Theme和自定义Theme
MaterialTheme中有很多颜色和字体样式,在外层包裹上MaterialTheme后,可以在内部的Compose函数中使用主题数据,如:style = +themeTextStyle { h1 }
@Composablefun Greeting(name: String) { Text ( text = "Hello $name!", modifier = Spacing(24.dp), style = +themeTextStyle { h1 } color = +themeColor { surface } )}
通过使用copy函数可以在现有的一个主题上修改某一个属性值,如:
textStyle = (+themeTextStyle { body1 }).copy(color = Color.Yellow)
自定义Theme
import androidx.compose.Composable@Composablefun CustomTheme(children: @Composable() () -> Unit) { // TODO }
import androidx.compose.Composableimport androidx.ui.core.CurrentTextStyleProviderimport androidx.ui.graphics.Colorimport androidx.ui.material.MaterialColorsimport androidx.ui.material.MaterialThemeimport androidx.ui.text.TextStyleval green = Color(0xFF1EB980.toInt())val grey = Color(0xFF26282F.toInt())private val themeColors = MaterialColors( primary = green, surface = grey, onSurface = Color.White)@Composablefun CustomTheme(children: @Composable() () -> Unit) { MaterialTheme(colors = themeColors) { val textStyle = TextStyle(color = Color.Red) CurrentTextStyleProvider(value = textStyle) { children() } }}
Effects和memo
memo的作用:
1. 在recompositions(即该UI组件内部的Model数据变化时,该UI组件就会重新构建)的时候保存状态值,如下代码:
@Composablefun MyScreenContent( names: List<String> = listOf("Android", "there"), counterState: CounterState = CounterState()) { ... }
上面的代码有一个问题,再重新构建的时候,原来的counterState数值就会丢失,每次都是一个新的counterState对象。
按照下面使用memo修改后,就可以解决问题:
@Composablefun MyScreenContent( names: List<String> = listOf("Android", "there"), counterState: CounterState = +memo { CounterState() }) { ... }
2. 在重组时,记住内部的一些计算结果,防止多次重复计算
如果在合成的中间需要进行计算,而又不想在每次重新组合函数时都进行计算,则可以记住该计算,即使重新组合了Composable函数,该计算也不会再次执行。
@Composablefun Greeting(name: String) { val formattedName = +memo { name.substringBefore(" ").toUpperCase() } Text ( text = "Hello $formattedName!", modifier = Spacing(24.dp), style = +themeTextStyle { h3 } )}@Preview@Composablefun DefaultPreview() { MaterialTheme { Greeting("Android 10") }}
比如这里的formattedName计算过程,在使用memo后,就不会重复计算,但是这样写有个bug,如果第二次调用时传入来了另外一个参数,那么由于memo复用原来的结果,就会导致bug,所以,对于需要修改的参数,可以以如下的方式来使用memo:
@Composablefun Greeting(name: String) { val formattedName = +memo(name) { name.substringBefore(" ").toUpperCase() } Text ( text = "Hello $formattedName!", modifier = Spacing(24.dp), style = +themeTextStyle { h3 } )}
@Model注解
model注解标记一个数据类之后,在Compose函数中可以直接监听到数据变化,自动更新显示,
如:
定义:
@Model class CounterState(var count: Int = 0)
使用:
@Composable fun Counter(state: CounterState) { Button( text = "I've been clicked ${state.count} times", onClick = { state.count++ } )}
状态提升、 数据流向下传递、事件流向上传递
@Modelclass FormState(var optionChecked: Boolean)@Composablefun Form(formState: FormState) { Checkbox( checked = formState.optionChecked, onCheckedChange = { newState -> formState.optionChecked = newState })}
在上面代码中,Checkbox的选中状态,在Checkbox和Form中都不保存,而改为由外部传入,原因是此时外部可能需要使用当前的状态值,那么由外部来创建并传递该参数到Compose函数中,这使得外部调用者提升了状态
⚠️注意:在可组合函数中,应该公开可能对调用函数有用的状态,因为这是可以使用或控制的唯一方法,称为状态提升。
状态提升的概念跟Flutter一样,后续应该也会像Flutter中的Provider、BLOC、或者Redux一样,推出相关的状态管理库,因为Compose + Model注解的方式,就是一种MVVM的思想,需要一种方便的数据状态管理的三方库来做这个事情。
关于数据流向: 父Composable函数可以控制其子数据。 子Compose UI不应从全局变量或全局数据存储中读取。Composable函数应仅接收所需信息,因此它们应尽可能简单,而不是调用父Composable函数可以提供的所有内容。
@Composablefun MultipleGreetings(user: User = +memo { User("name", "surname") }) { Column { Greeting("${user.firstName} ${user.lastName}") Greeting("Android 10") Button(text = "Change name", onClick = { user.firstName = "Changed" user.lastName = "New surname" }) }}@Composablefun Greeting(name: String) { val formattedName = +memo(name) { name.substringBefore(" ").toUpperCase() } Text ( text = "Hello $formattedName!", modifier = Spacing(24.dp), style = +themeTextStyle { h3 } )}
比如上面代码中,Greeting从调用方Compose函数(MultipleGreetings)获取数据,作为参数传入,且Greeting只接收一个String,并不是整个User对象。
事件向上传递
事件通过lambda回调而不断往上。 当子Composable函数收到事件时,更改应传播回至关心该信息的Composable。
在我们的示例中,我们可以通过将Greeting的内容包装在以onClick侦听器为参数的Clickable函数(可在库中使用)中来使其可点击。 但是,Greeting是一个可重用的功能,它本身并不知道如何处理用户交互。 应该使用lambda将该信息从层次结构的底部(Greeting中的Clickable composable)传播到顶部的Composable函数,这些函数知道如何处理该信息,如以下示例所示:
@Composablefun MultipleGreetings(user: User = +memo { User("name", "surname") }) { val onClick = { user.firstName = "Changed" } Column { Greeting("${user.firstName} ${user.lastName}", onClick) Greeting("Android 10", onClick) Button(text = "Change name", onClick = onClick) }}@Composablefun Greeting(name: String, onClick: () -> Unit) { val formattedName = +memo(name) { name.substringBefore(" ").toUpperCase() } Clickable(onClick = onClick) { Text ( text = "Hello $formattedName!", modifier = Spacing(24.dp), style = +themeTextStyle { h3 } ) }}
Greeting通过调用父级作为参数传递的lambda告诉MultipleGreetings它被单击了。 如果您运行该应用程序,则可以看到在任何问候语文本上进行点击都会传播更改,并且顶部的Greeting实例将重新组合。
Data flow in Compose apps. Data flows down with parameters, events flow up with lambdas.
Compose和现有的View互操作
Compose写的函数可以用在xml中,Android现有的View也可以用Compose的方式来写,如:
总结: Compose借鉴了Flutter和Swift UI的编写方式,代码简洁,可以实时预览效果,截止到2019年11月19日,目前版本才为0.1,预计正式发布1.0后,会有更多功能更新,日常的一个小demo可以先使用Compose熟悉起来。
参考:
https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#0
牛逼!Android Jetpack Compose UI组件库最新进展,写法完全类似Flutter
Android Studio 4.0 最新进展,这几个新功能可太牛逼了!
更多相关文章
- android中 检查网络连接状态的变化,无网络时跳转到设置界面
- android实现标题栏、状态栏图标文字颜色及背景动态变化
- 修改状态栏颜色和状态栏字体颜色
- 【总结备用】Android监听网络状态实现(BroadcastReceiver + Serv
- android 如何实现EditText从不可编辑状态变成可变成可编辑状态
- android如何判断服务是否正在运行状态
- android 状态栏提醒 Notification 的使用!
- Android沉浸式状态栏(包含 小米、魅族)
- android Toolbar的使用结合状态栏与返回键