| // 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. |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/rendering.dart'; |
| |
| import 'debug.dart'; |
| import 'framework.dart'; |
| |
| /// The signature of the [LayoutBuilder] builder function. |
| typedef LayoutWidgetBuilder = Widget Function(BuildContext context, BoxConstraints constraints); |
| |
| /// An abstract superclass for widgets that defer their building until layout. |
| /// |
| /// Similar to the [Builder] widget except that the framework calls the [builder] |
| /// function at layout time and provides the constraints that this widget should |
| /// adhere to. This is useful when the parent constrains the child's size and layout, |
| /// and doesn't depend on the child's intrinsic size. |
| abstract class ConstrainedLayoutBuilder<ConstraintType extends Constraints> extends RenderObjectWidget { |
| /// Creates a widget that defers its building until layout. |
| /// |
| /// The [builder] argument must not be null, and the returned widget should not |
| /// be null. |
| const ConstrainedLayoutBuilder({ |
| Key key, |
| @required this.builder, |
| }) : assert(builder != null), |
| super(key: key); |
| |
| @override |
| _LayoutBuilderElement<ConstraintType> createElement() => _LayoutBuilderElement<ConstraintType>(this); |
| |
| /// Called at layout time to construct the widget tree. |
| /// |
| /// The builder must not return null. |
| final Widget Function(BuildContext, ConstraintType) builder; |
| |
| // updateRenderObject is redundant with the logic in the LayoutBuilderElement below. |
| } |
| |
| class _LayoutBuilderElement<ConstraintType extends Constraints> extends RenderObjectElement { |
| _LayoutBuilderElement(ConstrainedLayoutBuilder<ConstraintType> widget) : super(widget); |
| |
| @override |
| ConstrainedLayoutBuilder<ConstraintType> get widget => super.widget as ConstrainedLayoutBuilder<ConstraintType>; |
| |
| @override |
| RenderConstrainedLayoutBuilder<ConstraintType, RenderObject> get renderObject => super.renderObject as RenderConstrainedLayoutBuilder<ConstraintType, RenderObject>; |
| |
| Element _child; |
| |
| @override |
| void visitChildren(ElementVisitor visitor) { |
| if (_child != null) |
| visitor(_child); |
| } |
| |
| @override |
| void forgetChild(Element child) { |
| assert(child == _child); |
| _child = null; |
| super.forgetChild(child); |
| } |
| |
| @override |
| void mount(Element parent, dynamic newSlot) { |
| super.mount(parent, newSlot); // Creates the renderObject. |
| renderObject.updateCallback(_layout); |
| } |
| |
| @override |
| void update(ConstrainedLayoutBuilder<ConstraintType> newWidget) { |
| assert(widget != newWidget); |
| super.update(newWidget); |
| assert(widget == newWidget); |
| renderObject.updateCallback(_layout); |
| renderObject.markNeedsLayout(); |
| } |
| |
| @override |
| void performRebuild() { |
| // This gets called if markNeedsBuild() is called on us. |
| // That might happen if, e.g., our builder uses Inherited widgets. |
| renderObject.markNeedsLayout(); |
| super.performRebuild(); // Calls widget.updateRenderObject (a no-op in this case). |
| } |
| |
| @override |
| void unmount() { |
| renderObject.updateCallback(null); |
| super.unmount(); |
| } |
| |
| void _layout(ConstraintType constraints) { |
| owner.buildScope(this, () { |
| Widget built; |
| if (widget.builder != null) { |
| try { |
| built = widget.builder(this, constraints); |
| debugWidgetBuilderValue(widget, built); |
| } catch (e, stack) { |
| built = ErrorWidget.builder( |
| _debugReportException( |
| ErrorDescription('building $widget'), |
| e, |
| stack, |
| informationCollector: () sync* { |
| yield DiagnosticsDebugCreator(DebugCreator(this)); |
| }, |
| ), |
| ); |
| } |
| } |
| try { |
| _child = updateChild(_child, built, null); |
| assert(_child != null); |
| } catch (e, stack) { |
| built = ErrorWidget.builder( |
| _debugReportException( |
| ErrorDescription('building $widget'), |
| e, |
| stack, |
| informationCollector: () sync* { |
| yield DiagnosticsDebugCreator(DebugCreator(this)); |
| }, |
| ), |
| ); |
| _child = updateChild(null, built, slot); |
| } |
| }); |
| } |
| |
| @override |
| void insertChildRenderObject(RenderObject child, dynamic slot) { |
| final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject; |
| assert(slot == null); |
| assert(renderObject.debugValidateChild(child)); |
| renderObject.child = child; |
| assert(renderObject == this.renderObject); |
| } |
| |
| @override |
| void moveChildRenderObject(RenderObject child, dynamic slot) { |
| assert(false); |
| } |
| |
| @override |
| void removeChildRenderObject(RenderObject child) { |
| final RenderConstrainedLayoutBuilder<ConstraintType, RenderObject> renderObject = this.renderObject; |
| assert(renderObject.child == child); |
| renderObject.child = null; |
| assert(renderObject == this.renderObject); |
| } |
| } |
| |
| /// Generic mixin for [RenderObject]s created by [ConstrainedLayoutBuilder]. |
| /// |
| /// Provides a callback that should be called at layout time, typically in |
| /// [RenderObject.performLayout]. |
| mixin RenderConstrainedLayoutBuilder<ConstraintType extends Constraints, ChildType extends RenderObject> on RenderObjectWithChildMixin<ChildType> { |
| LayoutCallback<ConstraintType> _callback; |
| /// Change the layout callback. |
| void updateCallback(LayoutCallback<ConstraintType> value) { |
| if (value == _callback) |
| return; |
| _callback = value; |
| markNeedsLayout(); |
| } |
| |
| /// Invoke the layout callback. |
| void layoutAndBuildChild() { |
| assert(_callback != null); |
| invokeLayoutCallback(_callback); |
| } |
| } |
| |
| /// Builds a widget tree that can depend on the parent widget's size. |
| /// |
| /// Similar to the [Builder] widget except that the framework calls the [builder] |
| /// function at layout time and provides the parent widget's constraints. This |
| /// is useful when the parent constrains the child's size and doesn't depend on |
| /// the child's intrinsic size. The [LayoutBuilder]'s final size will match its |
| /// child's size. |
| /// |
| /// {@youtube 560 315 https://www.youtube.com/watch?v=IYDVcriKjsw} |
| /// |
| /// If the child should be smaller than the parent, consider wrapping the child |
| /// in an [Align] widget. If the child might want to be bigger, consider |
| /// wrapping it in a [SingleChildScrollView]. |
| /// |
| /// See also: |
| /// |
| /// * [SliverLayoutBuilder], the sliver counterpart of this widget. |
| /// * [Builder], which calls a `builder` function at build time. |
| /// * [StatefulBuilder], which passes its `builder` function a `setState` callback. |
| /// * [CustomSingleChildLayout], which positions its child during layout. |
| class LayoutBuilder extends ConstrainedLayoutBuilder<BoxConstraints> { |
| /// Creates a widget that defers its building until layout. |
| /// |
| /// The [builder] argument must not be null. |
| const LayoutBuilder({ |
| Key key, |
| LayoutWidgetBuilder builder, |
| }) : super(key: key, builder: builder); |
| |
| @override |
| LayoutWidgetBuilder get builder => super.builder; |
| |
| @override |
| _RenderLayoutBuilder createRenderObject(BuildContext context) => _RenderLayoutBuilder(); |
| } |
| |
| class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<RenderBox>, RenderConstrainedLayoutBuilder<BoxConstraints, RenderBox> { |
| @override |
| double computeMinIntrinsicWidth(double height) { |
| assert(_debugThrowIfNotCheckingIntrinsics()); |
| return 0.0; |
| } |
| |
| @override |
| double computeMaxIntrinsicWidth(double height) { |
| assert(_debugThrowIfNotCheckingIntrinsics()); |
| return 0.0; |
| } |
| |
| @override |
| double computeMinIntrinsicHeight(double width) { |
| assert(_debugThrowIfNotCheckingIntrinsics()); |
| return 0.0; |
| } |
| |
| @override |
| double computeMaxIntrinsicHeight(double width) { |
| assert(_debugThrowIfNotCheckingIntrinsics()); |
| return 0.0; |
| } |
| |
| @override |
| void performLayout() { |
| final BoxConstraints constraints = this.constraints; |
| layoutAndBuildChild(); |
| if (child != null) { |
| child.layout(constraints, parentUsesSize: true); |
| size = constraints.constrain(child.size); |
| } else { |
| size = constraints.biggest; |
| } |
| } |
| |
| @override |
| bool hitTestChildren(BoxHitTestResult result, { Offset position }) { |
| return child?.hitTest(result, position: position) ?? false; |
| } |
| |
| @override |
| void paint(PaintingContext context, Offset offset) { |
| if (child != null) |
| context.paintChild(child, offset); |
| } |
| |
| bool _debugThrowIfNotCheckingIntrinsics() { |
| assert(() { |
| if (!RenderObject.debugCheckingIntrinsics) { |
| throw FlutterError( |
| 'LayoutBuilder does not support returning intrinsic dimensions.\n' |
| 'Calculating the intrinsic dimensions would require running the layout ' |
| 'callback speculatively, which might mutate the live render object tree.' |
| ); |
| } |
| return true; |
| }()); |
| |
| return true; |
| } |
| } |
| |
| FlutterErrorDetails _debugReportException( |
| DiagnosticsNode context, |
| dynamic exception, |
| StackTrace stack, { |
| InformationCollector informationCollector, |
| }) { |
| final FlutterErrorDetails details = FlutterErrorDetails( |
| exception: exception, |
| stack: stack, |
| library: 'widgets library', |
| context: context, |
| informationCollector: informationCollector, |
| ); |
| FlutterError.reportError(details); |
| return details; |
| } |