This tutorial is about how to use CustomSingleChildLayout
in Flutter which includes how to create a custom SingleChildLayoutDelegate
.
CustomSingleChildLayout
is a widget that defers the layout of its single child to a delegate. The layout constraints and the position of the child are determined by the delegate. The delegate can also be used to set the size of the parent. However, the parent's size cannot depend on the child's size.
Using CustomSingleChildLayout
Widget
Below is the constructor to be used.
const CustomSingleChildLayout({
Key key,
@required this.delegate,
Widget child,
})
You need to pass delegate argument whose type is SingleChildLayoutDelegate
which is used to set the layout and constraints of the child
. Because SingleChildLayoutDelegate
is an abstract class, you need to create a custom class that extends it.
There are some methods you can override. The first one is getSize
. It's used to determine the size of the CustomSingleChildLayout
object (not the child's). The method has a parameter whose type is BoxConstraints
which is the given constraints. If you don't override it, the value defaults to constraints.biggest
.
Size getSize(BoxConstraints constraints) => constraints.biggest;
To set the constraints of the child based on the incoming constraints, you can override the getConstraintsForChild
method. The constraints returned by the method are given to the child, so that the size of the child must satisfy the constraints.
BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints;
To set the position of the child, you need to override getPositionForChild
method. It has two parameters size
and childSize
which are the size of the parent and child respectively. The passed size
can be different from the value returned by getSize
if it doesn't comply with the constraints passed to getSize
. The passed childSize
is a value that satisfies the constraints from getConstraintsForChild
.
Offset getPositionForChild(Size size, Size childSize) => Offset.zero;
The other method you can override is shouldRelayout
. If it returns true
, it will trigger the layout methods (getSize
, getConstraintsForChild
, getPositionForChild
). It's called every time a new instance of the custom single child layout delegate class is created. However, the layout methods can be called even if this method returns false
(e.g. if the ancestor changes its layout) or not called at all (e.g. if the parent changes size).
bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate);
During the layout process, the getSize
function is called to determine the size of CustomSingleChildLayout
. After that, it calls getConstraintsForChild
to get the constraints for the child. Then, getPositionForChild
is called to determine the position of the child. Every time a new instance of this class is created, shouldRelayout
method is called. To trigger the relayout, you can pass relayout
argument to the constructor of SingleChildLayoutDelegate
.
Below is an example of a class that extends SingleChildLayoutDelegate
. It passes a ValueNotifier
listenable as the relayout
argument when calling the constructor of its super class. It will listen to the listenable and relayout will be performed whenever the listenable notifies that the value changes. The passed size will be used as the size of the CustomSingleChildLayout
object. The width and the height of the child are set to be half of the passed size. It also adds offset to the child as many as one fourth of the size.
class CustomLayoutDelegate extends SingleChildLayoutDelegate {
CustomLayoutDelegate(this.size) : super(relayout: size);
final ValueNotifier<Size> size;
@override
Size getSize(BoxConstraints constraints) {
return size.value;
}
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return BoxConstraints.tight(size.value / 2);
}
@override
Offset getPositionForChild(Size size, Size childSize) {
return Offset(size.width / 4, size.height / 4);
}
@override
bool shouldRelayout(CustomLayoutDelegate oldDelegate) {
return size != oldDelegate.size;
}
}
Below is how to use the custom class.
CustomSingleChildLayout(
delegate: CustomLayoutDelegate(_size),
child: Container(
color: Colors.teal,
width: 50,
height: 300,
),
)
Output:
From the output, you can see that the delegate is responsible to set the size of the child. Even though the child has defined size, it turns out that the constraints defined by the delegate are used to determine the size of the child.
Properties
Key key
: The widget's key.SingleChildLayoutDelegate delegate
*: The delegate that controls the layout of the child.Widget child
: The widget below this widget in the tree whose layout is controlled bydelegate
.
*: required
Full Code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Woolha.com Flutter Tutorial',
home: _CustomSingleChildLayoutWidgetStateExample(),
);
}
}
class _CustomSingleChildLayoutWidgetStateExample extends StatelessWidget {
final ValueNotifier<Size> _size = ValueNotifier<Size>(const Size(200.0, 100.0));
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Woolha.com Flutter Tutorial'),
),
body: Column(
children: [
CustomSingleChildLayout(
delegate: CustomLayoutDelegate(_size),
child: Container(
color: Colors.teal,
width: 50,
height: 300,
),
),
],
),
);
}
}
class CustomLayoutDelegate extends SingleChildLayoutDelegate {
CustomLayoutDelegate(this.size) : super(relayout: size);
final ValueNotifier<Size> size;
@override
Size getSize(BoxConstraints constraints) {
return size.value;
}
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return BoxConstraints.tight(size.value / 2);
}
@override
Offset getPositionForChild(Size size, Size childSize) {
return Offset(size.width / 4, size.height / 4);
}
@override
bool shouldRelayout(CustomLayoutDelegate oldDelegate) {
return size != oldDelegate.size;
}
}
Summary
That's how to use CustomSingleChildLayout
widget. You need to create a class that extends SingleChildLayoutDelegate
which is responsible to set the layout of the child. The delegate can be used to set the constraints and position of the child.