CustomMultiChildLayout in Flutter
What is MultiChildLayout?
Everything in flutter is a widget, so we can say a widget that has multiple children. For example, A column or a row is MultiChildLayout. We can not specify its position in our layout. It is fixed, if we use a column then all children will be linearly placed in a vertical order.
What if we want our layout where we can specify where the child should be placed? One solution is Stack we can give the position of the child in a layout but up to some extent.
When in your layout you need some calculations to place children stack might not help. Then the CustomMultiChildLayout can be useful.
Let’s start the example,
We will create a rectangle on the center of the screen and place the circles on each of the corners and center of the rectangle.
- Create a stateful widget
class CustomMultiChildExampleScreen extends StatefulWidget {
@override
_CustomMultiChildExampleScreenState createState() => _CustomMultiChildExampleScreenState();
}class _CustomMultiChildExampleScreenState extends State<CustomMultiChildExampleScreen> {
double width = 300.0;
double height = 150.0; @override
Widget build(BuildContext context) { return Scaffold(
appBar: AppBar(
title: Text('CustomMultiChild example'),
),
body: Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Container(
height: height,
width: width,
color: Colors.lightBlueAccent,
child: CustomMultiChildLayout(
// We will add custom child and delegte here...
),
)
),
),
);
}
}
Till now, we have created a container with a height of 150 and width 300. Added a child of the container a CustomMultiChildLayout.
2. Create children with LayoutId widgets. what LayoutId does? It uniquely identifies the child and according to this id, we can set the position of each child in a view.
class _CustomMultiChildExampleScreenState extends State<CustomMultiChildExampleScreen> {
double width = 300.0;
double height = 150.0;
@override
Widget build(BuildContext context) { return Scaffold(
appBar: AppBar(
title: Text('CustomMultiChild example'),
),
body: Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Container(
height: height,
width: width,
color: Colors.lightBlueAccent,
child: CustomMultiChildLayout(
delegate: // We will add delegate in next step
children: <Widget>[
LayoutId(
id: 1,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.purpleAccent
),
child: Center(child: Text('1')),
height: 75.0,
width: 75.0,
),
),
LayoutId(
id: 2,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.purpleAccent
),
child: Center(child: Text('2')),
height: 75.0,
width: 75.0,
),
),
LayoutId(
id: 3,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.purpleAccent
),
child: Center(child: Text('3')),
height: 75.0,
width: 75.0,
),
),
LayoutId(
id: 4,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.purpleAccent
),
child: Center(child: Text('4')),
height: 75.0,
width: 75.0,
),
),
LayoutId(
id: 5,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.purpleAccent
),
child: Center(child: Text('5')),
height: 75.0,
width: 75.0,
),
),
],
),
)
),
),
);
}
}
3. Create a custom multi-child delegate
Add a new file that holds your custom multi-child delegate. Here we will write our logic to place the children in a multi-child layout. In the above snippet, we have left the delegate part empty, because the MultiChildLayoutDelegate is an abstract class and we can not use it directly. So we will have to create a class that extends the MultiChildLayoutDelegate.
class _customMultiChildLayoutDelegate extends MultiChildLayoutDelegate {
// The height and width here are to calclulate the position of the child
final maxHeight;
final maxWidth;
_customMultiChildLayoutDelegate({this.maxHeight, this.maxWidth});
}
As we write down this code, we’ll get an error and it says to implement missing methods.
4. Implement performLayout and shouldRelayout
In performLayout, we will get each child by its id, and add layoutChild and positionChild for the same. The layoutChild will give the child a width and a height. The postionChild will set the position of the child based on our calculation of Offset.
shouldRelayout checks for the changes in maxWidth and maxHeight in our case, and if those do not match with the old then it will perform layout on the children.
class _customMultiChildLayoutDelegate extends MultiChildLayoutDelegate {
final maxHeight;
final maxWidth;
_customMultiChildLayoutDelegate({this.maxHeight, this.maxWidth});
@override
void performLayout(Size size) {
if (hasChild(1)) {
layoutChild(1, BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight));
// Center of the box
double dx = ((size.width / 2) - (maxWidth / 2));
double dy = ((size.height / 2) - (maxHeight / 2));
positionChild(1, Offset(dx, dy));
}
if (hasChild(2)) {
layoutChild(2, BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight));
//Top left of the box
double dx = -(maxWidth/2);
double dy = -((maxHeight / 2));
positionChild(2, Offset(dx, dy));
}
if (hasChild(3)) {
layoutChild(3, BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight));
// Top right of the box
double dx = ((size.width) - (maxWidth/2));
double dy = -((maxHeight / 2));
positionChild(3, Offset(dx, dy));
}
if (hasChild(4)) {
layoutChild(4, BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight));
// bottom right of the box double dx = ((size.width) - (maxWidth/2));
double dy = ((maxHeight * 2) + (maxHeight / 2));
positionChild(4, Offset(dx, dy));
}
if (hasChild(5)) {
layoutChild(5, BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight));
//bottom left of the box double dx = -(maxWidth/2);
double dy = ((maxHeight * 2) + (maxHeight / 2));
positionChild(5, Offset(dx, dy));
}
}
@override
bool shouldRelayout(_customMultiChildLayoutDelegate oldDelegate) {
return (maxHeight != oldDelegate.maxHeight || maxWidth != oldDelegate.maxWidth);
}
}
5. Add a delegate to the CustomMultiChildLayout in a stateful widget class.
Specify the width and height that you want to pass to the delegate. Here, we have added maxHeight = 50.0 and maxWidth = 50.0.
class _CustomMultiChildExampleScreenState extends State<CustomMultiChildExampleScreen> {
double width = 300.0;
double height = 150.0;
double maxHeight = 50.0;
double maxWidth = 50.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('CustomMultiChild example'),
),
body: Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Container(
height: height,
width: width,
color: Colors.lightBlueAccent,
child: CustomMultiChildLayout(
delegate: _customMultiChildLayoutDelegate(maxHeight: maxHeight, maxWidth: maxWidth),
children: <Widget>[
LayoutId(
id: 1,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.purpleAccent
),
child: Center(child: Text('1')),
height: 75.0,
width: 75.0,
),
),
LayoutId(
id: 2,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.purpleAccent
),
child: Center(child: Text('2')),
height: 75.0,
width: 75.0,
),
),
LayoutId(
id: 3,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.purpleAccent
),
child: Center(child: Text('3')),
height: 75.0,
width: 75.0,
),
),
LayoutId(
id: 4,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.purpleAccent
),
child: Center(child: Text('4')),
height: 75.0,
width: 75.0,
),
),
LayoutId(
id: 5,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.purpleAccent
),
child: Center(child: Text('5')),
height: 75.0,
width: 75.0,
),
),
],
),
)
),
),
);
}
}
6. Run the project.
Now, We have added children and multi-child delegate and we’re good to go! Run the project and we’ll get the output like this: