15.1 Compose 源码解析之测量、布局(一)

921 阅读5分钟

从分析 Touch 事件分发就可以看出 AndroidComposeView 是原生 View 体系和 Compose 的中间件。原生 View 体系中的事件传递到 AndroidComposeView ,AndroidComposeView 再传递到 AndroidComposeView.root 的 Compose 树中开启 Compose 事件处理流程。

62E15D1B-B588-4CCF-9BF2-9D5E17A38F1E.png Compose 测量流程也是如此

06E14B97-0E4F-43C2-80AE-DD893D6ADE43.png

结合上图可以看出 Compose 测量流程从 AndroidComposeView#onMeasure() 方法开始:

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        trace("AndroidOwner:onMeasure") {
            if (!isAttachedToWindow) {
                invalidateLayoutNodeMeasurement(root)
            }
            val (minWidth, maxWidth) = convertMeasureSpec(widthMeasureSpec)
            val (minHeight, maxHeight) = convertMeasureSpec(heightMeasureSpec)
			//计算最大最小宽高
            val constraints = Constraints(minWidth, maxWidth, minHeight, maxHeight)
            if (onMeasureConstraints == null) {
                // first onMeasure after last onLayout
                onMeasureConstraints = constraints
                wasMeasuredWithMultipleConstraints = false
            } else if (onMeasureConstraints != constraints) {
                // we were remeasured twice with different constraints after last onLayout
                wasMeasuredWithMultipleConstraints = true
            }
          	//设置 Compose 树根节点 最大最小宽高 约束 
            measureAndLayoutDelegate.updateRootConstraints(constraints)
          	//对 Compose 树进行测量、布局 
            measureAndLayoutDelegate.measureAndLayout()
          	//设置 AndroidComposeView 测量后的宽高
            setMeasuredDimension(root.width, root.height)
          	//如果 AndroidComposeView 存在 Android 原生 View 
            if (_androidViewsHandler != null) {
              	//对 Android 原生 View 进行测量
                androidViewsHandler.measure(
                    MeasureSpec.makeMeasureSpec(root.width, MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(root.height, MeasureSpec.EXACTLY)
                )
            }
        }
    }
internal class AndroidComposeView(context: Context) :
    ViewGroup(context), Owner, ViewRootForTest, PositionCalculator, DefaultLifecycleObserver {
      
    override val root = LayoutNode().also {
        it.measurePolicy = RootMeasurePolicy
        it.modifier = Modifier
            .then(semanticsModifier)
            .then(_focusManager.modifier)
            .then(keyInputModifier)
        it.density = density
    }
      
    private val measureAndLayoutDelegate = MeasureAndLayoutDelegate(root)
}

在 AndroidComposeView#onMeasure() 中 Compose 不在使用原生 View 的测量方法,而是由  MeasureAndLayoutDelegate 代理去完成整个 Compose 树的测量和布局。自此测量流程从 View 体系转入 Compose 体系。

为了更好的分析分析测量流程我们需要先对 LayoutNode 有所了解(接下来只分析跟测量流程相关的部分)

Compose 函数转换成 LayoutNode

Compose 中的 LayoutNode 相当于原生 View 体系中的 View , 所有的 Compose 组件都会转换成 LayoutNode 对象添加到 root 为根节点的 Compose 树中。

Compose 中提供的组件都是基于 Layout 或 SubcomposeLayout 函数实现的。

  • Layout :封装 ReusableComposeNode 函数,子组件测量时受 Layout 约束影响。例如:Column 、Row
  • SubcomposeLayout : 封装 ComposeNode 函数,子组件测量时除了受 SubcomposeLayout 约束影响外,测量结果还可以影响其他子组件测量。例如 LazyColumn

约束: LayoutNode 的最大/最小宽高 , 在 Constraints.kt 中定义。

ReusableComposeNode 和 ComposeNode 都通过 Composer 将组件转换成 LayoutNode 添加到 Compose 树中,转换的主要流程的是一样的,具体差异我们先不去研究。

以 Layout 为例来分析转换过程

@Suppress("ComposableLambdaParameterPosition")
@Composable inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    val density = LocalDensity.current
    val layoutDirection = LocalLayoutDirection.current
    val viewConfiguration = LocalViewConfiguration.current
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
      	//() -> ComposeUiNode = LayoutNode.Constructor
      	//factory 函数就是 LayoutNode 构造函数
        factory = ComposeUiNode.Constructor,
      	//执行时会依次设置 LayoutNode 中下面四个属性的值
        update = {
            set(measurePolicy, ComposeUiNode.MeasurePolicy)
            set(density, ComposeUiNode.SetDensity)
            set(layoutDirection, ComposeUiNode.SetLayoutDirection)
            set(viewConfiguration, ComposeUiNode.SetViewConfiguration)
        },
        // materializerOf -> set(materialized, ComposeUiNode.SetModifier)
        // 执行时设置 LayoutNode 中 modifier 属性的值
        skippableUpdate = materializerOf(modifier),
        //@Composable () -> Unit 子组件的 Compose 函数
        content = content
    )
}

//LayoutNode 实现 ComposeUiNode 接口, 上面代码执行时就是 LayoutNode 调用对应的方法
@PublishedApi
internal interface ComposeUiNode {
    var measurePolicy: MeasurePolicy
    var layoutDirection: LayoutDirection
    var density: Density
    var modifier: Modifier
    var viewConfiguration: ViewConfiguration

    companion object {
      	// LayoutNode.Constructor 
      	// internal val Constructor: () -> LayoutNode = { LayoutNode() }
        val Constructor: () -> ComposeUiNode = LayoutNode.Constructor
        val SetModifier: ComposeUiNode.(Modifier) -> Unit = { this.modifier = it }
        val SetDensity: ComposeUiNode.(Density) -> Unit = { this.density = it }
        val SetMeasurePolicy: ComposeUiNode.(MeasurePolicy) -> Unit =
            { this.measurePolicy = it }
        val SetLayoutDirection: ComposeUiNode.(LayoutDirection) -> Unit =
            { this.layoutDirection = it }
        val SetViewConfiguration: ComposeUiNode.(ViewConfiguration) -> Unit =
            { this.viewConfiguration = it }
    }
}

上面只是传参调用,下面是 ReusableComposeNode 函数的执行流程

@Composable
inline fun <T : Any?, reified E : Applier<*>> ReusableComposeNode(
    noinline factory: () -> T,
    update: @DisallowComposableCalls Updater<T>.() -> Unit,
    content: @Composable () -> Unit
) {
    if (currentComposer.applier !is E) invalidApplier()
    currentComposer.startReusableNode()
    if (currentComposer.inserting) {
      	//执行 LayoutNode 构造方法并将生成的对象添加到 Compose 树中
        currentComposer.createNode(factory)
    } else {
        currentComposer.useNode()
    }
    currentComposer.disableReusing()
  	//执行 update 方法设置 LayoutNode 对象的四个属性
    Updater<T>(currentComposer).update()
    currentComposer.enableReusing()
  	//设置 LayoutNode 对象的 modifier 属性
    SkippableUpdater<T>(currentComposer).skippableUpdate()
    currentComposer.startReplaceableGroup(0x7ab4aae9)
  	//执行子组件的 Compose 函数
    content()
    currentComposer.endReplaceableGroup()
    currentComposer.endNode()
}

转换过程主要分为下面四个步骤:

  1. 生成 LayoutNode 对象,并添加到 Compose 树中
  2. 设置 LayoutNode 对象的 measurePolicy、density、layoutDirection、viewConfiguration 属性
  3. 设置 LayoutNode 对象的 modifier 属性
  4. 继续转换子组件的 Compose 函数(基础组件例如 BasicText 或 Image 的 content 都是空函数)

1 生成 LayoutNode :调用无参构造生成 LayoutNode 对象,完成对象初始化。

MeasurePolicy

接口中声明了五个方法。其中四个方法有默认实现,作用是支持固有特性测量返回对应的最大/最小宽高。

具体的测量策略需要重写 MeasureScope.measure   方法,测量子元素与容器本身的大小并决定如何在容器中布局子元素。

    fun MeasureScope.measure(
      	//实现 Measurable 接口的子元素,一般是 LayoutNode
        measurables: List<Measurable>, 
      	//容器最大/最小宽高约束
        constraints: Constraints  
    ): MeasureResult //测量后容器的大小,及子元素布局函数

自定义布局

前面提到过 Compose 中提供的组件都是基于 Layout 或 SubcomposeLayout 函数实现的。 通过下面四个步骤自定义一个简单的自动换行标签容器来熟悉 MeasurePolicy 使用。

  1. 根据传入的约束(constraints)测量子元素(measurables)的大小
  2. 根据约束和子元素的大小确定容器(Layout)本身的大小
  3. 编写子元素在容器中布局的函数
  4. 将容器大小和布局函数封装到 MeasureResult 中返回

编码时会将 3、4 步使用 layout() 函数合并在一起。

@Composable
fun TagLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
    Layout(modifier = modifier, content = content) { measurables, constraints ->
        var usedWidth = 0
        var height = 0
        //1 :测量子组件,每个子组件测量后会返回该组件的 placeable 对象
        val placeables = measurables.mapIndexed { index, measurable ->
            val placeable = measurable.measure(constraints)
            // 2 :根据子组件的测量值计算 TagLayout 的 高度,TagLayout 宽度使用容器的最大宽度
            if (index == 0) {
                height = placeable.measuredHeight
                usedWidth = placeable.measuredWidth
            } else {
                //一行放不下 ,放到下一行 ,累加容器高度
                if (usedWidth + placeable.measuredWidth > constraints.maxWidth) {
                    usedWidth = 0
                    height += placeable.measuredHeight
                } else {
                    usedWidth += placeable.measuredWidth
                }
            }
            placeable
        }
        //3、4 :使用 layout 函数,传入测量后的宽(constraints.maxWidth) 高(height)
        //及子元素布局函数(lambda)生成返回的 measureResult
        val measureResult = layout(constraints.maxWidth, height) {
            var xOffset = 0
            var yOffset = 0
            placeables.forEach {
                //如果需要换行将 xOffset,yOffset 设置成下一行起始的位置 ,
                if (xOffset + it.measuredWidth > constraints.maxWidth) {
                    xOffset = 0
                    yOffset += it.measuredHeight
                }
                //将子组件左顶点放置到(x,y)处,参数 x ,y 是相对于容器左顶点(0,0)的偏移量
                it.placeRelative(xOffset, yOffset)
                // xOffset 累加
                xOffset += it.measuredWidth
            }
        }

        measureResult
    }
}
@Composable
fun Tag(text:String){
    val bgColor = Color.Magenta
    Text(modifier = Modifier.padding(2.dp).background(bgColor, RoundedCornerShape(2.dp))
        .padding(6.dp, 4.dp)
        ,text = text, color = contentColorFor(bgColor))
}
        setContent {
            TagLayout(modifier = Modifier.background(Color.Cyan)) {
                Tag(text = "MeasurePolicy")
                Tag(text = "constraints")
                Tag(text = "placeables")
                Tag(text = "measureResult")
                Tag(text = "layout")
            }
        }

463B71FB-C3F0-44A2-8428-4F1F6949B0B8.png

SetMeasurePolicy

接下来我们看 Compose 函数转换成 LayoutNode 时 setMeasurePolicy 都做了什么

internal class LayoutNode : Measurable, Remeasurement, OwnerScope, LayoutInfo, ComposeUiNode {
  	// set measurePolicy 相关
    internal val intrinsicsPolicy = IntrinsicsPolicy(this) 
    override var measurePolicy: MeasurePolicy = ErrorMeasurePolicy
        set(value) {
            if (field != value) {
                field = value
                intrinsicsPolicy.updateFrom(measurePolicy)
                requestRemeasure()
            }
        }    
}

LayoutNode 初始化时会实例化 IntrinsicsPolicy 对象, IntrinsicsPolicy 对象将 LayoutNode 对象当前的 MeasurePolicy 保存到 State 中,提供 updateFrom() 方法更新 State 的值, 使得 LayoutNode 对象的 MeasurePolicy 变化后可以触发重组机制。

internal class IntrinsicsPolicy(val layoutNode: LayoutNode) {
    private var measurePolicyState: MutableState<MeasurePolicy>? = null

    private var pendingMeasurePolicy: MeasurePolicy? = null

    fun updateFrom(measurePolicy: MeasurePolicy) {
        if (measurePolicyState != null) {
            measurePolicyState!!.value = measurePolicy
        } else {
            pendingMeasurePolicy = measurePolicy
        }
    }
	//......
}

下一篇我们继续分析 Modifier 和 Compose 函数转化成 LayoutNode 后 SetModifier 都做了什么