Jetpack Compose 是个啥?为啥要学它?
谷歌对 Jetpack Compose 的定义:
Jetpack Compose 是推荐用于构建原生 Android 界面的新工具包。它可简化并加快 Android 上的界面开发,使用更少的代码、强大的工具和直观的 Kotlin API,快速打造生动而精彩的应用。
提取关键词:界面开发新工具包、简化并加快界面开发、Kotlin API。
对于大部分Android项目来说,如果基础库(如网络库、hybird、图片加载、热修复库等)已经搭好,那么平时大部分时间就是跟 UI界面、需求逻辑 打交道了,而谷歌提供的 Jetpack Compose 正好是加快界面开发的工具包 。
就跟魂斗罗里的子弹类型似的,使用普通子弹(XML方式)也可以通关,但是相比之下耗时更长;而换成超级子弹(Jetpack Compose)体验就不一样了,耗时更少,而且游戏体验更爽!
命令式UI vs 声明式UI
长期以来,Android 视图层次结构一直可以表示为界面 widget 树。最常见的界面更新方式是使用 findViewById()
等函数遍历树,并通过调用 button.setText(String)、container.addChild(View) 或 img.setImageBitmap(Bitmap) 等方法更改节点。这些方法会改变 widget 的内部状态,这种手动更新UI的方式即是命令式UI。
在过去的几年中,整个行业已开始转向声明性界面模型,该模型大大简化了与构建和更新界面关联的工程任务。该技术的工作原理是在概念上从头开始重新生成整个屏幕,然后仅执行必要的更改。此方法可避免手动更新有状态视图层次结构的复杂性。Compose 即是一个声明式UI框架。
Jetpack Compose要学起来了?
很遗憾,Jetpack Compose 确实要学起来了(快起来,你还能学!哈哈...),随着Jetpack Compose 版本的不断迭代,API 逐渐稳定了,性能也越来越好了。
- 更少的代码 :编写代码只需要采用
Kotlin
,而不用拆分成Kotlin + XML
方式了。 - 直观:只需描述界面,Compose会负责处理剩余工作。应用状态变化时,界面自动更新。
- 加速开发 :View 与 Compose 之间可以相互调用,兼容现有的所有代码。借助AS可以实时预览界面,轻松执行界面检查。
- 功能强大:直接访问Android API,内置对Material Design、主题、动画等的支持。
Jetpack Compose vs Flutter
- Jetpack Compose的目的是为了提高 Android 原生的 UI 开发效率!声明式UI已经成为主流的开发方式了,就像当初谷歌将Kotlin定为Android主流语言时我们学习Kotlin一样,未来Jetpack Compose 一定会是Android UI开发的主流方式。
- Flutter 的定位是多平台 UI 框架,优势在于跨平台。
大家很喜欢把Jetpack Compose 和 Flutter作对比,不知道该学哪一个?的确,某些场景下它们确实挺像的,而且还都是谷歌在推的。
个人理解是:如果你未来的主攻方向还是Android,那么无脑选择Jetpack Compose,虽然Compose目前也能实现跨端,但跨端目前看并不是它的主要工作;而如果你的方向是多平台开发,那么学习Flutter是首选吧。
另外,与其一直纠结学哪一个,不如直接上手亲身感受下它们的不同,正所谓 "纸上得来终觉浅,绝知此事要躬行"。
Jetpack Compose入门
kotlin
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text("Hello World!")
}
}
}
其中,setContent()传入一个@Composable作用域,其作用跟之前的setContentView()一样用来设置界面。Text()用来描述一个UI元素,里面有各种参数,这里我们只把文案填上去,执行结果:
一个最简单的功能就完成了。
1、@Composable 可组合函数
还是上述展示一个文本的功能,我们换一种写法:
kotlin
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MessageCard("Android")
}
}
}
@Composable
fun MessageCard(name: String) {
Text(text = "Hello $name!")
}
执行效果跟上面一样。唯一的区别就是把文本展示单独抽离到一个方法中了,并且该方法上面加了@Composable 注解:
@Composable注解用于标记一个函数为可组合函数 。可组合函数是一种特殊的函数,不需要返回任何UI元素,因为可组合函数描述的是所需的屏幕状态,而不是构造界面widget;而如果按我们以前的XML编程方式,必须在方法中返回UI元素才能使用它(如返回View类型)。
@Composable注解的函数之间可以相互调用,因为这样Compose框架才能正确处理依赖关系。另外,@Composable函数中也可以调用普通函数,而普通函数中却不能直接调用@Composable函数。 这里可以类比下kotlin中suspend挂起函数的用法,其用法是相似的。
几个定义:
- 组合:对 Jetpack Compose 在执行可组合项时所构建界面的描述。
- 初始组合:通过首次运行可组合项创建组合。
- 重组 :
在数据发生变化时重新运行可组合项以更新组合
。
可组合函数的特点:
- 使用同一参数多次调用此函数时,它的行为方式相同,并且它不使用其他值,如全局变量或对 random() 的调用。
- 此函数描述界面而没有任何副作用,如修改属性或全局变量、点击事件的处理等。当需要执行附带效应时,应通过回调触发。如:
kotlin
@Composable
private fun NamePickerItem(name: String, onClicked: (String) -> Unit) {
Text(name, Modifier.clickable(onClick = { onClicked(name) }))
}
可见通过回调,点击事件这个附带效应是在调用方触发的。
在编译时,Jetpack Compose会将标记为@Composable的函数编译成字节码,并生成一个专门的ComposeNode类来管理其状态和属性。这个类会自动处理依赖关系,并在需要时计算UI元素。这样,开发者就可以专注于编写UI逻辑,而不用担心状态管理和UI更新的细节。
这里引出一个问题,Compose 是如何做 UI 更新的呢 ?总不能每次有一小部分数据的变化,整个UI都要跟着刷新一次吧,那性能肯定差的要死。其实,当有数据变化时,Compose实现的是增量更新,只会重新绘制数据有改动的UI(该过程称为重组),数据没有改动的则不会重新绘制了。
2、布局基础知识
Compose 通过元素组合、布局、绘制之后可以将状态转换为UI元素。
组合
在 Compose 中,可以通过从可组合函数中调用其他可组合函数来构建界面层次结构。 如图所示:
- Column :可以将多个项垂直地放置在屏幕上;
- Row :可以将多个项水平地放置在屏幕上;
- Box :可将元素放在其他元素上,还支持为其包含的元素配置特定的对齐方式。
排列及对齐方式:
kotlin
/**
* @param verticalArrangement 竖直排列方式
* @param horizontalAlignment 水平对齐方式
*/
inline fun Column(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable ColumnScope.() -> Unit
) {...}
/**
* @param horizontalArrangement 水平排列方式
* @param verticalAlignment 竖直对齐方式
*/
@Composable
inline fun Row(
modifier: Modifier = Modifier,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
content: @Composable RowScope.() -> Unit
) {...}
/**
* @param contentAlignment 内容对齐方式
*/
@Composable
inline fun Box(
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.TopStart,
propagateMinConstraints: Boolean = false,
content: @Composable BoxScope.() -> Unit
) {...}
verticalArrangement、horizontalArrangement 排列方式及效果:
布局
界面树布局通过单次传递即可完成。父节点会在其子节点之前进行测量,但会在其子节点的尺寸和放置位置确定之后再对自身进行调整 。 当界面树较深时,Compose 可以通过只测量一次子项来实现高性能。
3、Modifier修饰符
可以通过Modifier修饰符更改可组合项的大小、布局、外观,还可以添加高级互动,例如使元素可点击等。如:
kotlin
Image(
painter = painterResource(id = R.mipmap.icon_water_melon),
contentDescription = "",
modifier = Modifier
.size(50.dp)
.clip(CircleShape)
.border(width = 2.dp, Color.Red, CircleShape)
)
执行结果:,原图本身是长方形的,通过Modifier修饰符修饰后,很容易变成圆角图片。想一下如果用XML方式来写,是不是要写好多代码呢。
4、存储状态
可组合函数中可以使用 remember 将本地状态存储在内存中,并跟踪传递给 mutableStateOf 的值的变化。该值更新时,系统会自动重新绘制使用此状态的可组合项(及其子项),这也是上面所说的重组。如:
kotlin
@Composable
fun MessageCard(msg: Message) {
// We keep track if the message is expanded or not in this variable
var isExpanded by remember { mutableStateOf(false) }
// We toggle the isExpanded variable when we click on this Column
Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {...}
}
当点击Column
元素时,每次都会重新执行MessageCard()可组合函数进行刷新,而通过remember和mutableStateOf可以保存了上次的isExpanded状态;如果不使用它们,重新执行 MessageCard() 时 isExpanded 也会重新初始化。
除了remember之外,还有rememberSaveable、savedStateHandle.saveable等。。。
总结
这篇文章主要讲了Compose是什么以及我们要开始学习它的必要性。作为Compose 第一篇介绍文章,本文旨在初步感受一下 Compose的能力,后续再详细研究 Compose 的精彩用法!
资料
【1】谷歌Jetpack Compose 教程 : https://developer.android.com/jetpack/compose/tutorial?hl=zh-cn