Modifier.layout
看下面这段代码
Column(Modifier.fillMaxWidth()) {
Text(text = "hello world", Modifier.layout { measurable, constraints ->
// 可以理解为 传统view 中测量自己大小的步骤 获得自己宽高的尺寸
val placeable = measurable.measure(constraints)
// 拿到尺寸以后 就可以摆放自己的位置和大小了,大小就用测量出来的
layout(placeable.width, placeable.height) {
// 位置 就设置偏移量 即可
placeable.placeRelative(0, 0)
}
})
Text(
text = "hello world"
)
}
这里的2个Text 在实际展示上 位置和大小都一样,没有区别,放这个例子 其实就是为了给大家展示一下 layout的作用,实际上对于Modifer.size ,padding 等扩展函数 中,他们的内部实现 都是与这个layout 息息相关的
稍微分析一下上述的程序
由上图可知,我们传递进去的layout的lamda 他的返回值 必须得是 MeasureResult 这个接口类型的对象
可以看下这个接口, 他有宽高这2个我们熟悉的参数,还有 alignmentlines 这个有点像文字绘制时候的baseline的概念, 先跳过 不处理, 另外还有一个placeChildren的函数, 这个函数的作用 前面也提到过了,就是摆放自己的位置 ,这4个要素缺一不可,
回到我们开头的程序中
我们在lambda 内部调用的layout这个函数 注意看 他是属于measureScope的 不要和Modifier的layout搞混,这个 内部调用的layout函数,他默认帮我们返回了一个MeaureResult的对象, 方便就方便在这个对象 他默认帮我们实现了 alignmentLines 与 placeChildren函数
有人可能会疑惑 这里的 measure和layout和 传统自定义view的measure layout 好像啊,这里确实很像,但是细节上不是一个东西
比如说layout,传统自定义view中的layout 是专属于viewgroup的,你可以重写layout函数,摆放你所有子view的位置
而在compose的Modifier的layout中 你是无法摆放你子view的位置的
尝试在layout中完成padding的效果
我们有了第一小节的基础 就可以来尝试的完成 一个padding的效果
setContent {
Column(Modifier.fillMaxWidth()) {
Text(text = "hello world",
Modifier.background(Color.Yellow)
.layout { measurable, constraints ->
val padding = 10.dp.roundToPx()
// 可以理解为 传统view 中测量自己大小的步骤 获得自己宽高的尺寸
val placeable = measurable.measure(
constraints.copy(
maxWidth = constraints.maxWidth + padding * 2, maxHeight = constraints.maxHeight + padding * 2
)
)
// 拿到尺寸以后 就可以摆放自己的位置和大小了,大小就用测量出来的
layout(placeable.width + padding * 2, placeable.height * 2) {
// 位置 就设置偏移量 即可
placeable.placeRelative(padding, padding)
}
}
)
Text(
text = "hello world", modifier = Modifier.background(Color.Red)
)
}
}
LayoutModifier 对布局的影响
前面我们模仿了Modifier的padding实现,现在来看下 真正的padding 在compose中是如何做的
可以看到这里的关键是LayoutModifier接口
这里有人会问,在传统的view 体系中,是一个树形结构的view树,那在compose中是啥? 在compose中也是一个树形结构, 但是那不是view树了,而是一个LayoutNode树
在传统view体系中,通常是measure和layout 来相互配合 最终渲染出我们想要的结果,同样的,在LayoutNode中 也有类似的函数 remeasure 与 relayout
可以一步步跟一下核心的代码
这个measure方法 是个interface方法
他的具体实现其实就是
注意这里的实现类是InnerPlaceable ,我们后面还会再遇到他
这个measureResult 也就是测量结果 就会在布局的流程中 调用
了解了大概的measure流程,我们就可以开始分析一下 LayoutModifier到底对Compose的界面绘制有什么作用?
那么多的LayoutModifier 比如padding,size等等,他们的先后顺序 对Compose的界面绘制影响如何?
我们首先可以关注一下LayoutNode的 modifier这个属性
尤其是他的set方法, 这个就很关键了,我们每次给一个compose的组件 设置他的 modifier属性的时候 都会走到这个方法里面
仔细看一下这个函数,关键点在红框内:
我们首先看一下这个foldout函数,他的意思就是 把modifier这个链表,从右到左 进行遍历
怎么理解这句话
看下面的使用方法,所谓从右到左遍历,就是先读background 然后读padding 最后读fillmaxwidth
再看第二个红框中的代码:
这里我们首先关注那个最大的红框,这里其实就是当判断了mod 是layoutModifier以后 就会走这个大红框的逻辑
大红框的逻辑很简单,其实就是 创建一个ModifidLayoutNode 或者 重用之前的ModifidLayoutNode
这个mod是啥?这个mod就是我们设置的哪些LayoutModifier,
towrap是啥? 诶,这个地方就很关键了,我们之前提过,这里的遍历是从右往左进行遍历, 这个towrap
就是之前那次遍历的结果, 这个看一下这个lambda的最后表达式就很清楚了
有人这里就要问了,towrap 是之前那次遍历的结果,那第一次遍历呢? 第一次遍历
怎么取之前的遍历结果?
其实对于第一次遍历来说,这个towrap的值就是foldOut的参数 这个inneraLayoutNodeWrapper
他其实就是InnerPlaceable
InnerPlaceable 这个其实就是之前我们读过的代码 负责measure的
所以我们理解这段代码以后 他的含义就是
从右往左来遍历这个modifier,对于layoutModifier来说,最右边的在最里面,最左边的在最外面,
再换句话说,
如果我们写了一个Compose的组件,比如是一个Text组件,
当我们没有给这个组件 设置LayoutModifier参数时,他测量的结果就是Text自己的测量结果 也就是那个InnerPlaceable
当我们给这个组件设置了一个LayoutModifier参数时,他测量的结果就是 ModifiedLayoutNode , 而这个node 里面包裹的就是InnerPlaceable
以此类推。。。。。。。
所以现在问题的关键就是 看一下ModifierLayoutNode 是如何measure的吧
关键就在 这个measure方法了,我们点进去看一下
最终还是回到了LayoutModifier这个接口的measure方法里了
有人要问了,知道这么细有啥用呢,我只想知道modifier有哪些api 能完成哪些工作呀, 别急,有了上面的基础 我们可以看一下 下面几个例子了
比如下面这个代码:
Text(
text = "hello world", modifier = Modifier.padding(10.dp).padding(20.dp).background(Color.Red)
)
你能看出来 这个代码的padding最终的效果是多少吗?是10还是20?还是其他的值?
答案是30.dp
为啥?
因为这段代码的含义其实就是 最外层有一个10.dp的 LayoutModifer,里面有一个20.dp的layoutModifer,这个20.dp的layoutModifier里面还有一个text本身的measure,所以最终就是一个30padding的 text组件 仅此而已了
再看一段代码
Text(
text = "hello world", modifier = Modifier.padding(10.dp).size(20.dp).background(Color.Red)
)
你说这个代码 会不会因为有padding 而导致 我们这个text组件的文字展示不全?
当然不会,因为这里最终效果是 最外层是一个10.dp的layout,里面包裹着一个 size为20的layout, 这个size为20的layout里面 包裹了一个text, 你搞清楚这个逻辑 当然就可以轻易的得出结论 上述的写法不会导致 文字展示不全
再看一下
Box(modifier = Modifier.size(20.dp).size(50.dp).background(Color.Red)
)
你说上面这个代码 最终的box的大小是 20还是50 还是70?
答案当然是20,为啥? 因为最外层是一个20.dp的layout,里面包裹着一个50dp的layout, 没用啊,因为你最外层就20.dp
这句话还可以理解为, 我想要50.dp的大小,但是我的父layout告诉我我说 你只有20.dp的大小了 那我也就只能用20.dp的大小了
另外要注意的是,还有个api 叫requiresize 他和size是有区别的
Box(modifier = Modifier.size(20.dp).requiredSize.(50.dp).background(Color.Red)
)
比如这种,他用的是requiresize 意思就是我就用50.dp的大小, 虽然最外层就给了我20.dp的大小,没关系因为我要的是50 你给我20 ,那多出来的部分就被裁切掉了
而如果都是size 则不会出现裁切的情况了
作者:vivo祁同伟
链接:https://juejin.cn/post/7171251325655580685
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓