" Jetpack Compose - - Modifier 原理系列文章 "
????📑 《 深入解析 Compose 的 Modifier 原理 - - Modifier、CombinedModifier 》
????📑 《 深度解析 Compose 的 Modifier 原理 - - Modifier.composed()、ComposedModifier 》
????📑 《 深入解析 Compose 的 Modifier 原理 - - Modifier.layout()、LayoutModifier 》
????📑 《 深度解析 Compose 的 Modifier 原理 - - DrawModifier 》
????📑 《 深度解析 Compose 的 Modifier 原理 - - PointerInputModifier 》
????📑 《 深度解析 Compose 的 Modifier 原理 - - ParentDataModifier 》
在正式开始分析 LayoutModifier 相关原理之前,建议你先看看 【 Compose 是如何将数据转换成 UI 的?】这篇文章,当你了解了 Compose 的“组合”、“布局”、“绘制”的思维模型后,有助于你更透彻的了解 Modifier 的底层设计原理。如果此时你对 Modifier 还不了解,可以先阅读前面两篇关于 Modifier 的文章,这些都是必备基础。
先来看一个最简单的代码示例:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Box(Modifier.background(Color.Green)) {
Text("Hi Compose")
}
}
}
}
}
这段代码很简单,效果图如下:
接下来基于这个 Demo,我们会慢慢引入本篇文章的主角。
在 Compose 中 Modifier.layout() 是一种布局修饰符,它会包裹一个布局节点:Layout(什么是 LayoutNode,下面会讲),通常用作对目标组件进行测量和位置摆放的。
说白了就是你可以用 Modifier.layout() 来自定义目标组件的测量过程以及决定目标组件怎么摆放。
现在我们看看代码中怎么用,通常会像下面这样写:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Box(Modifier.background(Color.Green)) {
Text("Hi Compose",
Modifier.layout { measurable, constraints -> }
)
}
}
}
}
}
就这么简单,Text
就是目标组件,我们给它加了一个 Modifier.layout(),没有添加任何其他代码逻辑,{ measurable, constraints -> }
是自动生成的,此时你会发现在 Android Studio IDLE 中,这样写是会标红的:
正常来说,我这里什么也不填不就相当于对 Text() 不做任何修饰。但很明显这样是不行的,接下来我们一起尝试解决这个报错。
首先我们发现当使用 layout() 修饰符时,传入的回调 lambda 包含了两个参数:
我们定位到 Modifier.layout() 的源码:
fun Modifier.layout(
measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this then LayoutModifierElement(measure)
measurable 对应的是 Measurable:
interface Measurable : IntrinsicMeasurable {
// 返回一个 Placeable,它里面包含目标组件的宽、高等信息
fun measure(constraints: Constraints): Placeable
}
Measurable 是一个接口,内部仅有一个 measure() 方法。
所以现在可以开始修改刚才的报错了:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Box(Modifier.background(Color.Green)) {
Text("Hi Compose",
Modifier.layout { measurable, constraints ->
// 用一个变量保存返回的 Placeable 对象
val placeable = measurable.measure(constraints)
}
)
}
}
}
}
}
现在代码仍然是标红报错的,原因在于:我们只处理了 measurable,它返回的是 Placeable,而 Modifier.layout() 需要返回的类型是 MeasureResult:
fun Modifier.layout(
measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this then LayoutModifierElement(measure)
所以 MeasureResult 是什么?
interface MeasureResult {
val width: Int
val height: Int
val alignmentLines: Map<AlignmentLine, Int>
fun placeChildren()
}
MeasureResult 也是一个接口,它里面也有 width 和 height,继续修复刚才的报错:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Text("ComposeTest",
Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
object : MeasureResult {
override val alignmentLines: Map<AlignmentLine, Int>
get() = TODO("Not yet implemented")
override val height: Int
get() = TODO("Not yet implemented")
override val width: Int
get() = TODO("Not yet implemented")
override fun placeChildren() {
TODO("Not yet implemented")
}
}
}
)
}
}
}
}
既然 Modifier.layout() 需要一个 MeasureResult 返回对象,那我们就在内部给它创建一个 MeasureResult 对象,此时 IDLE 就不会再报错了。
当然我们还需要做一个工作,那就是把 placeable 的宽高传进 MeasureResult 内部,所以最终的代码修改如下:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Box(Modifier.background(Color.Green)) {
Text("Hi Compose",
Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
object : MeasureResult {
// 测量基准线,暂时不用关心
override val alignmentLines: Map<AlignmentLine, Int>
get() = TODO("Not yet implemented")
// 高:placeable.height
override val height: Int
get() = placeable.height
// 宽:placeable.width
override val width: Int
get() = placeable.width
// 摆放内部组件,暂时不用关心
override fun placeChildren() {
TODO("Not yet implemented")
}
}
}
)
}
}
}
}
}
至此报错就修复了,上面的代码演示了对 Text() 添加 Modifier.layout() 进行修饰(当然上面的做法等同于啥也没做)。
但这段代码有个缺陷:如果每次通过 Modifier.layout() 对组件修饰,都得像上面这样写一堆代码,那还不得疯?
其实在实际开发中我们并不会这么写,而是使用 Compose 提供给我们的 layout() 函数:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Box(Modifier.background(Color.Green)) {
Text("Hi Compose",
Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
/*object : MeasureResult {
override val alignmentLines: Map<AlignmentLine, Int>
get() = TODO("Not yet implemented")
override val height: Int
get() = TODO("Not yet implemented")
override val width: Int
get() = TODO("Not yet implemented")
override fun placeChildren() {
TODO("Not yet implemented")
}
}*/
layout() {
}
}
)
}
}
}
}
}
我们来看下 layout() 源码:
fun layout(
width: Int,
height: Int,
alignmentLines: Map<AlignmentLine, Int> = emptyMap(),
placementBlock: Placeable.PlacementScope.() -> Unit
) = object : MeasureResult { // 看这里,这是个啥?熟悉吗?
override val width = width
override val height = height
override val alignmentLines = alignmentLines
override fun placeChildren() {
Placeable.PlacementScope.executeWithRtlMirroringValues(
width,
layoutDirection,
this@MeasureScope as? LookaheadCapablePlaceable,
placementBlock
)
}
}
很明显 layout() 函数帮我们创建好了 MeasureResult 对象,同时它还帮我们干了另外两件没做的事:
所以,我们现在只需要补全 layout() 函数剩余的两个参数:width 和 height。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Box(Modifier.background(Color.Green)) {
Text("Hi Compose",
Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) {
}
}
)
}
}
}
}
}
这样写代码是不是瞬间感觉清爽了很多?但工作到这边还没有结束,layout() 函数还有第四个参数,是一个 Lambda 表达式,主要工作是处理被修饰组件的摆放规则,比如偏移量。
fun layout(
width: Int,
height: Int,
alignmentLines: Map<AlignmentLine, Int> = emptyMap(),
placementBlock: Placeable.PlacementScope.() -> Unit // Lambda 表达式
) = object : MeasureResult {
... ...
}
我们继续完善:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Box(Modifier.background(Color.Green)) {
Text("Hi Compose",
Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) {
// 不做任何偏移
placeable.placeRelative(0, 0)
}
}
)
}
}
}
}
}
现在所有工作(测量 + 摆放)都已完成,运行看下效果:
可以看出来,没有任何变化,因为我们虽然用 Modifier.layout() 对 Text 做修饰,但并没有对它做任何尺寸修改和位置偏移。
那如果我现在想修改 Text() 的尺寸,该怎么做?
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Box(Modifier.background(Color.Green)) {
Text("Hi Compose",
Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
val size = min(placeable.width, placeable.height)
layout(size, size) {
placeable.placeRelative(0, 0)
}
})
}
}
}
}
}
我们定义了一个 size 变量,通过 min() 函数获取宽高最小值,然后重新传入 layout() 里面,这样就会获得一个正方形的效果。
尺寸修改确实生效了,接下来再增加一个偏移:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Box(Modifier.background(Color.Green)) {
Text("Hi Compose",
Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
val size = min(placeable.width, placeable.height)
layout(size, size) {
placeable.placeRelative(10, 0)
}
})
}
}
}
}
}
看下效果:
另外有个细节需要说明下,除了使用 placeRelative 对组件偏移外,也可以使用 place 进行偏移操作,两者的区别就是 placeRelative 会自适应 RTL 布局。
讲到这里,Modifier.layout() 修饰符和 layout() 函数的用法你应该都清楚了,但还没结束,前面我们一直忽略了一个参数:constraints,它是什么?
前面的例子并没有对 constraints 做任何修改,在实际开发过程中,我们往往需要通过 constraints 对组件进行限制。
比如我想对 Text() 组件进行一个限制,类似 padding 的效果,给它加一个 10dp 的最大宽高的限制(最大宽高缩减 10dp)。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Box(Modifier.background(Color.Green)) {
Text("Hi Compose",
Modifier.layout { measurable, constraints ->
val paddingPx = 10.dp.roundToPx()
val placeable = measurable.measure(constraints.copy(
maxWidth = constraints.maxWidth - paddingPx * 2,
maxHeight = constraints.maxHeight - paddingPx * 2
))
layout(placeable.width + paddingPx * 2, placeable.height + paddingPx * 2) {
placeable.placeRelative(paddingPx, paddingPx)
}
})
}
}
}
}
}
看下效果:
很明显,我们实现的效果跟 Modifier.padding(10.dp) 的效果是一样的,如果你去看看 Modifier.padding 的源码,就会发现它的内部原理跟我们例子是一样的。
@Stable
fun Modifier.padding(
horizontal: Dp = 0.dp,
vertical: Dp = 0.dp
) = this.then(
PaddingModifier(
... ...
)
)
private class PaddingModifier(
val start: Dp = 0.dp,
val top: Dp = 0.dp,
val end: Dp = 0.dp,
val bottom: Dp = 0.dp,
val rtlAware: Boolean,
inspectorInfo: InspectorInfo.() -> Unit
) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
... ...
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult {
val horizontal = start.roundToPx() + end.roundToPx()
val vertical = top.roundToPx() + bottom.roundToPx()
val placeable = measurable.measure(constraints.offset(-horizontal, -vertical))
val width = constraints.constrainWidth(placeable.width + horizontal)
val height = constraints.constrainHeight(placeable.height + vertical)
return layout(width, height) {
if (rtlAware) {
placeable.placeRelative(start.roundToPx(), top.roundToPx())
} else {
placeable.place(start.roundToPx(), top.roundToPx())
}
}
}
... ...
}
前面所有的示例代码,不论是使用 Modifier.layout() 修饰符还是使用 Compose 提供给我们的现成的修饰符,比如:Modifier.padding() / Modifier.size(),它们都会对被修饰组件产生精细影响(组件大小、位置偏移)。
但到目前为止,我们仅仅是从 UI 效果上看到 Modifier.layout() 会影响被修饰组件,但源码底层是如何产生影响的呢?这才是我们这篇文章的核心!
所以,最硬核的原理部分来了!
我们就拿常用的 Modifier.padding() 分析:
@Stable
fun Modifier.padding(
horizontal: Dp = 0.dp,
vertical: Dp = 0.dp
) = this.then(
PaddingModifier(
... ...
)
)
private class PaddingModifier(
... ...
) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
interface LayoutModifier : Modifier.Element {
Modifier.padding() 内部会调用一个 PaddingModifier 对象,而 PaddingModifier 实现了 LayoutModifier 接口,这个 LayoutModifier 会被 Compose 用于修改测量和布局过程,从而最终影响到界面元素的位置和尺寸。
所以我们的重点就是要研究 LayoutModifier 是如何影响组件的!
但是!在分析 LayoutModifier 原理之前,有一个核心知识点是必须要提前了解的。
这段代码我们再熟悉不过了:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Box(Modifier.background(Color.Green)) {
Text("Hi Compose")
}
}
}
}
}
Box 、Text 这些函数在实际运行的时候,其实并不是这些函数直接存在于内存里面,而是 Compose 利用这些函数创造出的一些对象存在于内存里面,这个对象就是:LayoutNode,它才是最底层的那个节点,进行实际的测量、布局、绘制、触摸反馈等工作,你可以查看 【Compose 是如何将数据转换成 UI 的?】这篇文章,了解转换的思维模型!
我们既然想知道 LayoutModifier 是如何精细影响 Text() 组件,那就得先研究明白 Text() 自己的测量、布局、绘制的原理,因为 LayoutModifier 是包着这个 Text() 的。
在 LayoutNode 中,测量和布局是由 remeasure() 函数和 replace() 两个函数处理。
internal class LayoutNode(..) {
internal fun replace() // 布局
internal fun remeasure() // 测量
}
我们先来分析 remeasure() 函数:
internal class LayoutNode(..) {
internal fun remeasure(
constraints: Constraints? = layoutDelegate.lastConstraints
): Boolean {
return if (constraints != null) {
if (intrinsicsUsageByParent == UsageByParent.NotUsed) {
clearSubtreeIntrinsicsUsage()
}
// 测量工作交给 LayoutNodeLayoutDelegate 的内部类 MeasurePassDelegate 处理
measurePassDelegate.remeasure(constraints)
} else {
false
}
}
}
LayoutNode 内部要处理的事情非常多,它把测量的工作又交给了 MeasurePassDelegate 来处理。
internal class LayoutNodeLayoutDelegate(
private val layoutNode: LayoutNode,
var outerWrapper: LayoutNodeWrapper
) {
inner class MeasurePassDelegate : Measurable, Placeable(), AlignmentLinesOwner {
fun remeasure(constraints: Constraints): Boolean {
... ...
if (layoutNode.measurePending || measurementConstraints != constraints) {
... ...
// 核心代码
performMeasure(constraints)
... ...
}
return false
}
}
}
代码很长,这里我们只关注核心代码:performMeasure(constraints)。
internal class LayoutNodeLayoutDelegate(
private val layoutNode: LayoutNode,
var outerWrapper: LayoutNodeWrapper
) {
private fun performMeasure(constraints: Constraints) {
... ...
layoutNode.requireOwner().snapshotObserver.observeMeasureSnapshotReads(
layoutNode,
affectsLookahead = false
) {
// 关键代码,实际测量
outerWrapper.measure(constraints)
}
... ...
}
}
我们仍然只需要关注:outerWrapper.measure(constraints),它是做实际测量工作的。
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
}
???怎么是个接口啊,没有任何处理逻辑啊。
那肯定有其他地方实现了这个方法,我们可以搜一下哪些地方实现了。
有这么多地方实现了,但哪一个才是我们需要的?别慌,我带你找一下。
我们往回退,刚才哪里调用 measure 的?
internal class LayoutNodeLayoutDelegate(
private val layoutNode: LayoutNode,
// 2. 只是一个 LayoutNodeWrapper,所以我们得找到哪里传入了 outerWrapper
var outerWrapper: LayoutNodeWrapper
) {
private fun performMeasure(constraints: Constraints) {
... ...
// 1. 这里调用了 measure(),而 outerWrapper 是什么?
outerWrapper.measure(constraints)
... ...
}
继续回退到上一层:
internal class LayoutNodeLayoutDelegate(
private val layoutNode: LayoutNode,
var outerWrapper: LayoutNodeWrapper
) {
inner class MeasurePassDelegate : Measurable, Placeable(), AlignmentLinesOwner {
// 2. 我们继续回退
fun remeasure(constraints: Constraints): Boolean {
... ...
// 1. 没有地方传入 outWrapper 啊
performMeasure(constraints)
... ...
}
}
}
继续回退到上一层:
internal class LayoutNode(
) {
// 2. 通过 layoutDelegate 获取 measurePassDelegate,那 layoutDelegate 是什么?
private val measurePassDelegate
get() = layoutDelegate.measurePassDelegate
... ...
// 4. 而 innerLayoutNodeWrapper 是一个 InnerPlaceable。
internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this)
/**
* 3. LayoutNodeLayoutDelegate(this, innerLayoutNodeWrapper) 看到这行代码你就熟悉了吧?
* 我们之前一直找的那个 outWrapper 就是 innerLayoutNodeWrapper。
*/
internal val layoutDelegate = LayoutNodeLayoutDelegate(this, innerLayoutNodeWrapper)
internal val outerLayoutNodeWrapper: LayoutNodeWrapper
get() = layoutDelegate.outerWrapper
internal fun remeasure(
constraints: Constraints? = layoutDelegate.lastConstraints
): Boolean {
return if (constraints != null) {
... ...
// 1. measurePassDelegate 是什么?
measurePassDelegate.remeasure(constraints)
} else {
false
}
}
}
所以,到底是哪个实现类处理了 measure() 方法,就不用我说了吧?
接下来我们就看看 InnerPlaceable 是如何负责具体测量的:
internal class InnerPlaceable(
layoutNode: LayoutNode
) : LayoutNodeWrapper(layoutNode), Density by layoutNode.measureScope {
override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
layoutNode.forEachChild {
it.measuredByParent = LayoutNode.UsageByParent.NotUsed
}
// 2. 返回一个 MeasureResult 对象给 replace() 去布局
measureResult = with(layoutNode.measurePolicy) {
// 1. 最核心处:这边就是最底层开始测量的工作了
layoutNode.measureScope.measure(layoutNode.childMeasurables, constraints)
}
onMeasured()
return this
}
}
分析到这里,关于组件的测量和布局流程就跑通了: 我们在代码中所写的 Box、Text 等组件内部会有自己设定的测量数据,他们在代码实际运行过程中会被 Comopse 转换成 LayoutNode 节点(包含所有组件自身的测量数据),然后一层层往下传,最终传到 InnerPlaceable,由它进行最底层的测量工作,测量完成后会返回一个 MeasureResult 对象再交给 replace() 函数完成布局工作。
所以,Do you understand?
前面我们已经了解了组件自身的测量和布局原理,现在就可以开始分析 LayoutModifer 是如何影响组件的测量和布局了。
就像我们前面说的那样,所有你自己写的组件最终都会被转换为一个 LayoutNode,这个LayoutNode 包含了所有的测量数据,那同样它也会包含你对组件设定的 Modifier,所以最终经过一些列转换,也会传到 LayoutNode 里面,那 LayoutNode 里面必然会存在一个 modifer 属性来处理你所设定的所有 Modifer.xx,我们来看源码:
internal class LayoutNode(
private val isVirtual: Boolean = false
) : Remeasurement, OwnerScope, LayoutInfo, ComposeUiNode,
Owner.OnLayoutCompletedListener {
override var modifier: Modifier = Modifier
// 如果有新值变化
set(value) {
... ...
/** foldOut: 从右往左遍历
* 比如:
* Modifier.padding().background(): 先 backgroud() -> 后 padding()
*/
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
}
// 1. 这里就是 LayoutModifier 的处理逻辑
val wrapper = if (mod is LayoutModifier) {
/**
* 2. 如果是 LayoutModifier,就创建一个 ModifiedLayoutNode,
* 它有两个参数:toWrap, mod
* ? mod: 就是一步步遍历到的 Modifier,在我们这篇文章例子里面它就是 LayoutModifier
* ? toWrap: 初始值就是 innerLayoutNodeWrapper -> InnerPlaceable
*/
(reuseLayoutNodeWrapper(toWrap, mod)
?: ModifiedLayoutNode(toWrap, mod)).apply {
onInitialize()
updateLookaheadScope(mLookaheadScope)
}
} else {
toWrap
}
wrapper.entities.addAfterLayoutModifier(wrapper, mod)
// 3. 把处理完的 wrapper 赋值给 outerWrapper
wrapper
}
setModifierLocals(value)
outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper
// 4. 这里又把 layoutDelegate 的 outerWrapper 给换了
layoutDelegate.outerWrapper = outerWrapper
... ...
}
}
我们可以梳理一下:
1. 不设置 Modifier:
outWrapper = innerLayoutNodeWrapper -> 测量 Compose 函数,比如 Text()
2. 一个 LayoutModifer:
outerWrapper =
ModifiedLayoutNode[
LayoutModifier
+
innerLayoutNodeWrapper -> 测量 Compose 函数,比如 Text()
]
3. 两个 LayoutModifier:
outerWrapper =
ModifiedLayoutNode[
LayoutModifier
+
ModifiedLayoutNode[
LayoutModifier
+
innerLayoutNodeWrapper -> 测量 Compose 函数,比如 Text()
]
]
逻辑非常清晰,一层套一层。
我们之前说过,outerWapper 对象是做实际测量工作的,如果有 LayoutModifer,那么就会交由 ModifiedLayoutNode 做综合测量了。
我们看下 ModifiedLayoutNode 是怎么做测量的:
internal class ModifiedLayoutNode(
override var wrapped: LayoutNodeWrapper,
var modifier: LayoutModifier
) : LayoutNodeWrapper(wrapped.layoutNode) {
override fun measure(constraints: Constraints): Placeable {
performingMeasure(constraints) {
// 1. 核心代码,with 包含了 LayoutModifier,提供了一个 LayoutModifer 的上下文
with(modifier) {
// 2. 那么这个 measure 会跳转到哪里?
measureResult = measureScope.measure(wrapped, constraints)
this@ModifiedLayoutNode
}
}
onMeasured()
return this
}
}
measure() 的工作会跳转到哪里,是由 with() 决定的,源码跳转到了 LayoutModifier 里面。
interface LayoutModifier : Modifier.Element {
fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult
... ...
}
LayoutModifier 只是一个接口,所以具体的测量实现在哪?
跟着我的节奏考虑:
?--> 为什么跳转到了 LayoutModifier?-- 因为 with() 里面是一个 LayoutModifier 的上下文
????????--> LayoutModifier 是哪来的?-- 你传进来的
???????????????--> 你从哪传进来的?
还记得一开始修改完报错的全套代码吗:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Box(Modifier.background(Color.Green)) {
Text("Hi Compose",
// here,熟悉吗?我们自己写的代码
Modifier.layout { measurable, constraints ->
val paddingPx = 10.dp.roundToPx()
val placeable = measurable.measure(constraints.copy(
maxWidth = constraints.maxWidth - paddingPx * 2,
maxHeight = constraints.maxHeight - paddingPx * 2
))
layout(placeable.width + paddingPx * 2, placeable.height + paddingPx * 2) {
placeable.placeRelative(paddingPx, paddingPx)
}
})
}
Modifier.padding()
}
}
}
}
点进 Modifier.layout {}
看源码:
fun Modifier.layout(
measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this.then(
LayoutModifierImpl(
measureBlock = measure,
inspectorInfo = debugInspectorInfo {
name = "layout"
properties["measure"] = measure
}
)
)
再点击 LayoutModifierImpl 进去看看:
private class LayoutModifierImpl(
val measureBlock: MeasureScope.(Measurable, Constraints) -> MeasureResult,
inspectorInfo: InspectorInfo.() -> Unit,
) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
// 看!!!是不是重写了 MeasureScope.measure 方法 ? 所以具体测量工作就在这里了,我们找到了!
// 同理,像 Modifier.padding(),你也可以点进去看源码,同样重写了 MeasureScope.measure 方法。
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
) = measureBlock(measurable, constraints)
override fun equals(other: Any?): Boolean {
if (this === other) return true
val otherModifier = other as? LayoutModifierImpl ?: return false
return measureBlock == otherModifier.measureBlock
}
override fun hashCode(): Int {
return measureBlock.hashCode()
}
override fun toString(): String {
return "LayoutModifierImpl(measureBlock=$measureBlock)"
}
}
这里 measure
直接调用了 measureBlock()
,它是参数传进来的,往回退:
fun Modifier.layout(
measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this.then(
LayoutModifierImpl(
measureBlock = measure,
inspectorInfo = debugInspectorInfo {
name = "layout"
properties["measure"] = measure
}
)
)
measureBlock
又是 measure
,而 measure
是啥?就是你主代码里面写在 Modifier.layout {}
里面的测量和布局逻辑。
到这里所有原理都已经讲清楚了,看不懂的话,你就再看一遍!