Modifier II:自定义Compose

Modifier.layout()

  • 实现了Element接口
  • 涉及到measure和layout(可以理解为原生的Measure和Layout)
  • 它是我们用来自定义UI的测量摆放的一个API.

先上代码

  • placeable.place()和placeable.placeRelative()区别是支持RTL适配。
  • layout()的测量很简单,measure完成,传递width和height,同时制定placeable的摆放,就结束了。
  Box(Modifier.background(Color.Red)) {
                Text(text = "ryanhuen", Modifier.layout { measurable, constraints ->
                    val placeable =
                        measurable.measure(constraints)//根据外部传入的constraints,调用measruable的measure()函数
                    layout(placeable.width, placeable.height) {
                        placeable.placeRelative( // 定义组件在父亲节点内部的偏移量
                            0, 0
                        )
                    } 
                })
            }

###作用:

  • 干涉修饰的组件自身的measure大小,以及摆放的位置。不支持对组件内部的子节点做干涉。(比如Column自身的measure和offset可以修改,但是Column内部的Text等,不会因此受影响)

Measurable

  • 根据Constraints去measure一个Placeable的layout返回。一个Measurable实例,只能被measure一次。

Constraints

  • 用于测量布局的可变约束,父亲节点指定一个Constraints,子节点在这个Constrants中,measure和layout子节点。
  • 父亲节点穿入的Constraints约束,子节点可以基于这个约束,做自己所能定制的所有大小
 Box(Modifier.background(Color.Red)) {
                Text(text = "ryanhuen", Modifier.layout { measurable, constraints ->
                    val paddingHorizontal = 10.dp.roundToPx()
                    val newConstraints =
                        constraints.copy(maxWidth = constraints.maxWidth - (paddingHorizontal * 2))//根据父亲的Constraints,自定义一个范围,这样就能给Text留出一下padding大小了。
                    val placeable =
                        measurable.measure(newConstraints)//根据外部传入的constraints,调用measruable的measure()函数
                    layout(placeable.width + (paddingHorizontal * 2), placeable.height + (paddingHorizontal * 2)) {
                        placeable.placeRelative( // 定义组件在父亲节点内部的偏移量
                            paddingHorizontal, paddingHorizontal 
                        )
                    }
                })
            }
  • 上述方案,和Modifier.padding()实现类似,此处只是一个小Demo.

LayoutModifier

  • 所有涉及到尺寸和布局相关的Modifier,比如:Modifier.size()、Modifier.padding()等,都实现了LayoutModifier接口。
  • 如: PaddingModifier等
  • LayoutModifier主要是能够帮助我们去修改布局的测量属性,从而达到修改UI的摆放等效果。

Compose怎么测量

  • 所有的Compose组件,如:Text等,在布局的过程中,都会变成LayoutNode,它才是实际的测量、绘制、布局等相关。

remeasure(测量工作)

  • 1.2.0 版本Compose为例,LayoutNode的remeasure()函数调用过程如下:
LayoutNode.remeasure() 
-> outerMeasurablePlaceable.remeasure(constraints)
-> layoutNode.performMeasure(constraints)
-> outerLayoutNodeWrapper.measure(constraints)  //LayoutNodeWrapper

最终到达:
/**
 * A part of the composition that can be measured. This represents a layout.
 * The instance should never be stored.
 */
interface Measurable : IntrinsicMeasurable {
    /**
     * Measures the layout with [constraints], returning a [Placeable] layout that has its new
     * size. A [Measurable] can only be measured once inside a layout pass.
     */
    fun measure(constraints: Constraints): Placeable
}

  • remeasure()调用最终会转移到Measurable接口。需要看下Measurable的实现类。Measurable实现类其实非常多,但是其实measure()函数是LayoutNodeWrapper调取的,所以我们看LayoutNodeWrapper的实现类就行。
    • InnerPlaceable.kt
    • ModifiedLayoutNode.kt
    internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this)
    private val outerMeasurablePlaceable = OuterMeasurablePlaceable(this, innerLayoutNodeWrapper)
    internal val outerLayoutNodeWrapper: LayoutNodeWrapper
        get() = outerMeasurablePlaceable.outerWrapper
//LayoutNodeWrapper来自outerMeasurablePlaceable
//OuterMeasurablePlaceable 里面包装了 LayoutNodeWrapper
//指向innerLayoutNodeWrapper成员
//因此:
//LayoutNodeWrapper具体实现,是 --> InnerPlaceable

InnerPlaceable

  • 关键点还是它的measure()函数做了什么:
    override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
        // before rerunning the user's measure block reset previous measuredByParent for children
        layoutNode._children.forEach {
            it.measuredByParent = LayoutNode.UsageByParent.NotUsed
        }
        val measureResult = with(layoutNode.measurePolicy) {
    		 //调用LayoutNode的measure作用域,进行measure children
            layoutNode.measureScope.measure(layoutNode.children, constraints) 
        }
        layoutNode.handleMeasureResult(measureResult)
        onMeasured()
        return this
    }

  • 关键在于 layoutNode.measureScope.measure(layoutNode.children, constraints)
  • 调用LayoutNode的measure作用域,进行measure children
  • measure完成后,会得到measureResult,保存测量结果
/**
 * Interface holding the size and alignment lines of the measured layout, as well as the
 * children positioning logic.
 * [placeChildren] is the function used for positioning children. [Placeable.placeAt] should
 * be called on children inside [placeChildren].
 * The alignment lines can be used by the parent layouts to decide layout, and can be queried
 * using the [Placeable.get] operator. Note that alignment lines will be inherited by parent
 * layouts, such that indirect parents will be able to query them as well.
 */
interface MeasureResult {
    val width: Int
    val height: Int
    val alignmentLines: Map<AlignmentLine, Int>
    fun placeChildren()  //Mesure的宽高、Align等结果,在后续的replace()过程中,再调用placeChildren()就能够进行摆放工作
}

LayoutNode->modifier成员:

  • 接下来我们先看看LayoutNode中的modifer成员。
  • 当我们给一个Text等组件,设置的Modifer之后,我们说过这些组件最终都会变成LayoutNode,那么设置的Modifer也会被包装到LayoutNode的modifier成员中。
 // Create a new chain of LayoutNodeWrappers, reusing existing ones from wrappers
 // when possible.

    		//前面文章说过,这个是根据应用开发者modifer的编写顺序后序遍历调用并combineModifer.
            val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
                if (mod is RemeasurementModifier) {
                    mod.onRemeasurementAvailable(this)
                }

                toWrap.entities.addBeforeLayoutModifier(toWrap, mod)

                if (mod is OnGloballyPositionedModifier) {
                    getOrCreateOnPositionedCallbacks() += toWrap to mod
                }

                val wrapper = if (mod is LayoutModifier) {
                    // Re-use the layoutNodeWrapper if possible.
                    (reuseLayoutNodeWrapper(toWrap, mod)
                        ?: ModifiedLayoutNode(toWrap, mod)).apply { onInitialize() }//最终会在这里,把LayoutNodeWrapper,一层层包装这Modifer,做成一个链表结构,进行返回。
                } else {
                    toWrap
                }
                wrapper.entities.addAfterLayoutModifier(wrapper, mod)
                wrapper
            }

            setModifierLocals(value)

            outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper
            outerMeasurablePlaceable.outerWrapper = outerWrapper
    		//随后,返回的outerWrapper,重新赋值给OuterMeasurablePlaceable中的outerWrapper成员。这样去得到一个链表。
  • 上面这段代码,比较巧妙。
  • 加入一个Text()有一个Modifer.padding().size()
  • Text()的Composable的measurable就会是最早的innerLayoutNodeWrapper
  • 最早的innerLayoutNodeWrapper,是第一层的InnerPlaceable实现,随后会因为size()是一个LayoutModifer
  • size()会被包装成一个ModifiedLayoutNode,把innerLayoutNodeWrapper包装起来,作为自己的内部outerWrapper成员,最后自己作为链表的新节点,重新赋值给outerMeasurablePlaceable
  • padding()此时会再去接力。
padding()的ModifierLayoutNode[ 
                 size()的ModifierLayoutNode[ 
    			                       innerLayoutNodeWrapper(Text()的measurable),
    								   size() LayoutModifer],
    			padding() LayoutModifer]

ModifiedLayoutNode的measure

    override fun measure(constraints: Constraints): Placeable {
        val placeable = performingMeasure(constraints) {
            with(modifier) {//kotlin属性,with之后,with的{}中就变成了modifier的类的作用域
                measureResult = measureScope.measure(wrapped, constraints)//因此,measureScope.measure()的作用域,是modifer指定的。
                this@ModifiedLayoutNode
            }
        }
        onMeasured()
        return placeable
    }
  • 上面的code,解释了我们在使用不同的Modifer组合顺序的时候,是怎么实现不同的代码作用域的。
  • 因为measure的时候,其实是我们使用的Modifier在影响measure的具体效果
  • 那么这个with(modifier),是谁呢,其实就是LayoutModifier。LayoutModifier其实是我们写的Modifer属性的具体实现,如:size()、padding()之类的
  • 它们在Compose中的具体实现:
    • size->SizeModifier
    • padding -> PaddingModifier
  • 上述的具体指向Modifier实现,都继承了LayoutModifier,所以上面的代码,最终会把measure作用域调用到:
    • size -> SizeModifier -> measureScope.measure(wrapped, constraints)
    • padding -> PaddingModifier -> measureScope.measure(wrapped, constraints)
  • 接下来,上面说到的链表结构就起作用了。当size()的真正实现的measure()被调用的时候,它会收到它内部所包裹的wrapped对象,这个在我们的举例中,就是 -> " Text()的Composable的measurable"
  • 这样,size()在measure()的时候,就能关心自己内部有什么东西,这样测量,才是准确的。

示例:

Text("rock",Modifier.padding(10.dp).padding(20.dp))

padding()的ModifierLayoutNode[ 
                 padding()的ModifierLayoutNode[ 
    			                       innerLayoutNodeWrapper("rock"),
    								   20.dp LayoutModifer],
    			10.dp LayoutModifer]
    			
[10.dp
    [20.dp
    	["rock"]
    ]
]

replace(布局工作)


 评论