使用Compose编写ui界面
Jetpack Compose 1.0.0正式版于2021年7.28号发布,现在最新版本已经是1.2.0-alpha03。
文档:https://developer.android.com/jetpack/compose/why-adopt?hl=zh-cn
官方课程:https://developer.android.com/courses/pathways/compose?hl=zh-cn
Compose 编程思想
Jetpack Compose 是一个适用于 Android 的新式声明性界面工具包。首先说一下什么是声明式,一般来说,声明式是相对于命令式来说的。命令式就是根据指令来改变状态,拿安卓来说从findViewById()找到组件,然后通过textView.setText(String)设置数据手动改变ui的状态和显示。
声明式就是组件不以对象的方式提供,主要通过描述我们需要的需要展示的状态,状态改变的时候使用之前的描述重新更新ui,自动订阅数据的改变,不需要手动更新,当然现在的声明式的编程比如SwiftUI,React,Vue都是他们自身或者编译器提供了一些方式更新ui的时候只更新需要更新的部分,不会整个重新计算渲染。
关于改变界面的状态界面重新构建的更新,在React采用了VirtualDom的思想,自己实现了Diffing算法去实现局部刷新,以提高性能。
Compose 没有使用树形结构,而是在 Gap Buffer 这样线性结构上进行 diff。但本质上是相同的,可以将 Gap Buffer 理解为一个树形结构经 DFS 处理后的数组,数组单元通过 key 标记其在树上的位置信息。
Compose 在编译期为 Composable 生成带有位置信息的 key,存入到 Gap Buffer 数组的对应位置。运行时可以根据 key 来识别 Composable 节点是否发生了位置变化,以决定是否参与重组。
databing和compose的区别,databing只能更新界面的值,compose可以更新界面的任意内容,包括界面的结构。
Compose 性能

目前从测试的结果来看,compose的性能并不能比上xml布局的,原因也是各有各的说法,有的是说compose没有AOT,目前跑的JIT形式,还有的是说compose这类高度依赖计算的方式,加之kotlin中函数还不是一等公民,初始化布局时,大量的对象创建和堆栈调用,慢也不奇怪。
不过随着后面的发展,Google对compose的优化肯定是越来约好的,再一个,我们写普通的界面来说影响并不大,多列表的我们还是可以使用RecyclerView,因为compose可以方便在旧项目里面使用起来。
Compose 常用布局和组件
compose里面有三大基础容器布局组件
Column,Row可以类比LinearLayout,Box类比FrameLayout,LazyColumn和LazyRow类比RecyclerView,不过目前来说性能没有RecyclerView好。
Modifier修饰符
借助修饰符,以修饰可组合项,更改其行为、外观,添加无障碍功能标签等信息,处理用户输入,甚至添加高级交互(例如使某些元素可点击、可滚动、可拖动或可缩放)。
可以将它们分配给变量并重复使用,也可以将多个修饰符逐一串联起来,以组合这些修饰符。
串联修饰符时请务必小心,因为顺序很重要。由于修饰符会串联成一个参数,所以顺序将影响最终结果。
如下面一个简单的示例:
Compose 中的 State

先来看看在之前的view体系里面,我们一般更新一个ui是怎么操作的。
首先来看直接使用api的方式的更新
1 | class HelloCodelabActivity : AppCompatActivity() { |
后面推出了ViewModel,再来看使用ViewModel的方式的更新
1 | class HelloCodelabViewModel: ViewModel() { |
Compose版本
1 |
|
Compose 应用程序通过调用可组合函数将数据转换为 UI。如果您的数据发生更改,Compose 会使用新数据重新执行这些函数,创建更新的 UI — 这称为重新组合。Compose 还会查看单个可组合项需要哪些数据,以便它只需要重组数据已更改的组件,并跳过重组未受影响的组件。
对于上面的代码来说,意思就是如果用户输入的内容变了,会再次的调用HelloInput方法。这样的话,例如如下的代码,就算我们在Text里面输入了文本,界面也不会有任何的变化。
1 |
|
如上面的代码,OutlinedTextField是一个文本输入框,上面的代码,我们在输入框里面输入任何的内容,界面也不会有变化,这是因为,TextField 不会自行更新,但会在其 value 参数更改时更新。这是因 Compose 中组合和重组的工作原理造成的。
想要有变化,我们需要在onValueChange回调里面把我们输入的值赋值给OutlinedTextField的value,跟上面Compose版本的示例那样。
如上面所说如果您的数据发生更改,Compose 会使用新数据重新执行这些函数,创建更新的 UI,这样的话,我们假如有变量想在Composable修饰的函数里面使用,那么我们应该怎么做呢?
举一个简单的例子,假如我们有一个TextView,一个Input输入框,我们想要在输入框没有文字的时候隐藏TextView,有内容的时候TextView显示输入框的内容,我们会想到写出如下的代码:
1 |
|
按照compose的原理,每次界面改变,都会重新执行HelloContent方法,按照上面的代码,就算我们输入了内容,给name赋值了,OutlinedTextField里面的value变了,要展示界面的话又会重新执行HelloContent这样,name又变成了空字符串。
所以想要在Composable函数里面记住变量需要使用特别的方式。不过compose已经为我们提供好了对应的方式。
可组合函数(Composable函数)可以使用 remember 可组合项记住单个对象。系统会在初始组合期间将由 remember 计算的值存储在组合中,并在重组期间返回存储的值。remember 既可用于存储可变对象,又可用于存储不可变对象。
mutableStateOf 会创建可观察的 MutableState
在可组合项中声明 MutableState 对象的方法有三种:
1 | val mutableState = remember { mutableStateOf(default) } |
这些声明是等效的,以语法糖的形式针对状态的不同用法提供。
上面示例里面的name换如下形式即可。
1 | var name by remember { mutableStateOf("") } |
虽然 remember 可帮助您在重组后保持状态,但不会帮助您在配置更改(页面旋转方向之类的)后保持状态。为此,您必须使用 rememberSaveable。rememberSaveable 会自动保存可保存在 Bundle 中的任何值。对于其他值,您可以将其传入自定义 Saver 对象。
Jetpack Compose 并不要求您使用 MutableState
Compose 中的 实际应用
基于以上的知识点我们其实就可以开始开发一些简单的需求,像自定义layout,动画这些都是可以在用到的时候去学习的。