在 MultiChildRenderObjectWidget 中处理 SingleChildScrollView
Handle SingleChildScrollView in MultiChildRenderObjectWidget
我刚刚开始使用 RenderBox 自定义 UI。
当我了解 MultiChildRenderObjectWidget 并做了一个小例子时,我遇到了以下问题:
当我在屏幕上插入许多 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,
),
),
],
),
);
}
}
我刚刚开始使用 RenderBox 自定义 UI。
当我了解 MultiChildRenderObjectWidget 并做了一个小例子时,我遇到了以下问题:
当我在屏幕上插入许多 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,
),
),
],
),
);
}
}