1.Composable函数
Compose的UI体系,就是通过层层嵌套的Composable函数
来构建的,布局是函数,控件也是函数,最终形成一个树形结构
。 普通方法加上@Composable
注解符就是Composable函数,Compose编译器会对此类函数做特殊的变换处理。 在Activity的onCreate中设置Compose的顶层方法setContent,它后面的lambda表达式就要求是Composable函数,如下:
kotlin
public fun ComponentActivity.setContent(
parent: CompositionContext? = null,
content: @Composable () -> Unit
) {
...
}
再看经常使用的竖直布局Column
,同样也是一个Composable函数,它的content内容也同样需要是Composable函数:
kotlin
@Composable
inline fun Column(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable ColumnScope.() -> Unit,
) {
...
}
最后再看Text
,它是Compose树中最后的叶子节点,它本身是一个Composable函数,但是它后面不会再有子内容了:
kotlin
@Composable
fun Text(
text: String,
modifier: Modifier = Modifier,
...
onTextLayout: ((TextLayoutResult) -> Unit)? = null,
style: TextStyle = LocalTextStyle.current
) {
...
}
2.重组和重组作用域
每个Composable函数在编译之后,都会形成一个recomposition scope
,也叫重组作用域,类型是ScopeUpdateScope
。 并且在ScopeUpdateScope的内部注册了重组调用方法,递归调用自己。 Compose源代码:
kotlin
@Composable
fun Greeting(msg: String) {
Text(text = "Hello $msg!")
}
编译之后的Java代码:
java
public static final void Greeting(final String msg, Composer $composer,
final int $changed) {
// 1,开始
// ↓
$composer = $composer.startRestartGroup(-1948405856);
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty = $changed | ($composer.changed(msg) ? 4 : 2);
}
if (($dirty & 11) == 2 && $composer.getSkipping()) {
$composer.skipToGroupEnd();
} else {
TextKt.Text-fLXpl1I(msg, $composer, 0, 0, 65534);
}
// 2,结束
// ↓
ScopeUpdateScope var10000 = $composer.endRestartGroup();
if (var10000 != null) {
var10000.updateScope((Function2)(new Function2() {
public final void invoke(@Nullable Composer $composer, int $force) {
// 3,递归调用自己
// ↓
MainActivityKt.Greeting(msg, $composer, $changed | 1);
}
}));
}
}
所以,重组
简单理解,就是重新执行一遍Composable函数
,并更新UI的值。 那什么时候发生重组呢? 答案是Composable函数内部的状态(State)发生改变,会触发重组机制
,更新 UI。这里的状态,我们可以简单理解为就是UI组件使用到的数据值。 Compose规定,当State发生改变的时候,不需要对整个UI树的函数全部重新执行,而只对State数值有关联的函数做重组即可,所以也叫做智能重组
。那么这个State改变影响到的这几个函数,我们就可以叫做本次State改变的重组作用域
。
3.MutableState
我们知道Compose是声明式UI,并且可以根据数据源的改变自动刷新界面,这一点与传统命令式View更新UI方法不同。 在Compose中,如果想要实现UI的自动刷新,首先需要感知数据的变化,这里就会用到MutableState
,它继承自State\<T>
。 MutableState官方的解释:
一个可变更数据的持有者,在Composable函数执行期间读取value值,并且重组作用域会订阅这个值。 当值被重写或者发生改变,重组作用域会发生重组;如果值没有变化,那么就不会发生重组。
我们看一个例子:
kotlin
@Composable
fun CounterComponent() {
Log.d(TAG, "scope-1")
var counter by remember { mutableStateOf(0) }
Column(modifier = Modifier.statusBarsPadding()) {
Log.d(TAG, "scope-2")
Button(
onClick = {
Log.d(TAG, "Button onClick")
counter ++
}) {
Log.d(TAG, "scope-3")
Text("数量+1")
}
Text("$counter")
Text2Refresh()
}
}
@Composable
fun Text2Refresh() {
Log.d(TAG, "scope-4")
Text("哈哈")
}
在点击Button的时候,会改变counter的值,并且刷新Text,打印结果如下:
Button onClick scope-1 scope-2
首先是有订阅,然后才会有重组。 订阅就发生在取值的地方
,本示例就是在$counter这里发生了对State状态的订阅。 同时在订阅的时候,也确定了这个State对应的重组范围,即包含订阅的非inline且无返回值的Composable函数或者lambda
。 上面的例子中,对State取值订阅是在Column这个Composable函数里面实现的,但是由于Column是一个inline函数,所以不能成为本次重组作用域,只能向上一层扩展到CounterComponent这个Composable函数。 同时由于Text2Refresh这个Composable函数内部并没有对State取值,所以这个函数不在本次重组范围内。Button这个组件也是同理,没有状态的更新,并没有发生重组。 MutableState的底层是一套snapshot**快照系统
,它会保存State的状态,并通过 getter订阅,setter触发更新。
4.remember
上面的例子中,我们在定义counter数据的时候,还使用了remember。它的作用保证在初始化的时候才会执行一次MutableState的初始化,重组的时候返回的是MutableState的缓存值
,而不是再次初始化。 这样的特性符合我们的预期,不然每次点击Button的时候,counter的值都是0。
5.mutableStateListOf
当我们相对Collection集合做增删操作,并希望也能自动重组更新UI的时候。 举个例子,我们希望在按钮点击之后,就增加一条列表的Item,如果使用如下方式:
kotlin
@Composable
fun ColumnComponent() {
val items by remember { mutableStateOf(mutableListOf("one", "two", "three")) }
Column {
for (item in items) {
Text(item)
}
Button(onClick = {items.add("new item")}) {
Text("增加item")
}
}
}
会发现并没有发生重组,列表的Item没有增加。 这是因为我们对items集合只是做的add操作,并没有重新赋值做setter操作。而mutableStateOf创建的MutableState,是要求必须在setter的时候,才会触发重组
,所以这里没有触发重组。
实现这种需求,要使用mutableStateListOf
,它的解释如下:
Create an instance of MutableList that is observable and can be snapshot.
也就是创建一个可以被订阅并且可以使用Compse快照机制的集合
。 改成如下方式,就可以实现对集合增删的重组了:
kotlin
@Composable
fun ColumnComponent() {
val items = remember { mutableStateListOf("one", "two", "three") }
Column {
for (item in items) {
Text(item)
}
Button(onClick = {items.add("new item")}) {
Text("增加item")
}
}
}
注意,之前的by
改成了这里的"="赋值符号,这是由于mutableStateListOf返回的SnapshotStateList根本没有getter和setter,不能属性委托;并且它本身就是一个列表,已经实现了State的可观察,不需要委托。
6.状态上提
可以简单理解为,把控件的状态参数化,外部持有状态数据,通过外部传入参数的方式影响控件的状态
。 比如:
kotlin
@Composable
fun Text2Refresh() {
Log.d(TAG, "scope-4")
Text("哈哈")
}
这个控件,内部的状态(这里主要是显示的文字数据),外部没办法去获取状态。 状态提升的方式如下:
kotlin
@Composable
fun Text2Refresh(name : String) {
Log.d(TAG, "scope-4")
Text(name)
}
状态上提,有利于数据的单向流通,以及控件的复用。但状态上提而需要看具体情况,不是必要选择。