Last active
January 6, 2025 11:32
-
-
Save jxw1102/a9b58a78a80c8e2f54233b418429fa50 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
import 'dart:math' as math; | |
class CustomLayout extends MultiChildRenderObjectWidget { | |
CustomLayout({ | |
Key key, | |
List<Widget> children = const <Widget>[], | |
}) : super(key: key, children: children); | |
@override | |
RenderCustomLayoutBox createRenderObject(BuildContext context) { | |
return RenderCustomLayoutBox(); | |
} | |
} | |
class RenderCustomLayoutBox extends RenderBox | |
with ContainerRenderObjectMixin<RenderBox, CustomLayoutParentData>, | |
RenderBoxContainerDefaultsMixin<RenderBox, CustomLayoutParentData> { | |
RenderCustomLayoutBox({ | |
List<RenderBox> children, | |
}) { | |
addAll(children); | |
} | |
@override | |
void setupParentData(RenderBox child) { | |
if (child.parentData is! CustomLayoutParentData) { | |
child.parentData = CustomLayoutParentData(); | |
} | |
} | |
double _getIntrinsicHeight(double childSize(RenderBox child)) { | |
double inflexibleSpace = 0.0; | |
RenderBox child = firstChild; | |
while (child != null) { | |
if (child == lastChild) | |
break; | |
inflexibleSpace += childSize(child); | |
final FlexParentData childParentData = child.parentData; | |
child = childParentData.nextSibling; | |
} | |
return inflexibleSpace; | |
} | |
double _getIntrinsicWidth(double childSize(RenderBox child)) { | |
double maxSpace = 0.0; | |
RenderBox child = firstChild; | |
while (child != null) { | |
if (child == lastChild) | |
break; | |
maxSpace = math.max(maxSpace, childSize(child)); | |
final FlexParentData childParentData = child.parentData; | |
child = childParentData.nextSibling; | |
} | |
return maxSpace; | |
} | |
@override | |
double computeMinIntrinsicWidth(double height) { | |
return _getIntrinsicWidth((RenderBox child) => child.getMinIntrinsicWidth(height)); | |
} | |
@override | |
double computeMaxIntrinsicWidth(double height) { | |
return _getIntrinsicWidth((RenderBox child) => child.getMaxIntrinsicWidth(height)); | |
} | |
@override | |
double computeMinIntrinsicHeight(double width) { | |
return _getIntrinsicHeight((RenderBox child) => child.getMinIntrinsicHeight(width)); | |
} | |
@override | |
double computeMaxIntrinsicHeight(double width) { | |
return _getIntrinsicHeight((RenderBox child) => child.getMaxIntrinsicHeight(width)); | |
} | |
@override | |
void performLayout() { | |
if (childCount == 0) { | |
size = constraints.biggest; | |
assert(size.isFinite); | |
return; | |
} | |
double width = constraints.maxWidth; | |
double height = 0; | |
RenderBox child = firstChild; | |
while (child != null) { | |
if (child == lastChild) | |
break; | |
final CustomLayoutParentData childParentData = child.parentData; | |
child.layout(BoxConstraints.tightFor(width: width), parentUsesSize: true); | |
childParentData.offset = Offset(0, height); | |
final Size childSize = child.size; | |
width = math.max(width, childSize.width); | |
height += childSize.height; | |
child = childParentData.nextSibling; | |
} | |
size = Size(width, height); | |
lastChild.layout(BoxConstraints(maxWidth: width, maxHeight: height), parentUsesSize: true); | |
final CustomLayoutParentData childParentData = lastChild.parentData; | |
final double margin = 20; | |
final double x = size.width - lastChild.size.width - margin; | |
final double y = height - childParentData.previousSibling.size.height - lastChild.size.height / 2; | |
childParentData.offset = Offset(x, y); | |
} | |
@override | |
void paint(PaintingContext context, Offset offset) { | |
defaultPaint(context, offset); | |
} | |
@override | |
bool hitTestChildren(HitTestResult result, { Offset position }) { | |
return defaultHitTestChildren(result, position: position); | |
} | |
} | |
class CustomLayoutParentData extends ContainerBoxParentData<RenderBox> { | |
} |
Here's my update for a nullable version:
class CustomLayout extends MultiChildRenderObjectWidget {
const CustomLayout({
super.key,
super.children = const <Widget>[],
});
@override
RenderCustomLayoutBox createRenderObject(BuildContext context) {
return RenderCustomLayoutBox();
}
}
class RenderCustomLayoutBox extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, CustomLayoutParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, CustomLayoutParentData> {
RenderCustomLayoutBox({
List<RenderBox>? children,
}) {
addAll(children);
}
@override
void setupParentData(RenderBox child) {
if (child.parentData is! CustomLayoutParentData) {
child.parentData = CustomLayoutParentData();
}
}
double _getIntrinsicHeight(double Function(RenderBox child) childSize) {
double inflexibleSpace = 0.0;
RenderBox? child = firstChild;
while (child != null) {
if (child == lastChild) break;
inflexibleSpace += childSize(child);
final FlexParentData childParentData = child.parentData as FlexParentData;
child = childParentData.nextSibling;
}
return inflexibleSpace;
}
double _getIntrinsicWidth(double Function(RenderBox child) childSize) {
double maxSpace = 0.0;
RenderBox? child = firstChild;
while (child != null) {
if (child == lastChild) break;
maxSpace = math.max(maxSpace, childSize(child));
final FlexParentData childParentData = child.parentData as FlexParentData;
child = childParentData.nextSibling;
}
return maxSpace;
}
@override
double computeMinIntrinsicWidth(double height) {
return _getIntrinsicWidth(
(RenderBox child) => child.getMinIntrinsicWidth(height),
);
}
@override
double computeMaxIntrinsicWidth(double height) {
return _getIntrinsicWidth(
(RenderBox child) => child.getMaxIntrinsicWidth(height),
);
}
@override
double computeMinIntrinsicHeight(double width) {
return _getIntrinsicHeight(
(RenderBox child) => child.getMinIntrinsicHeight(width),
);
}
@override
double computeMaxIntrinsicHeight(double width) {
return _getIntrinsicHeight(
(RenderBox child) => child.getMaxIntrinsicHeight(width),
);
}
@override
void performLayout() {
if (childCount == 0) {
size = constraints.biggest;
assert(size.isFinite);
return;
}
double width = constraints.maxWidth;
double height = 0;
RenderBox? child = firstChild;
while (child != null) {
if (child == lastChild) break;
final CustomLayoutParentData childParentData =
child.parentData as CustomLayoutParentData;
child.layout(BoxConstraints.tightFor(width: width), parentUsesSize: true);
childParentData.offset = Offset(0, height);
final Size childSize = child.size;
width = math.max(width, childSize.width);
height += childSize.height;
child = childParentData.nextSibling;
}
size = Size(width, height);
lastChild?.layout(
BoxConstraints(maxWidth: width, maxHeight: height),
parentUsesSize: true,
);
final CustomLayoutParentData? childParentData =
lastChild?.parentData as CustomLayoutParentData?;
const double margin = 20;
final double x = size.width - (lastChild?.size.width ?? 0) - margin;
final double y = height -
(childParentData?.previousSibling?.size.height ?? 0) -
(lastChild?.size.height ?? 0) / 2;
childParentData?.offset = Offset(x, y);
}
@override
void paint(PaintingContext context, Offset offset) {
defaultPaint(context, offset);
}
@override
bool hitTestChildren(HitTestResult result, {required Offset position}) {
return defaultHitTestChildren(
result as BoxHitTestResult,
position: position,
);
}
}
class CustomLayoutParentData extends ContainerBoxParentData<RenderBox> {}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for this pattern. Any advice for someone who would like to enforce the meaning of the children rather than depend on position and such? Think of your example, but I would like to pass the children in using properties like
top
,bottom
,overlay
. If I do this, is it important to calladdAll([top, bottom, overlay])
or maintain achildren
list? I wouldn't necessarily usefirstChild
to return a RenderBox, but I don't know how to layout if I have a reference to theWidget
, and not theRenderBox
.Or would you recommend, I pass in the widgets as children, and then interrogate the
RenderBox
duringperformLayout
to determine which widget it represents?