// Copyright 2016 The Chromium 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 'debug.dart'; import 'framework.dart'; import 'package:flutter/rendering.dart'; import 'package:meta/meta.dart'; /// The signature of the [LayoutBuilder] builder function. typedef Widget LayoutWidgetBuilder(BuildContext context, BoxConstraints constraints); /// 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. /// /// 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 [ScrollableViewport]. /// /// See also: /// /// * [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 RenderObjectWidget { /// Creates a widget that defers its building until layout. /// /// The [builder] argument must not be null. LayoutBuilder({ Key key, @required this.builder }) : super(key: key) { assert(builder != null); } /// Called at layout time to construct the widget tree. The builder must not /// return null. final LayoutWidgetBuilder builder; @override _LayoutBuilderElement createElement() => new _LayoutBuilderElement(this); @override _RenderLayoutBuilder createRenderObject(BuildContext context) => new _RenderLayoutBuilder(); } class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<RenderBox> { _RenderLayoutBuilder({ LayoutCallback callback }) : _callback = callback; LayoutCallback get callback => _callback; LayoutCallback _callback; set callback(LayoutCallback value) { if (value == _callback) return; _callback = value; markNeedsLayout(); } bool _debugThrowIfNotCheckingIntrinsics() { assert(() { if (!RenderObject.debugCheckingIntrinsics) { throw new 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; } @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() { if (callback != null) invokeLayoutCallback(callback); if (child != null) { child.layout(constraints, parentUsesSize: true); size = constraints.constrain(child.size); } else { size = constraints.biggest; } } @override bool hitTestChildren(HitTestResult result, { Point position }) { return child?.hitTest(result, position: position) ?? false; } @override void paint(PaintingContext context, Offset offset) { if (child != null) context.paintChild(child, offset); } } class _LayoutBuilderElement extends RenderObjectElement { _LayoutBuilderElement(LayoutBuilder widget) : super(widget); @override LayoutBuilder get widget => super.widget; @override _RenderLayoutBuilder get renderObject => super.renderObject; Element _child; @override void visitChildren(ElementVisitor visitor) { if (_child != null) visitor(_child); } @override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); // Creates the renderObject. renderObject.callback = _layout; // The _child will be built during layout. } @override void update(LayoutBuilder newWidget) { assert(widget != newWidget); super.update(newWidget); assert(widget == newWidget); renderObject.callback = _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 } @override void unmount() { renderObject.callback = null; super.unmount(); } void _layout(BoxConstraints constraints) { if (widget.builder == null) return; owner.lockState(() { Widget built; try { built = widget.builder(this, constraints); debugWidgetBuilderValue(widget, built); } catch (e, stack) { _debugReportException('building $widget', e, stack); built = new ErrorWidget(e); } try { _child = updateChild(_child, built, null); assert(_child != null); } catch (e, stack) { _debugReportException('building $widget', e, stack); built = new ErrorWidget(e); _child = updateChild(null, built, slot); } }, building: true); } @override void insertChildRenderObject(RenderObject child, dynamic slot) { final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject; assert(slot == null); renderObject.child = child; assert(renderObject == this.renderObject); } @override void moveChildRenderObject(RenderObject child, dynamic slot) { assert(false); } @override void removeChildRenderObject(RenderObject child) { final _RenderLayoutBuilder renderObject = this.renderObject; assert(renderObject.child == child); renderObject.child = null; assert(renderObject == this.renderObject); } } void _debugReportException(String context, dynamic exception, StackTrace stack) { FlutterError.reportError(new FlutterErrorDetails( exception: exception, stack: stack, library: 'widgets library', context: context )); }