Compose Measure魔法

570 阅读7分钟

1、compose只允许测量一次,那么他是怎么准确的测量view的大小呢?

2、compose中各种Modifier他是怎么影响view的尺寸的?

在自定义Compose的layout或者组合view中其实比传统的view写法上要简单不少,比如

@Composable
fun GridView(rows: Int, modifier: Modifier = Modifier
    .padding()
    .offset()//代码1, content: @Composable () -> Unit) {
    Layout(content = content, modifier = modifier, measurePolicy = object : MeasurePolicy {
        override fun MeasureScope.measure(
            measurables: List<Measurable>,
            constraints: Constraints
        ): MeasureResult {
            val rowHeights = IntArray(rows) { 0 }
            val rowWidths = IntArray(rows) { 0 }

            val viewMeasures = measurables.mapIndexed { index, measurable ->
                val viewMeasure = measurable.measure(constraints)
                
                viewMeasure
            }
            val width = rowWidths.maxOrNull()
                ?.coerceIn(constraints.minWidth.rangeTo(constraints.maxWidth))
                ?: constraints.maxWidth


            val height =
                rowHeights.sum().coerceIn(constraints.minHeight.rangeTo(constraints.maxHeight))
            return layout(width, height) {
            }
        }
    })
}

这是个大概的自定义layout的代码,其中测量先关的主要是在measurePolicy中控制,那modifier主要有什么作用呢?在解决这个问题之前先看下

infix fun then(other: Modifier): Modifier =
    if (other === Modifier) this else CombinedModifier(this, other)

这个方法这是把所有的Modifier组合起来的函数,例如像代码1处的代码他是把Padding,offset组合在了一起,这个then方法就好像俄罗斯套娃组合之后大概样子就是

CombinedModifier(CombinedModifier(原始值,Padding),offset)

tips1:在compose中真的是把kotlin的骚操作用到了极致,看Modifier这个接口搞了一个伴生方法返回了Modifier相当于搞了一个静态内部类implements了Modifier接口,然后返回了一个当前对象

以下是Layout源码:

@Suppress("ComposableLambdaParameterPosition")
@Composable inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    val density = LocalDensity.current
    val layoutDirection = LocalLayoutDirection.current
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        factory = ComposeUiNode.Constructor,
        update = {
            set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
            set(density, ComposeUiNode.SetDensity)
            set(layoutDirection, ComposeUiNode.SetLayoutDirection)
        },
        skippableUpdate = materializerOf(modifier),//代码2
        content = content
    )
}

在代码2处发现了modifier的身影继续追踪:

@PublishedApi
internal fun materializerOf(
    modifier: Modifier
): @Composable SkippableUpdater<ComposeUiNode>.() -> Unit = {
    val materialized = currentComposer.materialize(modifier)
    update {
        set(materialized, ComposeUiNode.SetModifier)//代码3
    }
}

这里好像对modifier进行了一次加工返回了一个materialized变量继续追踪这个变量发现把materialized set给了ComposeUiNode.SetModifier那么直接追踪ComposeUiNode.SetModifier点进去

internal interface ComposeUiNode {
    var measurePolicy: MeasurePolicy
    var layoutDirection: LayoutDirection
    var density: Density
    var modifier: Modifier

    /**
     * Object of pre-allocated lambdas used to make use with ComposeNode allocation-less.
     */
    companion object {
        val Constructor: () -> ComposeUiNode = LayoutNode.Constructor
        val SetModifier: ComposeUiNode.(Modifier) -> Unit = { this.modifier = it }//代码4
        val SetDensity: ComposeUiNode.(Density) -> Unit = { this.density = it }
        val SetMeasurePolicy: ComposeUiNode.(MeasurePolicy) -> Unit =
            { this.measurePolicy = it }
        val SetLayoutDirection: ComposeUiNode.(LayoutDirection) -> Unit =
            { this.layoutDirection = it }
    }
}

好像到这里只是在代码4处找到了和代码3有关的代码,到这里好像断了。。。。。不过没关系看这个接口是谁来实现的呢?答案是:LayoutNode在compose里面所有的ui组件都是一个LayoutNode,这个你可以打断点看到,也就是说代码4中是把设置的所有Modifier赋值给了LayoutNode的Modifier那就继续LayoutNode的modifer赋值方法

...
internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this) //代码5
private val outerMeasurablePlaceable = OuterMeasurablePlaceable(this,innerLayoutNodeWrapper) //代码6
...

override var modifier: Modifier = Modifier
    set(value){
        ...
        val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap -> //代码7
            var wrapper = toWrap
            if (mod is RemeasurementModifier) {
                mod.onRemeasurementAvailable(this)
            }

            val delegate = reuseLayoutNodeWrapper(mod, toWrap)
            if (delegate != null) {
                if (delegate is OnGloballyPositionedModifierWrapper) {
                    getOrCreateOnPositionedCallbacks() += delegate
                }
                wrapper = delegate
            } else {
                if (mod is DrawModifier) {
                    wrapper = ModifiedDrawNode(wrapper, mod)
                }
                if (mod is FocusModifier) {
                    wrapper = ModifiedFocusNode(wrapper, mod).assignChained(toWrap)
                }
                if (mod is FocusEventModifier) {
                    wrapper = ModifiedFocusEventNode(wrapper, mod).assignChained(toWrap)
                }
                if (mod is FocusRequesterModifier) {
                    wrapper = ModifiedFocusRequesterNode(wrapper, mod).assignChained(toWrap)
                }
                if (mod is FocusOrderModifier) {
                    wrapper = ModifiedFocusOrderNode(wrapper, mod).assignChained(toWrap)
                }
                if (mod is KeyInputModifier) {
                    wrapper = ModifiedKeyInputNode(wrapper, mod).assignChained(toWrap)
                }
                if (mod is PointerInputModifier) {
                    wrapper = PointerInputDelegatingWrapper(wrapper, mod).assignChained(toWrap)
                }
                if (mod is NestedScrollModifier) {
                    wrapper = NestedScrollDelegatingWrapper(wrapper, mod).assignChained(toWrap)
                }
                if (mod is LayoutModifier) {
                    wrapper = ModifiedLayoutNode(wrapper, mod).assignChained(toWrap)
                }
                if (mod is ParentDataModifier) {
                    wrapper = ModifiedParentDataNode(wrapper, mod).assignChained(toWrap)
                }
                if (mod is SemanticsModifier) {
                    wrapper = SemanticsWrapper(wrapper, mod).assignChained(toWrap)
                }
                if (mod is OnRemeasuredModifier) {
                    wrapper = RemeasureModifierWrapper(wrapper, mod).assignChained(toWrap)
                }
                if (mod is OnGloballyPositionedModifier) {
                    wrapper =
                        OnGloballyPositionedModifierWrapper(wrapper, mod).assignChained(toWrap)
                    getOrCreateOnPositionedCallbacks() += wrapper
                }
            }
            wrapper
        }

        outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper
        outerMeasurablePlaceable.outerWrapper = outerWrapper
      ...
    }

这里代码7有个foldOut方法,这个方法具体解析可一看这篇文章大概意思就是遍历然后经过一系列操作返回一个新的wrapper经过这个方法会把原来的modifier链变成

企业微信截图_74454b34-e738-4031-a703-4d786c48801d.png 注意这里如果modifier的规则不一样生成的outerWrapper也是不一样的这里只是对Modifier.padding().offset()生成的

到这里好没有一个有关measure的方法,再回头看Layout中的measurePolicy参数,他需要一个实现自MeasurePolicy的类,咱们这里传入一个匿名内部类,然后实现measure方法,**那这个measure方法里面的measurables和constraints是从哪里来的呢?(问题1)**这个问题先放一下,下面在看下compose怎么在咱们代码里面获取到一个view的大小的其实官方demo的代码 主要是

measurable.measure(constraints)

那么问题1里面的两个参数是不是从这里了来的呢?点进去看看但是这个Measurable是个接口measure方法也是这个接口的方法,具体的实现类是那个呢?debug会发现其实就是LayoutNode,直接进LayoutNode看看

// Delegation from Measurable to measurableAndPlaceable
override fun measure(constraints: Constraints) =
    outerMeasurablePlaceable.measure(constraints)

发现了代码6的影子了,继续往里面走

override fun measure(constraints: Constraints): Placeable {
    ...
    remeasure(constraints)
    return this
}

fun remeasure(constraints: Constraints): Boolean {
    .....
        owner.snapshotObserver.observeMeasureSnapshotReads(layoutNode) {
            outerWrapper.measure(constraints)//代码8
        }
        // The resulting layout state might be Ready. This can happen when the layout node's
        // own modifier is querying an alignment line during measurement, therefore we
        // need to also layout the layout node.
        if (layoutNode.layoutState == LayoutState.Measuring) {
            layoutNode.layoutState = LayoutState.NeedsRelayout
        }
        val sizeChanged = outerWrapper.size != outerWrapperPreviousMeasuredSize ||
            outerWrapper.width != width ||
            outerWrapper.height != height
        // We are using the coerced wrapper size here to avoid double offset in layout coop.
        measuredSize = IntSize(outerWrapper.width, outerWrapper.height)
        return sizeChanged
    }
    return false
}

最终到了remeasure方法,又去调了outerWrapper的measure方法,回忆一下outerWrapper是啥呢???对的就是那个图片里面最终生成的那一坨 ModifiedLayoutNode(ModifiedLayoutNode(innerLayoutNodeWrapper,OffsetModifier),PaddingModifier) 继续进源码

internal class ModifiedLayoutNode(
    wrapped: LayoutNodeWrapper,
    modifier: LayoutModifier
) : DelegatingLayoutNodeWrapper<LayoutModifier>(wrapped, modifier) {

    override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
        with(modifier) {
            measureResult = measureScope.measure(wrapped, constraints) //代码9
            this@ModifiedLayoutNode
        }
    }
protected inline fun performingMeasure(
    constraints: Constraints,
    block: () -> Placeable
): Placeable {
    measurementConstraints = constraints
    val result = block()
    layer?.resize(measuredSize)
    return result
}

主要看代码9这里是调了measureScope的measure方法,这个measureScope又是个啥?赋值只看到了

internal class InnerPlaceable(
    layoutNode: LayoutNode
) : LayoutNodeWrapper(layoutNode), Density by layoutNode.measureScope {

    override val measureScope get() = layoutNode.measureScope
internal open class DelegatingLayoutNodeWrapper<T : Modifier.Element>(
    override var wrapped: LayoutNodeWrapper,
    open var modifier: T
) : LayoutNodeWrapper(wrapped.layoutNode) {
    override val measureScope: MeasureScope get() = wrapped.measureScope

这两个地方赋值,第二个就是传入wrapped的measurescope所以主要看第一个地方,发现这个类在代码5用过啊而且就是innerLayoutNodeWrapper,这个先放一边接续measure方法(问题2) 因为当前的modifier是PaddingModifier直接进看一下

class PaddingModifier {
override fun MeasureScope.measure(
    measurable: Measurable,
    constraints: Constraints
): MeasureResult {
    ...
    val placeable = measurable.measure(constraints.offset(-horizontal, -vertical))
    ...
}
}

这里的measurable是在代码9传入的wrapped其实就是ModifiedLayoutNode(innerLayoutNodeWrapper,OffsetModifier)这个联合对象,在继续这个联合对象的measure会调OffsetModifier的measure方法

private class OffsetModifier(
    val x: Dp,
    val y: Dp,
    val rtlAware: Boolean,
    inspectorInfo: InspectorInfo.() -> Unit
) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        val placeable = measurable.measure(constraints)
        return layout(placeable.width, placeable.height) {
            if (rtlAware) {
                placeable.placeRelative(x.roundToPx(), y.roundToPx())
            } else {
                placeable.place(x.roundToPx(), y.roundToPx())
            }
        }
    }
    ...

这个measurable是innerLayoutNodeWrapper,再看他的measure方法

internal class InnerPlaceable(
    layoutNode: LayoutNode
) : LayoutNodeWrapper(layoutNode), Density by layoutNode.measureScope {

    override val measureScope get() = layoutNode.measureScope

    override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
        val measureResult = with(layoutNode.measurePolicy) {
            layoutNode.measureScope.measure(layoutNode.children, constraints)//代码10
        }
        layoutNode.handleMeasureResult(measureResult)
        return this
    }
    ...

代码10这个measure好像和咱们刚才看的都不一样,其实这个measure方法就是你在自定义view中的MeasurePolicy的measure方法,而刚才问题2的measurescope就是自定view中的measurescope,测量完毕后会调LayoutNode中的

internal fun handleMeasureResult(measureResult: MeasureResult) {
    innerLayoutNodeWrapper.measureResult = measureResult//代码11
}

这里把结果赋值给了innerLayoutNodeWrapper而innerLayoutNodeWrapper又是outerMeasurablePlaceable中的的outerWrapper,所以相当于把经过传给了outerMeasurablePlaceable,再看remeasure方法

fun remeasure(constraints: Constraints): Boolean {
    ...
        owner.snapshotObserver.observeMeasureSnapshotReads(layoutNode) {
            outerWrapper.measure(constraints)
        }
       
        if (layoutNode.layoutState == LayoutState.Measuring) {
            layoutNode.layoutState = LayoutState.NeedsRelayout
        }
        val sizeChanged = outerWrapper.size != outerWrapperPreviousMeasuredSize ||
            outerWrapper.width != width ||
            outerWrapper.height != height
        measuredSize = IntSize(outerWrapper.width, outerWrapper.height)
        return sizeChanged
    }
    return false
}

这个measuredSize其实就是通过测量得到的尺寸但是这个width和height又是从哪来的呢??(问题3) 再看源码,方法

internal abstract class LayoutNodeWrapper(
    internal val layoutNode: LayoutNode
) : Placeable(), Measurable, LayoutCoordinates, OwnerScope, (Canvas) -> Unit {
 ...
    var measureResult: MeasureResult
        get() = _measureResult ?: error(UnmeasuredError)
        internal set(value) {
            val old = _measureResult
            if (value !== old) {
                _measureResult = value
                if (old == null || value.width != old.width || value.height != old.height) {
                    onMeasureResultChanged(value.width, value.height) //代码12
                }

protected open fun onMeasureResultChanged(width: Int, height: Int) {
   ...
    measuredSize = IntSize(width, height)
}
protected var measuredSize: IntSize = IntSize(0, 0)
    set(value) {
        if (field != value) {
            field = value
            recalculateWidthAndHeight()
        }
    }
private fun recalculateWidthAndHeight() {
    width = measuredSize.width.coerceIn(
        measurementConstraints.minWidth,
        measurementConstraints.maxWidth
    )
    height = measuredSize.height.coerceIn(
        measurementConstraints.minHeight,
        measurementConstraints.maxHeight
    )
}

代码11有个measureresult的赋值,继续往下到代码12再继续,一直到recalculateWidthAndHeight这个方法是Placeable的接口方法,而remeasure中的outerWrapper方法也是实现了这个接口的所以尺寸现在可以拿到了。

总结:1、compose只允许测量一次,那么他是怎么准确的测量view的大小呢? 答:compose是一层层的递归去测量每个子view的大小,最终得到整个view的大小
2、compose中各种Modifier他是怎么影响view的尺寸的? 答:每个Modifier都会对应生成不同实现自LayoutNodeWrapper接口的实现类而这个也在测量中起到了至关重要的最用,其实就是每种Modifier基本上都会参与测量

有错误请指出谢谢
参考文章:juejin.cn/post/698180…