在 MultiChildRenderObjectWidget 中处理 SingleChildScrollView

Handle SingleChildScrollView in MultiChildRenderObjectWidget

我刚刚开始使用 RenderBox 自定义 UI。 当我了解 Mu​​ltiChildRenderObjectWidget 并做了一个小例子时,我遇到了以下问题:
当我在屏幕上插入许多 children 的列表时,小部件的高度会大于屏幕的高度。现在我添加一个 SingleChildScrollView 标签以允许向上滚动查看。而且报错happened.It无法滚动。并像下面这样抛出错误。我试图在 performLayout() 方法中更改大小 属性。但是得到了想要的结果。

未实现对缺失静态目标的处理

抛出异常时,这是堆栈: #0 RenderObject._updateCompositingBits(包:flutter/src/rendering/object.dart:2158:5) #1 RenderObject._updateCompositingBits。 (包:flutter/src/rendering/object.dart:2159:13) #2 RenderObjectWithChildMixin.visitChildren(包:flutter/src/rendering/object.dart:3122:14) #3 RenderObject._updateCompositingBits(包:flutter/src/rendering/object.dart:2158:5) #4 RenderObject._updateCompositingBits。 (包:flutter/src/rendering/object.dart:2159:13) #5 ContainerRenderObjectMixin.visitChildren(包:flutter/src/rendering/object.dart:3406:14) #6 RenderObject._updateCompositingBits(包:flutter/src/rendering/object.dart:2158:5) #7 RenderObject._updateCompositingBits。 (包:flutter/src/rendering/object.dart:2159:13) #8 RenderObjectWithChildMixin.visitChildren(包:flutter/src/rendering/object.dart:3122:14) #9 RenderObject._updateCompositingBits(包:flutter/src/rendering/object.dart:2158:5) #10 RenderObject._updateCompositingBits。 (包:flutter/src/rendering/object.dart:2159:13)

..... 希望任何人都可以帮助解决我的问题的概念和解决方案。

这里是示例图片和示例的完整代码

 @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        child: BranchComponent(
          children: [
            Container(
                height: 100,
                color: Colors.red,
                child: const Text('1')),
            Container(
                height: 20,
                width: double.infinity,
                color: Colors.blue,
                child: const Text('2')),
            Container(
                height: 20,
                width: double.infinity,
                color: Colors.blue,
                child: const Text('2')),
            Container(
                height: 20,
                width: double.infinity,
                color: Colors.blue,
                child: const Text('2')),

            Container(
                height: 200,
                color: Colors.yellow,
                child: const Text('3')),
          ],
        ),
      ),
    );
  }
class BranchComponent extends MultiChildRenderObjectWidget {
  BranchComponent({Key? key,
    required List<Widget> children,
  }) : super(key: key, children: children);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderBranchComponent();
  }
}

class _BranchComponentChild extends ContainerBoxParentData<RenderBox> with ContainerParentDataMixin<RenderBox> {}


class RenderBranchComponent extends RenderBox
    with
        ContainerRenderObjectMixin<RenderBox, _BranchComponentChild>{

  @override
  void setupParentData(covariant RenderObject child) {
    child.parentData = _BranchComponentChild();

  }

  @override
  void performLayout() {
    size = Size(constraints.maxWidth, constraints.minHeight);

    for (var child = firstChild; child != null; child = childAfter(child)) {
      child.layout(
        // limit children to a max height of 50
        constraints
      );
    }
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    var verticalOffset = .0;

    var child = firstChild;
    if(child==null){
      return;
    }

    var first = firstChild;

    for (child = firstChild; child != null; child = childAfter(child)) {
      context.paintChild(child, offset + Offset(50, verticalOffset));

      var nextCursor = childAfter(child);

      var before = childBefore(child);
      if(nextCursor== null){
        bottomCorrect(context.canvas,Offset(offset.dx+5,offset.dy +verticalOffset+child.size.height/2 ),50, 5);
        context.canvas.drawLine(Offset(offset.dx+5, offset.dy +(first?.size.height??0)/2 +15 ), Offset(offset.dx+5,offset.dy +verticalOffset+child.size.height/2-15), paintLine);
      }

      if(before==null){
        topCorrect(context.canvas,Offset(offset.dx+5,offset.dy +verticalOffset+child.size.height/2 ),50, 5);
      }

      if(nextCursor!=null&&before!=null){
        centerNode(context.canvas,Offset(offset.dx+5,offset.dy +verticalOffset+child.size.height/2 ),50, 5);
      }

      verticalOffset += child.size.height;
    }
  }
  final Paint paintLine = Paint()..strokeWidth = 3..style =PaintingStyle.stroke;

  final Paint paintShape = Paint();

  void topCorrect(Canvas canvas, Offset offset, double width, double radius){

    Path path = Path();

    path.moveTo(offset.dx,offset.dy+15);
    path.quadraticBezierTo(offset.dx, offset.dy, offset.dx+15, offset.dy,);
    canvas.drawPath(path, paintLine);

    canvas.drawLine(Offset(offset.dx+15, offset.dy), Offset(offset.dx+20, offset.dy), paintLine);

    canvas.drawCircle(Offset(offset.dx +radius +20, offset.dy), radius, paintShape);
  }

  void centerNode(Canvas canvas, Offset offset, double width, double radius){
    canvas.drawLine(Offset(offset.dx, offset.dy), Offset(offset.dx+20, offset.dy), paintLine);
    canvas.drawLine(Offset(offset.dx, offset.dy+10), Offset(offset.dx, offset.dy-10), paintLine);

    // ve 1 diem tron
    canvas.drawCircle(Offset(offset.dx +radius +20, offset.dy), radius, paintShape);
  }

  void bottomCorrect(Canvas canvas, Offset offset, double width, double radius){
    Path path = Path();
    path.moveTo(offset.dx,offset.dy-15);
    path.quadraticBezierTo(offset.dx, offset.dy, offset.dx+15, offset.dy,);
    canvas.drawPath(path, paintLine);

    canvas.drawLine(Offset(offset.dx+15, offset.dy), Offset(offset.dx+20, offset.dy), paintLine);

    // ve 1 diem tron
    canvas.drawCircle(Offset(offset.dx +radius +20, offset.dy), radius, paintShape);
  }


// @override
  // bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
  //   // return defaultHitTestChildren(result, position: position);
  // }
  // ...
}

这是可以做到的:

class BranchComponent extends MultiChildRenderObjectWidget {
  BranchComponent({
    Key? key,
    required this.dotColor,
    required List<Widget> children,
  }) : super(key: key, children: children);

  final Color dotColor;

  @override
  RenderObject createRenderObject(BuildContext context) => RenderBranchComponent(
    dotColor: dotColor,
  );

  @override
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) {
    (renderObject as RenderBranchComponent).dotColor = dotColor;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<Color>('dotColor', dotColor));
  }
}

class BranchComponentParentData extends ContainerBoxParentData<RenderBox> {}

const double childPadding = 50;
const double dotRadius = 6;
const double graphPadding = 10;
const double graphRadius = 8;

class RenderBranchComponent extends RenderBox with
  ContainerRenderObjectMixin<RenderBox, BranchComponentParentData>,
  RenderBoxContainerDefaultsMixin<RenderBox, BranchComponentParentData> {

  RenderBranchComponent({
    required Color dotColor,
  }) : _dotColor = dotColor;

  Color get dotColor => _dotColor;
  Color _dotColor;
  set dotColor(Color value) {
    if (value == _dotColor) {
      return;
    }
    _dotColor = value;
    markNeedsPaint();
  }

  @override
  void setupParentData(covariant RenderObject child) {
    if (child.parentData is! BranchComponentParentData) {
      child.parentData = BranchComponentParentData();
    }
  }

  @override
  void performLayout() {
    double height = 0;
    final deflatedConstraints = constraints.deflate(const EdgeInsets.only(left: childPadding));
    print('constraints:         $constraints');
    print('deflatedConstraints: $deflatedConstraints');
    for (var child = firstChild; child != null; child = childAfter(child)) {
      child.layout(deflatedConstraints, parentUsesSize: true);
      (child.parentData as BoxParentData).offset = Offset(childPadding, height);
      height += child.size.height;
    }
    size = Size(constraints.maxWidth, height);
  }

  final Paint linesPaint = Paint()
    ..color = Colors.black
    ..strokeWidth = 2
    ..style = PaintingStyle.stroke;

  @override
  void paint(PaintingContext context, Offset offset) {
    if (childCount == 0) {
      return;
    }

    int childNumber = 0;
    double y = offset.dy;
    Offset? start, end;
    late Rect rect1, rect2;
    Path lines = Path();
    Path dots = Path();
    for (var child = firstChild; child != null; child = childAfter(child)) {
      final BranchComponentParentData childParentData = child.parentData! as BranchComponentParentData;
      context.paintChild(child, childParentData.offset + offset);

      final centerY = y + child.size.height / 2;
      final dotCenter = Offset(childPadding / 2, centerY);
      const side = graphRadius * 2;
      if (childNumber == 0) {
        // first child
        start = dotCenter;
        rect1 = Rect.fromLTWH(graphPadding, centerY, side, side);
      } else
      if (childNumber == childCount - 1) {
        // last child
        end = dotCenter;
        rect2 = Rect.fromLTWH(graphPadding, centerY - side, side, side);
      } else {
        // middle child
        lines
          ..moveTo(graphPadding, centerY)
          ..lineTo(dotCenter.dx, dotCenter.dy);
      }
      dots.addOval(Rect.fromCircle(center: dotCenter, radius: dotRadius));

      y += child.size.height;
      childNumber++;
    }

    if (start != null && end != null) {
      lines
        ..moveTo(start.dx, start.dy)
        ..arcTo(rect1, -pi / 2, -pi / 2, false)
        ..arcTo(rect2, -pi, -pi / 2, false)
        ..lineTo(end.dx, end.dy);
    } else {
      // TODO paint something if there is a single child
    }
    final Paint dotsPaint = Paint()
      ..color = dotColor;
    context.canvas
      ..drawPath(lines, linesPaint)
      ..drawPath(dots, dotsPaint);
  }

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

示例测试小部件代码:

class BranchComponentTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: BranchComponent(
        dotColor: Colors.green,
        children: [
          Container(
            color: Colors.teal,
            child: const Text('here you can change "dotColor: ..." 4 lines above and hot reload will work.', textScaleFactor: 2,),
          ),
          SizedBox(
            height: 40,
            width: double.infinity,
            child: Material(
              color: Colors.blue.shade500,
              child: InkWell(onTap: () => print('2 tapped!!!'), child: const Center(child: Text('2, press me'))),
            ),
          ),
          SizedBox(
            height: 40,
            width: double.infinity,
            child: ColoredBox(
              color: Colors.blue.shade700,
              child: const Center(child: Text('2.1')),
            ),
          ),
          SizedBox(
            height: 40,
            width: double.infinity,
            child: Material(
              color: Colors.blue.shade900,
              child: InkWell(onTap: () => print('2.2 tapped!!!'), child: const Center(child: Text('2.2, press me'))),
            ),
          ),
          Card(
            color: Colors.teal.shade900,
            child: const FlutterLogo(
              size: 600,
            ),
          ),
        ],
      ),
    );
  }
}