Flutter - Using CustomSingleChildLayout Widget Examples

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:

Flutter - Using CustomSingleChildLayout Widget Examples

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 by delegate.

*: 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.