BoxyDelegate<LayoutData extends Object> class Null safety

A delegate that controls the layout and paint of child widgets, used by CustomBoxy.

Delegates must ensure an identical delegate would produce the same layout. If your delegate takes arguments also make sure shouldRelayout and/or shouldRepaint return true when those fields change.

A single delegate can be used by multiple widgets at a time and should not keep any state. If you need to pass information from layout to another method, store it in layoutData or BoxyChild.parentData.

Delegates may access their children by name with getChild, alternatively they can be accessed through the children list.

The default constructor accepts Listenables that can trigger re-layout and re-paint. It's much more efficient for the boxy to listen directly to animations than rebuilding the CustomBoxy with a new delegate each frame.

Layout

Override layout to control the layout of children and return what size the boxy should be.

This method should call BoxyChild.layout for each child. It should also specify the position of each child with BoxyChild.position.

If the delegate does not depend on the size of a particular child, pass useSize: false to BoxyChild.layout, this prevents a change in the child's size from causing a re-layout.

The following example lays out two children like a column where the second widget is the same width as the first:

  @override
  Size layout() {
    // Get both children by a Symbol id
    var firstChild = getChild(#first);
    var secondChild = getChild(#second);

    // Lay out the first child with the incoming constraints
    var firstSize = firstChild.layout(constraints);
    firstChild.position(Offset.zero);

    // Lay out the second child
    var secondSize = secondChild.layout(
      constraints.deflate(
        // Subtract height consumed by the first child from the constraints
        EdgeInsets.only(top: firstSize.height),
      ).tighten(
        // Force width to be the same as the first child
        width: firstSize.width,
      )
    );

    // Position the second child below the first
    secondChild.position(Offset(0, firstSize.height));

    // Calculate the total size based both child sizes
    return Size(
      firstSize.width,
      firstSize.height + secondSize.height,
    );
  }

Painting

Override paint to draw behind children, this is similar to CustomPainter.paint which gives you a Canvas.

The following example simply gives the widget a blue background:

  @override
  void paint() {
    canvas.drawRect(
      Offset.zero & render.size,
      Paint()..color = Colors.blue,
    );
  }

You can draw above children similarly by overriding paintForeground.

Override paintChildren to change how children themselves are painted, for example changing the order or adding layers.

The default behavior is to paint children at the BoxyChild.offset given to them during layout.

The canvas available to paintChildren is not transformed implicitly like paint and paintForeground, implementers of this method should draw at paintOffset and restore the canvas before painting a child. This is required by the framework because a child might need to insert its own compositing Layer between two other PictureLayers.

The following example draws a semi transparent rectangle between two children:

  @override
  void paintChildren() {
    getChild(#first).paint();
    canvas.drawRect(
      paintOffset & render.size,
      Paint()..color = Colors.blue.withOpacity(0.3),
    );
    getChild(#second).paint();
  }

Layers

In order to apply special effects to children such as transforms, opacity, clipping, etc. delegates will need to interact with the compositing tree. Boxy wraps this functionality conveniently with layers.

Before a delegate can push layers make sure to override needsCompositing. This getter can check the fields of the boxy to determine if compositing will be necessary, return true if that is the case.

The following example paints its child with 50% opacity:

  @override
  bool get needsCompositing => true;

  @override
  void paintChildren() {
    layers.opacity(
      opacity: 0.5,
      paint: () {
        getChild(#first).paint();
      },
    );
  }

Widget inflation

One of the most powerful features of the boxy library is to inflate arbitrary widgets at layout time, this would otherwise be extraordinarily difficult to implement in Flutter.

In layout delegates can inflate arbitrary widgets using the inflate method, this enables complex layouts where the contents of widgets change depending on the size and orientation of others, in addition to constraints.

After calling this method the child becomes available in children and any following painting and hit testing, it is removed from the list before the next layout.

Unlike children explicitly passed to CustomBoxy, Keys are not managed for widgets inflated during layout, this means a widgets state is preserved if inflated again with the same object id, rather than equal Keys.

The following example displays a text widget describing the size of another child:

  @override
  Size layout() {
    var firstChild = getChild(#first);

    var firstSize = firstChild.layout(constraints);
    firstChild.position(Offset.zero);

    var text = Padding(child: Text(
      "^ This guy is ${firstSize.width} x ${firstSize.height}",
      textAlign: TextAlign.center,
    ), padding: EdgeInsets.all(8));

    // Inflate the text widget
    var secondChild = inflate(text, id: #second);

    var secondSize = secondChild.layout(
      constraints.deflate(
        EdgeInsets.only(top: firstSize.height)
      ).tighten(
        width: firstSize.width
      )
    );

    secondChild.position(Offset(0, firstSize.height));

    return Size(
      firstSize.width,
      firstSize.height + secondSize.height,
    );
  }

See also:

Inheritance
Mixed in types

Constructors

BoxyDelegate({Listenable? relayout, Listenable? repaint})
Constructs a BoxyDelegate with optional relayout and repaint Listenables.

Properties

buildContext BuildContext
Gets the BuildContext of this boxy.
read-only, inherited
canvas Canvas
The current canvas, should only be accessed from paint methods.
read-only, inherited
children List<BoxyChild>
A list of each BoxyChild handle associated with the boxy, the list itself should not be modified by the delegate.
read-only, inherited
constraints BoxConstraints
The most recent constraints given to this boxy by its parent. [...]
read-only, inherited
debugPhase BoxyDelegatePhase
The current phase in the render pipeline that this boxy is performing, only valid in debug mode.
read-only, inherited
hashCode int
The hash code for this object. [...]
read-only, inherited
hitTestResult BoxHitTestResult
The current hit test result, should only be accessed from hitTest.
read-only, inherited
indexedChildCount int
The number of children that have not been given a BoxyId, this guarantees there are child ids between 0 (inclusive) and indexedChildCount (exclusive).
read-only, inherited
isDryLayout bool
Whether or not this boxy is performing a dry layout.
read-only, inherited
layers BoxyLayerContext
The current layer context, useful for pushing Layers to the scene during paintChildren. [...]
read-only, inherited
layoutData ↔ LayoutData?
A variable to hold additional data created during layout which can be used while painting and hit testing.
read / write, inherited
needsCompositing bool
Override this method to return true if the paint method will push one or more layers to paintingContext. [...]
read-only, inherited
paintingContext PaintingContext
The current painting context, should only be accessed from paint methods.
read-only, inherited
paintOffset Offset
The offset of the current paint context. [...]
read-only, inherited
render RenderBoxy<BoxyChild>
The RenderBoxyMixin of the current context.
read-only, inherited
runtimeType Type
A representation of the runtime type of the object.
read-only, inherited

Methods

addHit() → void
Adds the boxy to hitTestResult, this should typically be called from hitTest when a hit succeeds.
inherited
getChild<T extends ChildHandleType>(Object id) → T
Gets the child handle with the specified id.
inherited
hasChild(Object id) bool
Returns true if a child exists with the specified id.
inherited
hitTest(SliverOffset position) bool
Override this method to change how the boxy gets hit tested. [...]
inherited
inflate<T extends ChildHandleType>(Widget widget, {Object? id}) → T
Dynamically inflates a widget as a child of this boxy, should only be called in BoxyChild.layout. [...]
inherited
layout() Size
Override this method to lay out children and return the final size of the boxy. [...]
override
maxIntrinsicHeight(double width) double
Override to change the maximum height that this box could be without failing to correctly paint its contents within itself, without clipping. [...]
inherited
maxIntrinsicWidth(double height) double
Override to change the maximum width that this box could be without failing to correctly paint its contents within itself, without clipping. [...]
inherited
minIntrinsicHeight(double width) double
Override to change the minimum height that this box could be without failing to correctly paint its contents within itself, without clipping. [...]
inherited
minIntrinsicWidth(double height) double
Override to change the minimum width that this box could be without failing to correctly paint its contents within itself, without clipping. [...]
inherited
noSuchMethod(Invocation invocation) → dynamic
Invoked when a non-existent method or property is accessed. [...]
inherited
paint() → void
Override this method to paint below children. [...]
inherited
paintChildren() → void
Override this method to change how children get painted. [...]
inherited
paintForeground() → void
Override this method to paint above children. [...]
inherited
paintLayer(ContainerLayer layer, {VoidCallback? painter, Offset? offset, Rect? debugBounds}) → void
Paints a ContainerLayer compositing layer in the current painting context with an optional painter callback, this should only be called in paintChildren. [...]
@Deprecated('Use layers.push instead'), inherited
shouldRelayout(covariant BaseBoxyDelegate<Object, BaseBoxyChild> oldDelegate) bool
Override this method to return true when the children need to be laid out. [...]
inherited
shouldRepaint(covariant BaseBoxyDelegate<Object, BaseBoxyChild> oldDelegate) bool
Override this method to return true when the children need to be repainted. [...]
inherited
toString() String
Override this method to include additional information in the debugging data printed by debugDumpRenderTree and friends. [...]
inherited

Operators

operator ==(Object other) bool
The equality operator. [...]
inherited