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"]
]
]