Flutter-MultiChildRenderObjectWidget
本文最后更新于 677 天前

一、初步使用-和SingleChildRenderObjectWidget类似

    1. class CusRenderBox extends SingleChildRenderObjectWidget/MultiChildRenderObjectWidget
    1. 不同点:
// 可以进行内部绘制
// class RenderCusRenderBox extends RenderBox with RenderObjectWithChildMixin{
class RenderCusRenderBox extends RenderProxyBox{}

// 可以进行内部绘制-需要额外设置ContainerBoxParentData以及多个孩子的布局
// CustomMultiChildLayout 需要将 绘制Widget Child传进来 内部不能绘制
class RenderCloudWidget extends RenderBox
    with ContainerRenderObjectMixin<RenderBox, RenderCloudParentData>,
        RenderBoxContainerDefaultsMixin<RenderBox, RenderCloudParentData> {}
1.1 ContainerRenderObjectMixin
主要是维护提供了一个双链表的 children RenderObject 。
通过在 RenderBox 里混入 ContainerRenderObjectMixin , 我们就可以得到一个双链表的 children ,
方便在我们布局时,可以正向或者反向去获取和管理 RenderObject

1.2 RenderBoxContainerDefaultsMixin
RenderBoxContainerDefaultsMixin 主要是对 ContainerRenderObjectMixin 的拓展,是对 ContainerRenderObjectMixin 内的 children 提供常用的默认行为和管理

/// 计算返回第一个 child 的基线 ,常用于 child 的位置顺序有关
double defaultComputeDistanceToFirstActualBaseline(TextBaseline baseline)

/// 计算返回所有 child 中最小的基线,常用于 child 的位置顺序无关
double defaultComputeDistanceToHighestActualBaseline(TextBaseline baseline)

/// 触摸碰撞测试
bool defaultHitTestChildren(BoxHitTestResult result, { Offset position })

/// 默认绘制
void defaultPaint(PaintingContext context, Offset offset)

/// 以数组方式返回 child 链表
List<ChildType> getChildrenAsList()

1.3 ContainerBoxParentData
ContainerBoxParentData 是 BoxParentData 的子类,
主要是关联了 ContainerDefaultsMixin 和 BoxParentData ,BoxParentData 是 RenderBox 绘制时所需的位置类。

通过 ContainerBoxParentData ,我们可以将 RenderBox 需要的 BoxParentData 和上面的 ContainerParentDataMixin 组合起来,
事实上我们得到的 children 双链表就是以 ParentData 的形式呈现出来的

1.4 实现步骤
  • 1、自定义 ParentData 继承 ContainerBoxParentData 。
  • 2、继承 RenderBox ,同时混入 ContainerRenderObjectMixin 和 RenderBoxContainerDefaultsMixin 实现自定义RenderObject 。
  • 3、继承 MultiChildRenderObjectWidget,实现 createRenderObject 和 updateRenderObject 方法,关联我们自定义的 RenderBox。
  • 4、override RenderBox 的 performLayout 和 setupParentData 方法,实现自定义布局

二、简化-直接使用CustomMultiChildLayout

CustomMultiChildLayout 是 Flutter 为我们封装的简化自定义布局实现,它的内部同样是通过 MultiChildRenderObjectWidget 实现,但是它为我们封装了 RenderCustomMultiChildLayoutBox 和 MultiChildLayoutParentData ,并通过 MultiChildLayoutDelegate 暴露出需要自定义的地方

只需要继承 MultiChildLayoutDelegate ,并实现如下方法即可
void performLayout(Size size);

bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate);

参考

Container(
    color: Colors.black54,
    child: CustomMultiChildLayout(
        delegate: CircleLayoutDelegate(
            ["a","b","c","d","e"],
            center: Offset(200,500)
        ),
        children: [
            LayoutId(
                id: "a",
                child: Text("a"),
            ),
            LayoutId(
                id: "b",
                child: Text("b"),
            ),
            LayoutId(
                id: "c",
                child: Text("a"),
            ),
            LayoutId(
                id: "d",
                child: Text("d"),
            ),
            LayoutId(
                id: "e",
                child: Text("e"),
            ),
        ],
    )
)

///自定义实现圆形布局
class CircleLayoutDelegate extends MultiChildLayoutDelegate {
  final List<String> customLayoutId;

  final Offset center;

  Size? childSize;

  CircleLayoutDelegate(this.customLayoutId,
      {this.center = Offset.zero, this.childSize});

  @override
  void performLayout(Size size) {
    for (var item in customLayoutId) {
      if (hasChild(item)) {
        double r = 100;

        int index = int.parse(item);

        double step = 360 / customLayoutId.length;

        double hd = (2 * math.pi / 360) * step * index;

        var x = center.dx + math.sin(hd) * r;

        var y = center.dy - math.cos(hd) * r;

        childSize ??= Size(size.width / customLayoutId.length,
            size.height / customLayoutId.length);

        ///设置 child 大小
        layoutChild(item, BoxConstraints.loose(childSize!));

        final double centerX = childSize!.width / 2.0;

        final double centerY = childSize!.height / 2.0;

        var result = new Offset(x - centerX, y - centerY);

        ///设置 child 位置
        positionChild(item, result);
      }
    }
  }

  @override
  bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => false;
}

参考

import 'dart:math' as math;

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

///CloudWidget RenderBox
///默认都会 mixins  ContainerRenderObjectMixin 和 RenderBoxContainerDefaultsMixin
class RenderCloudWidget extends RenderBox
    with
        ContainerRenderObjectMixin<RenderBox, RenderCloudParentData>,
        RenderBoxContainerDefaultsMixin<RenderBox, RenderCloudParentData> {
  RenderCloudWidget({
    List<RenderBox> children,
  })  {
    addAll(children);
  }

  ///是否重复区域了
  bool overlaps(RenderCloudParentData data) {
    Rect rect = data.content;
    RenderBox child = data.previousSibling;

    if (child == null) {
      return false;
    }

    do {
      RenderCloudParentData childParentData = child.parentData as RenderCloudParentData;
      if (rect.overlaps(childParentData.content)) {
        return true;
      }
      child = childParentData.previousSibling;
    } while (child != null);
    return false;
  }

  ///设置为我们的数据
  @override
  void setupParentData(RenderBox child) {
    if (child.parentData is! RenderCloudParentData)
      child.parentData = RenderCloudParentData();
  }

  @override
  void performLayout() {
    ///没有 childCount 不玩
    if (childCount == 0) {
      size = constraints.smallest;
      return;
    }

    ///初始化区域
    var recordRect = Rect.zero;
    var previousChildRect = Rect.zero;

    RenderBox child = firstChild;

    while (child != null) {
      var curIndex = -1;
      ///提出数据
      final RenderCloudParentData childParentData = child.parentData as RenderCloudParentData;

      child.layout(constraints, parentUsesSize: true);

      var childSize = child.size;

      ///记录大小
      childParentData.width = childSize.width;
      childParentData.height = childSize.height;

      do {
        ++curIndex;

        ///设置为遏制
        childParentData.offset = Offset(0, curIndex * 8.toDouble());

        ///判处是否交叠
      } while (overlaps(childParentData));

      ///记录区域
      previousChildRect = childParentData.content;
      recordRect = recordRect.expandToInclude(previousChildRect);

      ///下一个
      child = childParentData.nextSibling;
    }

    ///调整布局大小
    size = constraints
        .tighten(
          height: recordRect.height,
          width: recordRect.width,
        )
        .smallest;
  }

  ///设置绘制默认
  @override
  void paint(PaintingContext context, Offset offset) {
    defaultPaint(context, offset);
  }

  @override
  double computeDistanceToActualBaseline(TextBaseline baseline) {
    return defaultComputeDistanceToHighestActualBaseline(baseline);
  }

  @override
  bool hitTestChildren(HitTestResult result, { Offset position}) {
    return defaultHitTestChildren(result as BoxHitTestResult, position: position);
  }
}

/// CloudParentData
class RenderCloudParentData extends ContainerBoxParentData<RenderBox> {
  double width;
  double height;

  Rect get content => Rect.fromLTWH(
        offset.dx,
        offset.dy,
        width,
        height,
      );
}


class CloudWidget extends MultiChildRenderObjectWidget {
  CloudWidget({
    Key key,
    List<Widget> children = const <Widget>[],
  }) : super(key: key, children: children);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderCloudWidget();
  }
}
iichen:https://iichen.cn/?p=315
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇