| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /// Flutter code sample for [SlottedMultiChildRenderObjectWidgetMixin]. |
| |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/rendering.dart'; |
| |
| /// Slots used for the children of [Diagonal] and [RenderDiagonal]. |
| enum DiagonalSlot { |
| topLeft, |
| bottomRight, |
| } |
| |
| /// A widget that demonstrates the usage of |
| /// [SlottedMultiChildRenderObjectWidgetMixin] by providing slots for two |
| /// children that will be arranged diagonally. |
| class Diagonal extends RenderObjectWidget with SlottedMultiChildRenderObjectWidgetMixin<DiagonalSlot> { |
| const Diagonal({ |
| super.key, |
| this.topLeft, |
| this.bottomRight, |
| this.backgroundColor, |
| }); |
| |
| final Widget? topLeft; |
| final Widget? bottomRight; |
| final Color? backgroundColor; |
| |
| @override |
| Iterable<DiagonalSlot> get slots => DiagonalSlot.values; |
| |
| @override |
| Widget? childForSlot(DiagonalSlot slot) { |
| switch (slot) { |
| case DiagonalSlot.topLeft: |
| return topLeft; |
| case DiagonalSlot.bottomRight: |
| return bottomRight; |
| } |
| } |
| |
| // The [createRenderObject] and [updateRenderObject] methods configure the |
| // [RenderObject] backing this widget with the configuration of the widget. |
| // They do not need to do anything with the children of the widget, though. |
| // The children of the widget are automatically configured on the |
| // [RenderObject] by [SlottedRenderObjectElement.mount] and |
| // [SlottedRenderObjectElement.update]. |
| |
| @override |
| SlottedContainerRenderObjectMixin<DiagonalSlot> createRenderObject( |
| BuildContext context, |
| ) { |
| return RenderDiagonal( |
| backgroundColor: backgroundColor, |
| ); |
| } |
| |
| @override |
| void updateRenderObject( |
| BuildContext context, |
| SlottedContainerRenderObjectMixin<DiagonalSlot> renderObject, |
| ) { |
| (renderObject as RenderDiagonal).backgroundColor = backgroundColor; |
| } |
| } |
| |
| /// A render object that demonstrates the usage of |
| /// [SlottedContainerRenderObjectMixin] by providing slots for two children that |
| /// will be arranged diagonally. |
| class RenderDiagonal extends RenderBox with SlottedContainerRenderObjectMixin<DiagonalSlot>, DebugOverflowIndicatorMixin { |
| RenderDiagonal({Color? backgroundColor}) : _backgroundColor = backgroundColor; |
| |
| // Getters and setters to configure the [RenderObject] with the configuration |
| // of the [Widget]. These mostly contain boilerplate code, but depending on |
| // where the configuration value is used, the setter has to call |
| // [markNeedsLayout], [markNeedsPaint], or [markNeedsSemanticsUpdate]. |
| Color? get backgroundColor => _backgroundColor; |
| Color? _backgroundColor; |
| set backgroundColor(Color? value) { |
| assert(value != null); |
| if (_backgroundColor == value) { |
| return; |
| } |
| _backgroundColor = value; |
| markNeedsPaint(); |
| } |
| |
| // Getters to simplify accessing the slotted children. |
| RenderBox? get _topLeft => childForSlot(DiagonalSlot.topLeft); |
| RenderBox? get _bottomRight => childForSlot(DiagonalSlot.bottomRight); |
| |
| // The size this render object would have if the incoming constraints were |
| // unconstrained; calculated during performLayout used during paint for an |
| // assertion that checks for unintended overflow. |
| late Size _childrenSize; |
| |
| // Returns children in hit test order. |
| @override |
| Iterable<RenderBox> get children { |
| return <RenderBox>[ |
| if (_topLeft != null) |
| _topLeft!, |
| if (_bottomRight != null) |
| _bottomRight!, |
| ]; |
| } |
| |
| // LAYOUT |
| |
| @override |
| void performLayout() { |
| // Children are allowed to be as big as they want (= unconstrained). |
| const BoxConstraints childConstraints = BoxConstraints(); |
| |
| // Lay out the top left child and position it at offset zero. |
| Size topLeftSize = Size.zero; |
| final RenderBox? topLeft = _topLeft; |
| if (topLeft != null) { |
| topLeft.layout(childConstraints, parentUsesSize: true); |
| _positionChild(topLeft, Offset.zero); |
| topLeftSize = topLeft.size; |
| } |
| |
| // Lay out the bottom right child and position it at the bottom right corner |
| // of the top left child. |
| Size bottomRightSize = Size.zero; |
| final RenderBox? bottomRight = _bottomRight; |
| if (bottomRight != null) { |
| bottomRight.layout(childConstraints, parentUsesSize: true); |
| _positionChild( |
| bottomRight, |
| Offset(topLeftSize.width, topLeftSize.height), |
| ); |
| bottomRightSize = bottomRight.size; |
| } |
| |
| // Calculate the overall size and constrain it to the given constraints. |
| // Any overflow is marked (in debug mode) during paint. |
| _childrenSize = Size( |
| topLeftSize.width + bottomRightSize.width, |
| topLeftSize.height + bottomRightSize.height, |
| ); |
| size = constraints.constrain(_childrenSize); |
| } |
| |
| void _positionChild(RenderBox child, Offset offset) { |
| (child.parentData! as BoxParentData).offset = offset; |
| } |
| |
| // PAINT |
| |
| @override |
| void paint(PaintingContext context, Offset offset) { |
| // Paint the background. |
| if (backgroundColor != null) { |
| context.canvas.drawRect( |
| offset & size, |
| Paint() |
| ..color = backgroundColor!, |
| ); |
| } |
| |
| void paintChild(RenderBox child, PaintingContext context, Offset offset) { |
| final BoxParentData childParentData = child.parentData! as BoxParentData; |
| context.paintChild(child, childParentData.offset + offset); |
| } |
| |
| // Paint the children at the offset calculated during layout. |
| final RenderBox? topLeft = _topLeft; |
| if (topLeft != null) { |
| paintChild(topLeft, context, offset); |
| } |
| final RenderBox? bottomRight = _bottomRight; |
| if (bottomRight != null) { |
| paintChild(bottomRight, context, offset); |
| } |
| |
| // Paint an overflow indicator in debug mode if the children want to be |
| // larger than the incoming constraints allow. |
| assert(() { |
| paintOverflowIndicator( |
| context, |
| offset, |
| Offset.zero & size, |
| Offset.zero & _childrenSize, |
| ); |
| return true; |
| }()); |
| } |
| |
| // HIT TEST |
| |
| @override |
| bool hitTestChildren(BoxHitTestResult result, { required Offset position }) { |
| for (final RenderBox child in children) { |
| final BoxParentData parentData = child.parentData! as BoxParentData; |
| final bool isHit = result.addWithPaintOffset( |
| offset: parentData.offset, |
| position: position, |
| hitTest: (BoxHitTestResult result, Offset transformed) { |
| assert(transformed == position - parentData.offset); |
| return child.hitTest(result, position: transformed); |
| }, |
| ); |
| if (isHit) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // INTRINSICS |
| |
| // Incoming height/width are ignored as children are always laid out unconstrained. |
| |
| @override |
| double computeMinIntrinsicWidth(double height) { |
| final double topLeftWidth = _topLeft?.getMinIntrinsicWidth(double.infinity) ?? 0; |
| final double bottomRightWith = _bottomRight?.getMinIntrinsicWidth(double.infinity) ?? 0; |
| return topLeftWidth + bottomRightWith; |
| } |
| |
| @override |
| double computeMaxIntrinsicWidth(double height) { |
| final double topLeftWidth = _topLeft?.getMaxIntrinsicWidth(double.infinity) ?? 0; |
| final double bottomRightWith = _bottomRight?.getMaxIntrinsicWidth(double.infinity) ?? 0; |
| return topLeftWidth + bottomRightWith; } |
| |
| @override |
| double computeMinIntrinsicHeight(double width) { |
| final double topLeftHeight = _topLeft?.getMinIntrinsicHeight(double.infinity) ?? 0; |
| final double bottomRightHeight = _bottomRight?.getMinIntrinsicHeight(double.infinity) ?? 0; |
| return topLeftHeight + bottomRightHeight; |
| } |
| |
| @override |
| double computeMaxIntrinsicHeight(double width) { |
| final double topLeftHeight = _topLeft?.getMaxIntrinsicHeight(double.infinity) ?? 0; |
| final double bottomRightHeight = _bottomRight?.getMaxIntrinsicHeight(double.infinity) ?? 0; |
| return topLeftHeight + bottomRightHeight; |
| } |
| |
| @override |
| Size computeDryLayout(BoxConstraints constraints) { |
| const BoxConstraints childConstraints = BoxConstraints(); |
| final Size topLeftSize = _topLeft?.computeDryLayout(childConstraints) ?? Size.zero; |
| final Size bottomRightSize = _bottomRight?.computeDryLayout(childConstraints) ?? Size.zero; |
| return constraints.constrain(Size( |
| topLeftSize.width + bottomRightSize.width, |
| topLeftSize.height + bottomRightSize.height, |
| )); |
| } |
| } |
| |
| class ExampleWidget extends StatelessWidget { |
| const ExampleWidget({super.key}); |
| |
| @override |
| Widget build(BuildContext context) { |
| return MaterialApp( |
| home: Scaffold( |
| appBar: AppBar(title: const Text('Slotted RenderObject Example')), |
| body: Center( |
| child: Diagonal( |
| topLeft: Container( |
| color: Colors.green, |
| height: 100, |
| width: 200, |
| child: const Center( |
| child: Text('topLeft'), |
| ), |
| ), |
| bottomRight: Container( |
| color: Colors.yellow, |
| height: 60, |
| width: 30, |
| child: const Center( |
| child: Text('bottomRight'), |
| ), |
| ), |
| backgroundColor: Colors.blue, |
| ), |
| ), |
| ), |
| ); |
| } |
| } |
| |
| void main() { |
| runApp(const ExampleWidget()); |
| } |