背景
最近在做需求的时候,设计页面如上,滑块下面的气泡要按照上面的方式排列,首尾两个滑块在两端,中间的需要在滑块宽度的35%的地方。这时候使用Row组件就不合适了,因为无法精确定位中间的位置。
方案
仔细分析一下,其实我们所需要的就是一个能手动控制子组件位置的widget,但是Flutter中并没有提供类似的(可能有,但是我没找到),不过我们能手动实现一个。
在实现这样的组件之前,我们需要对Flutter的布局流程有个简单的认识:
- 上层组件向下层组件传递约束(constraints)条件。
- 下层组件确定自己的大小,然后告诉上层组件。注意下层组件的大小必须符合父组件的约束。
- 上层组件确定下层组件相对于自身的偏移和确定自身的大小(大多数情况下会根据子组件的大小来确定自身的大小)。
而Flutter中布局类组件都直接或间接的继承自SingleChildRenderObjectWidget
和MultiChildRenderObjectWidget
的Widget,比如Row、Column、Stack
等,我们可以参考这些组件来实现一个自定义子组件位置的widget。在自定义组件里我们需要把子组件的位置交由外界来处理。
实现
我们可以自定义一个ManualLayoutWidget
组件,继承自MultiChildRenderObjectWidget
,看过Flutter中一个能获取行数的Wrap这篇文章的可能了解MultiChildRenderObjectWidget
的实现方式,该组件需要一个RenderObject
对象,也就是真正渲染的对象。
在RenderObject对象中,我们重点需要实现三个方法:
setupParentData
: 当子组件被添加到父组件的时候,该方法会设置子组件的parentData
(后续定位会用到)performLayout
:用来布局子组件,并确定自身的size(在该方法里定位每个child的位置)paint
:绘制
可以看到每个child的位置都是在performLayout
方法里决定的,我们需要在该方法里进行:
- 布局子组件
- 将组件自身的size,上一个child的rect,下标以及约束传递给外界,让外界决定child的位置(Offset)
- 根据每个组件的位置、大小决定自身的大小
上面说了大致的思路,下面来看下具体实现:
/// 定义方法 外界传回child的位置
/// [childSize] 当前child的大小
/// [previousChildRect] 前一个child的rect
/// [index] 当前child所在的下标
/// [constraints] 组件本身的约束
typedef ManualLayoutChildCallBack = Offset Function(
Size childSize,
Rect? previousChildRect,
int index,
BoxConstraints constraints,
)
@override
void performLayout() {
RenderBox? child = firstChild
final constraints = this.constraints
if (child == null) {
size = constraints.smallest
return
}
Rect? previousChildRect
final BoxConstraints childConstraints = BoxConstraints(maxWidth: constraints.maxWidth)
double maxX = .0
double maxY = .0
int index = 0
while (child != null) {
child.layout(childConstraints, parentUsesSize: true)
final childSize = child.size
final childParentData = child.parentData as _ManualRenderParentData
final offset = _layoutChild(childSize, previousChildRect, index, constraints)
childParentData.offset = offset
previousChildRect = offset & childSize
maxX = math.max(maxX, previousChildRect.right)
maxY = math.max(maxY, previousChildRect.bottom)
child = childParentData.nextSibling
index = index + 1
}
// 这里暂时只考虑横向
size = constraints.constrain(Size(constraints.maxWidth, maxY))
}
为什么不用Stack?
为什么不用Stack呢,使用Positioned
也可以自己决定child的位置。不过这种方式有一些缺点,不太符合需求。可以看到滑块和下面的气泡有一个灰色带圆角的背景,这里我是整体用Container包裹的,大致的结构是Container > Column > [滑块 气泡],而Positioned是不参与Stack组件的大小计算的,我们可以看下源码:
// 计算Stack的大小
Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
... 省去部分代码
double width = constraints.minWidth
double height = constraints.minHeight
RenderBox? child = firstChild
while (child != null) {
final StackParentData childParentData = child.parentData! as StackParentData
if (!childParentData.isPositioned) {
hasNonPositionedChildren = true
final Size childSize = layoutChild(child, nonPositionedConstraints)
width = math.max(width, childSize.width)
height = math.max(height, childSize.height)
}
child = childParentData.nextSibling
}
final Size size
if (hasNonPositionedChildren) {
size = Size(width, height)
assert(size.width == constraints.constrainWidth(width))
assert(size.height == constraints.constrainHeight(height))
} else {
size = constraints.biggest
}
assert(size.isFinite)
return size
}
这样就无法拿到真实的大小,这样也就无法决定Container的大小了。当然要实现的话也可以,我们可以给Container一个固定的高度,因为在这个例子里气泡的高度其实是固定的。但是尝试另外一种方式不也挺happy嘛😊
完整代码可以在github.com/lwy121810/m… 这里查看
参考
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=20871,转载请注明出处。
评论0