一、概念不可变性每个Modifier操作如padding/background都会返回一个新的Modifier原Modifier不变避免副作用。链式调用通过链式调用连接多个修饰符按“从左到右”的顺序累加生效顺序影响最终效果。装饰者模式每个修饰符不修改组件本身而是包装组件添加额外能力如clickable为组件添加点击事件。单一职责每个修饰符只做一件事如padding仅处理边距background仅处理背景便于组合和复用。在 NodeKind.kt 文件中根据功能对 Modifier 类型进行了分类。1.1 什么时候用到三大使用场景修改外观尺寸、样式、布局、行为。详见添加交互功能点击、滚动、拖拽、缩放。详见添加额外信息如无障碍标签。1.2 调用顺序优化每一个 Modifier 都是对前一个结果的再包装调用顺序的不同直接影响最终效果。先通过布局类型明确大小位置 → 再通过绘制类型进行内容装修 → 最后通过交互类型让成本响应用户操作。遵循这个顺序确保组件行为符合直觉且性能最高。布局类型size/padding/weight等大小align/offset等位置。绘制类型background/border/clip等。交互类型clickable/draggable/focusable等。1.2.1 减少布局计算用 remember() 缓存需要复杂计算的尺寸/位置避免每次布局阶段重复计算。val screenWidth LocalConfiguration.current.screenWidthDp.dp val itemWidth by remember(screenWidth) { derivedStateOf { screenWidth * 0.8f } } Box(modifier Modifier.size(itemWidth)) {}1.2.2 使用带 Lambda 参数版本的 Modifier 读取状态应该尽量避免在 Modifier 修饰符上读取状态因为它并不是用来显示数据的地方。标准的 Modifier 函数是一定会在组合阶段被执行的当状态变化时会重新创建 Modifier 实例UI树会先删除旧的再添加新的实例而UI树的变化会导致重组每次重组都可能触发 组合→布局→绘制 三个阶段若读取的是一个频繁变化的状态非常要命动画或滚动。val color by animateColorBetween(Color.Blue, Color.Red) Box( modifier Modifier.background(color) //Box必须在每一帧上重组因为每一帧的颜色值都在变化 )有时不可避免需要一直读取一个不断变化的值从而达到变化的绘制效果。带 Lambda 参数版本的 Modifier 函数不会在组合阶段被执行根据修改类型延迟到布局阶段或绘制阶段Modifier实例不会改变因此UI树不会变化Compose只会在需要的时候调用 Lambda也就可以跳过不必要的重组了。Composable fun Demo() { var offsetX by remember { mutableStateOf(0f) } val modifier1 Modifier.offset(x offsetX.dp) //直接读取 val modifier2 Modifier.offset{ val newX offsetX.dp } //在Lambda中读取 }1.2.3 合并图形操作 graphicsLayer()绘制类型的修饰符底层都是使用 Modifier.graphicsLayer() 实现的都会创建一个图形层例如应用 alpha 效果保存 (Save)当前的绘制状态。创建一个新的、独立的 Layer (离屏缓冲区)。把内容绘制到这个新 Layer 上。对整个 Layer 应用alpha效果。把修改后的 Layer 绘制回主画布。恢复 (Restore)原始的绘制状态。每一次 save 和 restore 都是有成本的。当把这些绘制类型的修饰符分开写时就相当于在反复支付这个“图层税”。使用 Modifier.graphicsLayer() 相当于批处理一次性完成多种图形变换减少GPU过度绘制避免重复计算。由于只是绘制当它读取状态时请求的是 GPU 重绘完全绕过了 Kotlin 代码的执行逻辑。这几乎是 0 CPU 开销。graphicsLayer()fun Modifier.graphicsLayer(block: GraphicsLayerScope.() - Unit): ModifierGraphicsLayerScopeinterface GraphicsLayerScope : Density {var scaleX: Float //X轴缩放var scaleY: Float //Y轴缩放var alpha: Float //透明度var translationX: Floatvar translationY: Floatvar shadowElevation: Float //阴影高度var ambientShadowColor: Colorvar spotShadowColor: Colorvar rotationX: Float //X轴旋转角度var rotationY: Float //Y轴旋转角度var rotationZ: Float //Z轴旋转角度var cameraDistance: Float //查看图层的距离拉远拉进var transformOrigin: TransformOriginvar clip: Boolean //是否裁切var shape: Shape //形状var renderEffect: RenderEffect? //高斯模糊var blendMode: BlendMode //混合模式var colorFilter: ColorFilter? //色彩滤镜var compositingStrategy: CompositingStrategyval size: Size //尺寸}错误示范三种绘制类型修饰符分别创建了独立的图层并且 RoundedCornerShape(12.dp) 这个形状的路径计算也可能被重复执行了多次。modifier Modifier //Layer1透明度 .alpha(0.5F) //Layer2裁剪 .clip(RoundedCornerShape(12.dp)) //Layer3阴影 .shadow(8.dp, RoundedCornerShape(12.dp))正确使用modifier Modifier .graphicsLayer { alpha 0.5F, shadowElevation 8.dp.toPx(), shape RoundedCornerShape(12.dp), clip true, renderEffect BlurEffect(10f, 10f) } .background(Colors.red) //背景会被绘制在上面创建的Layer内部并自动被裁剪1.3 为组合函数添加 Modifier 参数任何一个组合项都应该有一个 Modifier 参数以便让调用方进行调整。放在第一个可选参数位置由于是可选参数放在所有必传参数后面这样调用方就可以选择是否传递那些有默认值的可选参数否则就必须被强制性的先指定 Modifier 才能传必传参数。作用于内部根节点上调用方一般只需要调整根节点的布局对于内部子元素的配置可通过传递其它的参数来配置。避免重复使用将同一个 Modifier 传递给不同的可组合共享可能引起不必要的重组。同名覆盖问题通过 then() 可以定义组件内部配置和外部传入配置的顺序。对尺寸的设置参考 Constrains详见一般都会用具体值fillMaxSize、100.dp因此以第一个为准约束范围写死了最大值最小值都是100dp后续再调用不会生效。offset 多次调用会多次偏移。background 默认情况下只看得到后一个如果多次调用之间存在 padding、 offset 就不会完全覆盖可以看到之前的颜色。Composable fun Out() { //调用时指定对齐方式 In(Modifier.background(Color.Red)) } Composable fun In( text: String, modifier: Modifier Modifier, //放在第一个可选参数位置 enable: Boolean true ) { // 传入的 Modifier 作用域根部节点 Box( // 举例一这里把外部配置放在了最前面最终显示 Blue modifier modifier .background(Color.Blue) // 举例二这里把外部配置放在了后面最终显示 Red modifier Modifier .background(Color.Blue) .then(modifier) ) }1.3 底层结构原理Modifier底层是一个链表结构每个修饰符节点包含Element具体操作和next下一个修饰符Compose在测量/布局/绘制组件时会遍历修饰符链表依次应用每个修饰符的逻辑。Modifier#then 创建 Element 加入 Modifier chain 中。Element 是无状态的重组中会重新生成Element 会在组合中创建有状态的 Modifier Node。Modifier Node 有状态重组中仅当状态发生变化时被更新否则不会重新生成。Modifier Node 是 Compose 1.5 引入的新优化目的就是通过存储 Modifier 状态参与比较提升重组性能。interface Modifier {interface Element : Modifier {...}companion object : Modifier {...}class CombinedModifier {...}fun then() {...}}fun Modifier.composed() {...}Element 子接口调用 Modifier 不同的配置方法会返回各种 Modifier 的实现类对象如 .size() 返回 SizeElement、.background() 返回 BackgroundElement这些实现类又都是 Element 类型。companion 伴生对象伴生对象实现了 Modifier因此类名 Modifier 可以用作链式调用的开头。CombinedModifierclass CombinedModifier(internal val outer: Modifier, //当前的 Elementinternal val inner: Modifier //新增的 Element) : Modifier内部维护的数据结构用于连接调用链中的每个 Element 结点类似于俄罗斯套娃一样的装饰者模式。then() 函数用于连接两个 Element 的方法底层就是用的 CombinedModifier 结构。composed() 扩展函数fun Modifier.composed(inspectorInfo: InspectorInfo.() - Unit NoInspectorInfo,factory: Composable Modifier.() - Modifier): Modifier内部持有一个工厂 Lambda 来生产 Modifier用于实现内部有状态的 Modifier如监听手势的修饰符 .pointerInput() 底层就是用到 composed()。1.3.1 链式调用Modifier.size(100.dp).background(Color.Red).padding(10.dp)当链式调用 Modifier 的时候先调用的会包裹后调用的最里层是 Layout Node。调用 .size() 生成 SizeElement。调用 .background() 生成 CombinedModifier(outer SizeElement, inner BackgroundElement)。调用 .padding() 生成 CombinedModifier(outer CombinedModifier(SizeElement, BackgroundElement) inner PaddingElement)。二、自定义 Modifier优化2.1 提取重复使用的效果将相同的效果修饰符链提取到变量中以便在多个组件中重复使用这样做有助于提高代码可读性还有助于提升应用性能。对使用修饰符的可组合项进行重组时不会重新分配修饰符。修饰符链可能很长并且非常复杂因此重复使用相同的链实例可以减轻 Compose 运行时在对比时所需的工作量。这种提取方式可提高整个代码库中的代码简洁性、一致性和可维护性。2.1.1 简单使用注意有些修饰符限定了作用域只能在特定场景下使用如 BoxScope 的 matchParentSize()val reusableModifier Modifier .fillMaxWidth() .background(Color.Red) .padding(12.dp) Composable fun Demo() { Box(modifier reusableModifier) Box(modifier reusableModifier) }2.1.2 观察频繁变化的状态时在组件中观察频繁变化的状态时如动画状态、scrollState可能引发大量重组在这种情况下Modifier 会在每次重组时创建实例这可能发生在动画的每一帧。可以提取 Modifier 到可组合项外面来重用。val reusableModifier Modifier .padding(12.dp) .background(Color.Gray) Composable fun Demo() { val animatedState animateFloatAsState(/*...*/) LoadingWheel( // 无需分配内存因为只是重复使用同一个实例 modifier reusableModifier, animatedState animatedState ) }2.1.3 延迟布局条目复用延迟布局会大量创建条目复用 Modifier 可以优化内存。val reusableItemModifier Modifier .padding(bottom 12.dp) .size(216.dp) .clip(CircleShape) Composable private fun Demo(authors: ListAuthor) { LazyColumn { items(authors) { // 无需分配内存因为我们只是重复使用同一个实例。 AsyncImage(modifier reusableItemModifier) } } }2.1.4 进一步链接提取的修饰符通过调用 then() 进一步链接或附加提取的修饰符链。val reusableModifier Modifier .fillMaxWidth() .background(Color.Red) .padding(12.dp) // 追加到自己的修饰符后面 reusableModifier.clickable { /*...*/ } // 追加到别人的后面 otherModifier.then(reusableModifier)2.2 自定义带参数的修饰符fun Modifier.cardStyle( backgroundColor: Color Color.White, cornerRadius: Dp 8.dp, elevation: Dp 4.dp ): Modifier { return this .background(backgroundColor, shape RoundedCornerShape(cornerRadius)) .shadow(elevation, shape RoundedCornerShape(cornerRadius)) .padding(16.dp) }2.3 自定义 Element//自定义测量和摆放 fun Modifier.XXX(): Modifier then( layout { measurable, constraints - //TODO... } } //去掉涟漪效果 fun Modifier.clickableNoRipple(onClick: () - Unit): Modifier composed { clickable( onClick onClick, interactionSource remember { MutableInteractionSource() }, indication null ) }三、添加额外信息在Compose的内部是用树型结构来存储一次重组过程中每个Composable函数节点的。一颗就是我们现在看到的重组树另外一颗则是我们看不到的语义树。语义树完全不参与绘制和渲染工作因此是完全不可见的它只为 Accessibility 和 Test 服务。Accessibility需要根据语义树的节点内容进行发音Test则需要根据语义树找到想要测试的节点来执行测试逻辑。绝大部分情况下不需要专门为语义树去做什么事情标准的组合项已经在内部处理好了这些工作Button嵌套一个Text它俩是独立控件Talkback会单独发声但只要控件可点击就会自动将所有子节点合并。若使用了一些底层API自行绘制界面日历选中8号只会发音选中日历这些工作就得自己来做了。.semantics(mergeDescendants: Boolean false,properties: (SemanticsPropertyReceiver.() - Unit))允许向当前Compose控件添加键值对形式的额外信息但是不能覆写。.clearAndSetSemantics(properties: (SemanticsPropertyReceiver.() - Unit))相对用得更多一些它会把Compsoe控件之前携带的一些额外信息都清除掉。
Compose 修饰符 - 原理
一、概念不可变性每个Modifier操作如padding/background都会返回一个新的Modifier原Modifier不变避免副作用。链式调用通过链式调用连接多个修饰符按“从左到右”的顺序累加生效顺序影响最终效果。装饰者模式每个修饰符不修改组件本身而是包装组件添加额外能力如clickable为组件添加点击事件。单一职责每个修饰符只做一件事如padding仅处理边距background仅处理背景便于组合和复用。在 NodeKind.kt 文件中根据功能对 Modifier 类型进行了分类。1.1 什么时候用到三大使用场景修改外观尺寸、样式、布局、行为。详见添加交互功能点击、滚动、拖拽、缩放。详见添加额外信息如无障碍标签。1.2 调用顺序优化每一个 Modifier 都是对前一个结果的再包装调用顺序的不同直接影响最终效果。先通过布局类型明确大小位置 → 再通过绘制类型进行内容装修 → 最后通过交互类型让成本响应用户操作。遵循这个顺序确保组件行为符合直觉且性能最高。布局类型size/padding/weight等大小align/offset等位置。绘制类型background/border/clip等。交互类型clickable/draggable/focusable等。1.2.1 减少布局计算用 remember() 缓存需要复杂计算的尺寸/位置避免每次布局阶段重复计算。val screenWidth LocalConfiguration.current.screenWidthDp.dp val itemWidth by remember(screenWidth) { derivedStateOf { screenWidth * 0.8f } } Box(modifier Modifier.size(itemWidth)) {}1.2.2 使用带 Lambda 参数版本的 Modifier 读取状态应该尽量避免在 Modifier 修饰符上读取状态因为它并不是用来显示数据的地方。标准的 Modifier 函数是一定会在组合阶段被执行的当状态变化时会重新创建 Modifier 实例UI树会先删除旧的再添加新的实例而UI树的变化会导致重组每次重组都可能触发 组合→布局→绘制 三个阶段若读取的是一个频繁变化的状态非常要命动画或滚动。val color by animateColorBetween(Color.Blue, Color.Red) Box( modifier Modifier.background(color) //Box必须在每一帧上重组因为每一帧的颜色值都在变化 )有时不可避免需要一直读取一个不断变化的值从而达到变化的绘制效果。带 Lambda 参数版本的 Modifier 函数不会在组合阶段被执行根据修改类型延迟到布局阶段或绘制阶段Modifier实例不会改变因此UI树不会变化Compose只会在需要的时候调用 Lambda也就可以跳过不必要的重组了。Composable fun Demo() { var offsetX by remember { mutableStateOf(0f) } val modifier1 Modifier.offset(x offsetX.dp) //直接读取 val modifier2 Modifier.offset{ val newX offsetX.dp } //在Lambda中读取 }1.2.3 合并图形操作 graphicsLayer()绘制类型的修饰符底层都是使用 Modifier.graphicsLayer() 实现的都会创建一个图形层例如应用 alpha 效果保存 (Save)当前的绘制状态。创建一个新的、独立的 Layer (离屏缓冲区)。把内容绘制到这个新 Layer 上。对整个 Layer 应用alpha效果。把修改后的 Layer 绘制回主画布。恢复 (Restore)原始的绘制状态。每一次 save 和 restore 都是有成本的。当把这些绘制类型的修饰符分开写时就相当于在反复支付这个“图层税”。使用 Modifier.graphicsLayer() 相当于批处理一次性完成多种图形变换减少GPU过度绘制避免重复计算。由于只是绘制当它读取状态时请求的是 GPU 重绘完全绕过了 Kotlin 代码的执行逻辑。这几乎是 0 CPU 开销。graphicsLayer()fun Modifier.graphicsLayer(block: GraphicsLayerScope.() - Unit): ModifierGraphicsLayerScopeinterface GraphicsLayerScope : Density {var scaleX: Float //X轴缩放var scaleY: Float //Y轴缩放var alpha: Float //透明度var translationX: Floatvar translationY: Floatvar shadowElevation: Float //阴影高度var ambientShadowColor: Colorvar spotShadowColor: Colorvar rotationX: Float //X轴旋转角度var rotationY: Float //Y轴旋转角度var rotationZ: Float //Z轴旋转角度var cameraDistance: Float //查看图层的距离拉远拉进var transformOrigin: TransformOriginvar clip: Boolean //是否裁切var shape: Shape //形状var renderEffect: RenderEffect? //高斯模糊var blendMode: BlendMode //混合模式var colorFilter: ColorFilter? //色彩滤镜var compositingStrategy: CompositingStrategyval size: Size //尺寸}错误示范三种绘制类型修饰符分别创建了独立的图层并且 RoundedCornerShape(12.dp) 这个形状的路径计算也可能被重复执行了多次。modifier Modifier //Layer1透明度 .alpha(0.5F) //Layer2裁剪 .clip(RoundedCornerShape(12.dp)) //Layer3阴影 .shadow(8.dp, RoundedCornerShape(12.dp))正确使用modifier Modifier .graphicsLayer { alpha 0.5F, shadowElevation 8.dp.toPx(), shape RoundedCornerShape(12.dp), clip true, renderEffect BlurEffect(10f, 10f) } .background(Colors.red) //背景会被绘制在上面创建的Layer内部并自动被裁剪1.3 为组合函数添加 Modifier 参数任何一个组合项都应该有一个 Modifier 参数以便让调用方进行调整。放在第一个可选参数位置由于是可选参数放在所有必传参数后面这样调用方就可以选择是否传递那些有默认值的可选参数否则就必须被强制性的先指定 Modifier 才能传必传参数。作用于内部根节点上调用方一般只需要调整根节点的布局对于内部子元素的配置可通过传递其它的参数来配置。避免重复使用将同一个 Modifier 传递给不同的可组合共享可能引起不必要的重组。同名覆盖问题通过 then() 可以定义组件内部配置和外部传入配置的顺序。对尺寸的设置参考 Constrains详见一般都会用具体值fillMaxSize、100.dp因此以第一个为准约束范围写死了最大值最小值都是100dp后续再调用不会生效。offset 多次调用会多次偏移。background 默认情况下只看得到后一个如果多次调用之间存在 padding、 offset 就不会完全覆盖可以看到之前的颜色。Composable fun Out() { //调用时指定对齐方式 In(Modifier.background(Color.Red)) } Composable fun In( text: String, modifier: Modifier Modifier, //放在第一个可选参数位置 enable: Boolean true ) { // 传入的 Modifier 作用域根部节点 Box( // 举例一这里把外部配置放在了最前面最终显示 Blue modifier modifier .background(Color.Blue) // 举例二这里把外部配置放在了后面最终显示 Red modifier Modifier .background(Color.Blue) .then(modifier) ) }1.3 底层结构原理Modifier底层是一个链表结构每个修饰符节点包含Element具体操作和next下一个修饰符Compose在测量/布局/绘制组件时会遍历修饰符链表依次应用每个修饰符的逻辑。Modifier#then 创建 Element 加入 Modifier chain 中。Element 是无状态的重组中会重新生成Element 会在组合中创建有状态的 Modifier Node。Modifier Node 有状态重组中仅当状态发生变化时被更新否则不会重新生成。Modifier Node 是 Compose 1.5 引入的新优化目的就是通过存储 Modifier 状态参与比较提升重组性能。interface Modifier {interface Element : Modifier {...}companion object : Modifier {...}class CombinedModifier {...}fun then() {...}}fun Modifier.composed() {...}Element 子接口调用 Modifier 不同的配置方法会返回各种 Modifier 的实现类对象如 .size() 返回 SizeElement、.background() 返回 BackgroundElement这些实现类又都是 Element 类型。companion 伴生对象伴生对象实现了 Modifier因此类名 Modifier 可以用作链式调用的开头。CombinedModifierclass CombinedModifier(internal val outer: Modifier, //当前的 Elementinternal val inner: Modifier //新增的 Element) : Modifier内部维护的数据结构用于连接调用链中的每个 Element 结点类似于俄罗斯套娃一样的装饰者模式。then() 函数用于连接两个 Element 的方法底层就是用的 CombinedModifier 结构。composed() 扩展函数fun Modifier.composed(inspectorInfo: InspectorInfo.() - Unit NoInspectorInfo,factory: Composable Modifier.() - Modifier): Modifier内部持有一个工厂 Lambda 来生产 Modifier用于实现内部有状态的 Modifier如监听手势的修饰符 .pointerInput() 底层就是用到 composed()。1.3.1 链式调用Modifier.size(100.dp).background(Color.Red).padding(10.dp)当链式调用 Modifier 的时候先调用的会包裹后调用的最里层是 Layout Node。调用 .size() 生成 SizeElement。调用 .background() 生成 CombinedModifier(outer SizeElement, inner BackgroundElement)。调用 .padding() 生成 CombinedModifier(outer CombinedModifier(SizeElement, BackgroundElement) inner PaddingElement)。二、自定义 Modifier优化2.1 提取重复使用的效果将相同的效果修饰符链提取到变量中以便在多个组件中重复使用这样做有助于提高代码可读性还有助于提升应用性能。对使用修饰符的可组合项进行重组时不会重新分配修饰符。修饰符链可能很长并且非常复杂因此重复使用相同的链实例可以减轻 Compose 运行时在对比时所需的工作量。这种提取方式可提高整个代码库中的代码简洁性、一致性和可维护性。2.1.1 简单使用注意有些修饰符限定了作用域只能在特定场景下使用如 BoxScope 的 matchParentSize()val reusableModifier Modifier .fillMaxWidth() .background(Color.Red) .padding(12.dp) Composable fun Demo() { Box(modifier reusableModifier) Box(modifier reusableModifier) }2.1.2 观察频繁变化的状态时在组件中观察频繁变化的状态时如动画状态、scrollState可能引发大量重组在这种情况下Modifier 会在每次重组时创建实例这可能发生在动画的每一帧。可以提取 Modifier 到可组合项外面来重用。val reusableModifier Modifier .padding(12.dp) .background(Color.Gray) Composable fun Demo() { val animatedState animateFloatAsState(/*...*/) LoadingWheel( // 无需分配内存因为只是重复使用同一个实例 modifier reusableModifier, animatedState animatedState ) }2.1.3 延迟布局条目复用延迟布局会大量创建条目复用 Modifier 可以优化内存。val reusableItemModifier Modifier .padding(bottom 12.dp) .size(216.dp) .clip(CircleShape) Composable private fun Demo(authors: ListAuthor) { LazyColumn { items(authors) { // 无需分配内存因为我们只是重复使用同一个实例。 AsyncImage(modifier reusableItemModifier) } } }2.1.4 进一步链接提取的修饰符通过调用 then() 进一步链接或附加提取的修饰符链。val reusableModifier Modifier .fillMaxWidth() .background(Color.Red) .padding(12.dp) // 追加到自己的修饰符后面 reusableModifier.clickable { /*...*/ } // 追加到别人的后面 otherModifier.then(reusableModifier)2.2 自定义带参数的修饰符fun Modifier.cardStyle( backgroundColor: Color Color.White, cornerRadius: Dp 8.dp, elevation: Dp 4.dp ): Modifier { return this .background(backgroundColor, shape RoundedCornerShape(cornerRadius)) .shadow(elevation, shape RoundedCornerShape(cornerRadius)) .padding(16.dp) }2.3 自定义 Element//自定义测量和摆放 fun Modifier.XXX(): Modifier then( layout { measurable, constraints - //TODO... } } //去掉涟漪效果 fun Modifier.clickableNoRipple(onClick: () - Unit): Modifier composed { clickable( onClick onClick, interactionSource remember { MutableInteractionSource() }, indication null ) }三、添加额外信息在Compose的内部是用树型结构来存储一次重组过程中每个Composable函数节点的。一颗就是我们现在看到的重组树另外一颗则是我们看不到的语义树。语义树完全不参与绘制和渲染工作因此是完全不可见的它只为 Accessibility 和 Test 服务。Accessibility需要根据语义树的节点内容进行发音Test则需要根据语义树找到想要测试的节点来执行测试逻辑。绝大部分情况下不需要专门为语义树去做什么事情标准的组合项已经在内部处理好了这些工作Button嵌套一个Text它俩是独立控件Talkback会单独发声但只要控件可点击就会自动将所有子节点合并。若使用了一些底层API自行绘制界面日历选中8号只会发音选中日历这些工作就得自己来做了。.semantics(mergeDescendants: Boolean false,properties: (SemanticsPropertyReceiver.() - Unit))允许向当前Compose控件添加键值对形式的额外信息但是不能覆写。.clearAndSetSemantics(properties: (SemanticsPropertyReceiver.() - Unit))相对用得更多一些它会把Compsoe控件之前携带的一些额外信息都清除掉。