Flutter中一个自定义child位置的组件

背景

image.png

最近在做需求的时候,设计页面如上,滑块下面的气泡要按照上面的方式排列,首尾两个滑块在两端,中间的需要在滑块宽度的35%的地方。这时候使用Row组件就不合适了,因为无法精确定位中间的位置。

方案

仔细分析一下,其实我们所需要的就是一个能手动控制子组件位置的widget,但是Flutter中并没有提供类似的(可能有,但是我没找到),不过我们能手动实现一个。

在实现这样的组件之前,我们需要对Flutter的布局流程有个简单的认识:

  1. 上层组件向下层组件传递约束(constraints)条件。
  2. 下层组件确定自己的大小,然后告诉上层组件。注意下层组件的大小必须符合父组件的约束。
  3. 上层组件确定下层组件相对于自身的偏移和确定自身的大小(大多数情况下会根据子组件的大小来确定自身的大小)。

而Flutter中布局类组件都直接或间接的继承自SingleChildRenderObjectWidget 和MultiChildRenderObjectWidget的Widget,比如Row、Column、Stack等,我们可以参考这些组件来实现一个自定义子组件位置的widget。在自定义组件里我们需要把子组件的位置交由外界来处理。

实现

我们可以自定义一个ManualLayoutWidget组件,继承自MultiChildRenderObjectWidget,看过Flutter中一个能获取行数的Wrap这篇文章的可能了解MultiChildRenderObjectWidget的实现方式,该组件需要一个RenderObject对象,也就是真正渲染的对象。

在RenderObject对象中,我们重点需要实现三个方法:

  1. setupParentData: 当子组件被添加到父组件的时候,该方法会设置子组件的parentData(后续定位会用到)
  2. performLayout:用来布局子组件,并确定自身的size(在该方法里定位每个child的位置)
  3. paint:绘制

可以看到每个child的位置都是在performLayout方法里决定的,我们需要在该方法里进行:

  1. 布局子组件
  2. 将组件自身的size,上一个child的rect,下标以及约束传递给外界,让外界决定child的位置(Offset)
  3. 根据每个组件的位置、大小决定自身的大小

上面说了大致的思路,下面来看下具体实现:

/// 定义方法 外界传回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. book.flutterchina.club/chapter4/co…
阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=20871,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?