什么是Compose
Google于2019年在I/O大会上首次公开发布了Compose。
android 官网 主推UI框架
响应式数据驱动,MVI架构中的UI层框架
特点
- 声明式编程模型 - 通过Kotlin代码描述界面应该是什么样子
- 基于Kotlin - Compose使用Kotlin语言构建界面,提供了惯用的Kotlin函数和编程方式。
- 响应式数据驱动(Remember) - 界面结构和状态保存在可变数据类中,状态变更驱动界面更新。
- 增量构建界面 - 通过布局组件的嵌套组合来构建界面层级结构。
- 可交互和动画友好 - 内置可组合的动画和手势系统。
- 实时预览与热重载 - 支持直接在编辑界面时实时预览,修改即可立即看到效果。
- 可在现有项目中使用 - 可以与View系统一起使用,无需全重写即可使用Compose。
- 多平台 - 未来可扩展到更多平台,如Android/iOS/Web等
声明式UI
声明式UI可以理解为一种概念或者是一种如何描述布局样式的行为。
下面是Android 布局演变过程
- 传统命令式代码:
EditText usernameInput = new EditText(context);
usernameInput.setHint("Username");
layout.addView(usernameInput);
缺点:
1、UI复杂时臃肿,不易理解
2、如果需要更新View ,会导致 view对象到处传递
3、不能实时刷新效果
- XML布局:
xml 在传统View对象上,通过解析 转换,做了一些优化,加载时,通过读取布局文件,显示UI,在某种意义上XML算准声明式了
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="你好"/>
</LinearLayout>
缺点:
1、XML着重于声明静态的文档结构,而不是应用的动态视图
2、XML需要手动处理视图的更新,不是响应式自动更新的。
3、XML作为标记语言,语义表达能力较弱,不能附加逻辑业务
4、XML编写的界面与应用逻辑严重耦合(需要再Activity里面findViewById)。
- Compose
@Composable
fun EditeText() {
var value by remember{
mutableStateOf("")
}
BasicTextField(value = value, onValueChange = {
value = it
})
}
1、界面是状态函数的映射关系。
2、界面响应状态自动更新。
3、界面语义表达能力强,语法清晰。
4、界面与逻辑解耦。
5、支持实时预览编辑,可快速配置数据效果。
核心概念-数据重组
在 Jetpack Compose中, UI更新由两个核心完成。
重组机制: 用于维护数据的生命周期与状态变更。
@Composable
fun helloCompose(){
//重组
val hello by remember { mutableStateOf("Hello Compose~") }
//UI
Text(
text = hello,
)
}
对于重组,着重从两个方法切入理解 remember() 和 mutableStateOf()
remember
由于Compose语法的设计,所有组件都是需要被@Composable修饰的方法,但是方法内的变量会随着方法进出栈被重置,所以Compose提供了remember() 方法,用来记住组件内的这些状态。
@Composable
inline fun <T> remember(crossinline calculation: @DisallowComposableCalls () -> T): T =
currentComposer.cache(false, calculation)
可以看得出来 remembe() 是被inLine修饰的高阶函数,最终将参数内的方法,cache到Composer中。
主要作用:
- 记忆化结果-可以记忆计算函数的结果,避免重复计算。
- 避免状态值错乱-避免状态在重新组合时被改变
- 优化性能-可以跳过无用的重复计算和状态更改引起的重组。
- 控制组合生命周期-可以通过使用LaunchedEff(),手动控制组合函数的创建和销毁生命周期。
State
在重组机制中,remember 负责数据与组件之间的映射,而state负责数据的保存和状态维护。
fun <T> mutableStateOf(
value: T,
policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
): MutableState<T> = createSnapshotMutableState(value, policy)
可以看得出来 mutableStateOf() 除了需要被维护的数据作为参数以外,还需要一个可变快照的策略的参数,通过两个参数 返回一个MutableState,关于快照系统 后面单独再写一篇文章详细介绍吧,这里就不过多说了。
State主要作用:
1、保存组件的数据状态-State对象用于在组件内部保存数据状态。
2、驱动界面更新-当State中的数据变化时,会触发重新组合流程,使界面保持同步更新。
3、避免重复繁琐的状态管理代码
通过封装State对象,可以避免手动管理状态带来的大量模板代码。
4、实现声明式编程模型
State与UI语义上的声明式 mapping 关系,而不是命令式更新。
5、优化性能
框架可以根据State的变化优化增量更新,而不是全部重绘。
6、线程安全的数据容器
State对象本身是不可变的,可以安全地在不同线程访问。
7、实现Undo/Redo
State的变更记录可以方便实现撤销重做功能。
8、集中管理组件的状态
将多个状态属性封装在一个State对象中,便于统一管理。
核心概念-组件UI
上面笼统的讲了一下Compose中数据的维护,那作为UI框架,最重要的一定是绘制,布局。
由于Compose设计的初衷是为了跨平台的,所以UI层的绘制,会和部分原生布局系统有关,但相对是隔离的,因为没看过其他平台代码,以下只针对Android-Compose的部分类,接口介绍,由于整个UI架构比较特殊,短短篇幅不能完全介绍完,后面会在单独出一篇文章,系统性的来介绍一下Compose是如何跨平台的。
对于传统XML,整个布局系统是一棵树,而Compose是通过各个Node进行连接,由于子节点可以被多个父节点共享,所以不是严格的树结构。
构成整个UI页面一共有几个关键接口Applier,ComposeUiNode,Modifier
- Applier 负责持有组件树的根节点,并负责调度组件树的组合工作。
- ComposeUiNode 作为组件树的节点,包含了单个组件的状态信息和内容。
- Modifier 被组件持有,用来声明性地配置一个组件的属性、布局等信息
Applier 接口
在Android UI系统里,Applier的具体实现类是UiApplier,主要负责连接组合系统和绘制系统的关键类,负责整个UI的组合、更新以及绘制控制。
- 持有组件树:UiApplier中持有组成UI的组件树以及其根节点。
- 分发组合:UiApplier负责调用组件树的根节点进行组合,将组合工作分发到整棵组件树。
- 收集组合结果:组合完成后,UiApplier会收集到组件树的组合结果,也就是组件的状态、内容等信息。
- 生成绘制列表:UiApplier会根据组件树的组合结果,生成用于绘制的绘制指令列表。
- 触发重组:当检测到需要重组时,UiApplier会清理旧的组件树并重新触发组合工作。
- 控制绘制:最终UiApplier会根据绘制列表输出内容到目标绘制界面。
ComposeUiNode 接口
ComposeUiNode 接口的具体实现类是LayoutNode, 用于存储布局中主要信息,例如modifier、layoutDirection、density等
- 保存布局参数:LayoutNode会保存组件的对齐方式、边距、尺寸、位置等布局参数。
- 实现布局逻辑:不同的LayoutNode会有不同的布局排列逻辑,如线性布局、约束布局等。
- 提供布局上下文:LayoutNode作为一个布局节点,关联所有子元素并提供布局过程中的上下文。
- 协调布局流程:LayoutNode会负责调用子节点的测量和布局逻辑,协调其布局流程。
- 计算子元素布局:LayoutNode根据布局逻辑、约束条件计算出子元素的位置和大小。
- 保存布局结果:LayoutNode在布局完成后会保存子元素的布局相关位置信息。
- 标记脏节点:在参数变更时LayoutNode会标记脏以请求重布局。
- 控制绘图:LayoutNode根据布局结果控制对应UI元素的绘制。
- 响应参数变化:LayoutNode会在参数变化时通知父节点请求重新布局。 所以LayoutNode是布局系统中的关键单元,负责具体的布局逻辑实现和过程控制。
Modifer 接口
ConstraintLayout(modifier = Modifier
.padding(12.dp)
.fillMaxWidth()
.background(color = Color.White, shape = RoundedCornerShape(size = 16.dp))
.padding(12.dp)
.clickable {
}) {
}
Modifer 在整个系统里面,有很多的实现类,这里就不详细讲解某一个了。
对于Node而言,Modifier的存储会更加具体,Node 针对整个布局节点的配置,其中包含Modifier, 而Modifier 会更加细节,例如:颜色,size,背景,边距等。
主要作用:
- 修改组件属性:可以通过 Modifier 来修改组件的大小、边距、填充、边框等显示属性。
- 应用转换:可以通过 Modifier 应用调整组件的缩放、旋转、偏移等转换。
- 设置绘制层级:可以通过 Modifier 改变组件的绘制层级。
- 添加交互:可以使用 Modifier 给组件添加点击、长按、拖拽等交互。
- 应用约束:可以通过 Modifier 给组件添加各种布局约束条件。
- 应用动画:可以通过 Modifier 给组件设置动画。
- 创建自定义Modifier:可以继承 BaseModifier 类来创建自定义的 Modifier。
- 组合修改器:可以使用 Modifier.compose() 来合并多个 Modifier 的效果。
- 改变绘制顺序:可以通过 Modifier 改变组件的绘制顺序。