Compose 修饰符 - 原理

本文围绕Android中Compose的Modifier展开。介绍了Modifier的使用场景、添加参数的规则及底层结构原理。还阐述了通过它修改外观(尺寸、样式等)、添加额外信息、交互功能(点击、滚动等)以及处理用户输入的方法,帮助开发者更好地运用Modifier。

一、概念

在 NodeKind.kt 文件中根据功能对 Modifier 类型进行了分类。

1.1 什么时候用到

三大使用场景:

  • 修改外观(尺寸、样式、布局、行为)。详见
  • 添加交互功能(点击、滚动、拖拽、缩放)。详见
  • 添加额外信息(如无障碍标签)。

1.2 为组合函数添加 Modifier 参数

任何一个组合项都应该有一个 Modifier 参数,以便让调用方进行调整。

  • 放在第一个可选参数位置:由于是可选参数,放在所有必传参数后面,这样调用方就可以选择是否传递那些有默认值的可选参数,否则就必须被强制性的先指定Modifier。 
  • 作用于内部根节点上:调用方一般只需要调整根节点的布局,对于子元素别的配置可通过传递其它的参数。
  • 避免重复使用:将同一个 Modifier 传递给不同的可组合共享,可能引起不必要的重组。
  • 使用时设置的效果会覆盖掉传入的Modifier效果:调用方将设置过 size 的 Modifier 传入,内部使用的时候时候又设置了 size,这样就会覆盖掉传入的效果,即可组合项内部设置的效果无法被外部控制。
@Composable
fun ParentLayout(modifier: Modifier = Modifier) {
    //调用时指定对齐方式
    Avatar(Modifier.align(Alignment.CenterHorizontally))
}

@Composable
fun Avatar(modifier: Modifier = Modifier) {
    Image(
        painter = painterResource(id = R.drawable.icon),
        contentDescription = "Icon Image",
        //使用时,用传入的modifier
        modifier = modifier
            .wrapContentSize()
            .background(Color.Gray)
            .padding(18.dp)
            .border(5.dp, Color.Magenta, CircleShape)
            .clip(CircleShape)
    )
}
@Composable
fun TestComposable(a: Int, b: String, modifier: Modifier = Modifier) {...}

1.3 底层结构原理

Layout 阶段, 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 可以用作链式调用的开头。
CombinedModifier

class CombinedModifier(
    internal val outer: Modifier,        //当前的 Element
    internal 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)。 

1.3.2 自定义 Modifier

//自定义测量和摆放
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控件之前携带的一些额外信息都清除掉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值