1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/foundation.dart';
6import 'package:flutter/rendering.dart';
7
8import 'debug.dart';
9import 'framework.dart';
10
11/// The signature of the [LayoutBuilder] builder function.
12typedef LayoutWidgetBuilder = Widget Function(BuildContext context, BoxConstraints constraints);
13
14/// An abstract superclass for widgets that defer their building until layout.
15///
16/// Similar to the [Builder] widget except that the framework calls the [builder]
17/// function at layout time and provides the constraints that this widget should
18/// adhere to. This is useful when the parent constrains the child's size and layout,
19/// and doesn't depend on the child's intrinsic size.
20///
21/// {@template flutter.widgets.ConstrainedLayoutBuilder}
22/// The [builder] function is called in the following situations:
23///
24/// * The first time the widget is laid out.
25/// * When the parent widget passes different layout constraints.
26/// * When the parent widget updates this widget.
27/// * When the dependencies that the [builder] function subscribes to change.
28///
29/// The [builder] function is _not_ called during layout if the parent passes
30/// the same constraints repeatedly.
31/// {@endtemplate}
32///
33/// Subclasses must return a [RenderObject] that mixes in
34/// [RenderConstrainedLayoutBuilder].
35abstract class ConstrainedLayoutBuilder<ConstraintType extends Constraints> extends RenderObjectWidget {
36 /// Creates a widget that defers its building until layout.
37 const ConstrainedLayoutBuilder({
38 super.key,
39 required this.builder,
40 });
41
42 @override
43 RenderObjectElement createElement() => _LayoutBuilderElement<ConstraintType>(this);
44
45 /// Called at layout time to construct the widget tree.
46 ///
47 /// The builder must not return null.
48 final Widget Function(BuildContext context, ConstraintType constraints) builder;
49
50 /// Whether [builder] needs to be called again even if the layout constraints
51 /// are the same.
52 ///
53 /// When this widget's configuration is updated, the [builder] callback most
54 /// likely needs to be called to build this widget's child. However,
55 /// subclasses may provide ways in which the widget can be updated without
56 /// needing to rebuild the child. Such subclasses can use this method to tell
57 /// the framework when the child widget should be rebuilt.
58 ///
59 /// When this method is called by the framework, the newly configured widget
60 /// is asked if it requires a rebuild, and it is passed the old widget as a
61 /// parameter.
62 ///
63 /// See also:
64 ///
65 /// * [State.setState] and [State.didUpdateWidget], which talk about widget
66 /// configuration changes and how they're triggered.
67 /// * [Element.update], the method that actually updates the widget's
68 /// configuration.
69 @protected
70 bool updateShouldRebuild(covariant ConstrainedLayoutBuilder<ConstraintType> oldWidget) => true;
71
72 // updateRenderObject is redundant with the logic in the LayoutBuilderElement below.
73}
74
75class _LayoutBuilderElement<ConstraintType extends Constraints> extends RenderObjectElement {
76 _LayoutBuilderElement(ConstrainedLayoutBuilder<ConstraintType> super.widget);
77
78 @override
79 RenderConstrainedLayoutBuilder<ConstraintType, RenderObject> get renderObject => super.renderObject as RenderConstrainedLayoutBuilder<ConstraintType, RenderObject>;
80
81 Element? _child;
82
83 @override
84 void visitChildren(ElementVisitor visitor) {
85 if (_child != null) {
86 visitor(_child!);
87 }
88 }
89
90 @override
91 void forgetChild(Element child) {
92 assert(child == _child);
93 _child = null;
94 super.forgetChild(child);
95 }
96
97 @override
98 void mount(Element? parent, Object? newSlot) {
99 super.mount(parent, newSlot); // Creates the renderObject.
100 renderObject.updateCallback(_layout);
101 }
102
103 @override
104 void update(ConstrainedLayoutBuilder<ConstraintType> newWidget) {
105 assert(widget != newWidget);
106 final ConstrainedLayoutBuilder<ConstraintType> oldWidget = widget as ConstrainedLayoutBuilder<ConstraintType>;
107 super.update(newWidget);
108 assert(widget == newWidget);
109
110 renderObject.updateCallback(_layout);
111 if (newWidget.updateShouldRebuild(oldWidget)) {
112 renderObject.markNeedsBuild();
113 }
114 }
115
116 @override
117 void performRebuild() {
118 // This gets called if markNeedsBuild() is called on us.
119 // That might happen if, e.g., our builder uses Inherited widgets.
120
121 // Force the callback to be called, even if the layout constraints are the
122 // same. This is because that callback may depend on the updated widget
123 // configuration, or an inherited widget.
124 renderObject.markNeedsBuild();
125 super.performRebuild(); // Calls widget.updateRenderObject (a no-op in this case).
126 }
127
128 @override
129 void unmount() {
130 renderObject.updateCallback(null);
131 super.unmount();
132 }
133
134 void _layout(ConstraintType constraints) {
135 @pragma('vm:notify-debugger-on-exception')
136 void layoutCallback() {
137 Widget built;
138 try {
139 built = (widget as ConstrainedLayoutBuilder<ConstraintType>).builder(this, constraints);
140 debugWidgetBuilderValue(widget, built);
141 } catch (e, stack) {
142 built = ErrorWidget.builder(
143 _reportException(
144 ErrorDescription('building $widget'),
145 e,
146 stack,
147 informationCollector: () => <DiagnosticsNode>[
148 if (kDebugMode)
149 DiagnosticsDebugCreator(DebugCreator(this)),
150 ],
151 ),
152 );
153 }
154 try {
155 _child = updateChild(_child, built, null);
156 assert(_child != null);
157 } catch (e, stack) {
158 built = ErrorWidget.builder(
159 _reportException(
160 ErrorDescription('building $widget'),
161 e,
162 stack,
163 informationCollector: () => <DiagnosticsNode>[
164 if (kDebugMode)
165 DiagnosticsDebugCreator(DebugCreator(this)),
166 ],
167 ),
168 );
169 _child = updateChild(null, built, slot);
170 }
171 }
172
173 owner!.buildScope(this, layoutCallback);
174 }
175
176 @override
177 void insertRenderObjectChild(RenderObject child, Object? slot) {
178 final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
179 assert(slot == null);
180 assert(renderObject.debugValidateChild(child));
181 renderObject.child = child;
182 assert(renderObject == this.renderObject);
183 }
184
185 @override
186 void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) {
187 assert(false);
188 }
189
190 @override
191 void removeRenderObjectChild(RenderObject child, Object? slot) {
192 final RenderConstrainedLayoutBuilder<ConstraintType, RenderObject> renderObject = this.renderObject;
193 assert(renderObject.child == child);
194 renderObject.child = null;
195 assert(renderObject == this.renderObject);
196 }
197}
198
199/// Generic mixin for [RenderObject]s created by [ConstrainedLayoutBuilder].
200///
201/// Provides a callback that should be called at layout time, typically in
202/// [RenderObject.performLayout].
203mixin RenderConstrainedLayoutBuilder<ConstraintType extends Constraints, ChildType extends RenderObject> on RenderObjectWithChildMixin<ChildType> {
204 LayoutCallback<ConstraintType>? _callback;
205 /// Change the layout callback.
206 void updateCallback(LayoutCallback<ConstraintType>? value) {
207 if (value == _callback) {
208 return;
209 }
210 _callback = value;
211 markNeedsLayout();
212 }
213
214 bool _needsBuild = true;
215
216 /// Marks this layout builder as needing to rebuild.
217 ///
218 /// The layout build rebuilds automatically when layout constraints change.
219 /// However, we must also rebuild when the widget updates, e.g. after
220 /// [State.setState], or [State.didChangeDependencies], even when the layout
221 /// constraints remain unchanged.
222 ///
223 /// See also:
224 ///
225 /// * [ConstrainedLayoutBuilder.builder], which is called during the rebuild.
226 void markNeedsBuild() {
227 // Do not call the callback directly. It must be called during the layout
228 // phase, when parent constraints are available. Calling `markNeedsLayout`
229 // will cause it to be called at the right time.
230 _needsBuild = true;
231 markNeedsLayout();
232 }
233
234 // The constraints that were passed to this class last time it was laid out.
235 // These constraints are compared to the new constraints to determine whether
236 // [ConstrainedLayoutBuilder.builder] needs to be called.
237 Constraints? _previousConstraints;
238
239 /// Invoke the callback supplied via [updateCallback].
240 ///
241 /// Typically this results in [ConstrainedLayoutBuilder.builder] being called
242 /// during layout.
243 void rebuildIfNecessary() {
244 assert(_callback != null);
245 if (_needsBuild || constraints != _previousConstraints) {
246 _previousConstraints = constraints;
247 _needsBuild = false;
248 invokeLayoutCallback(_callback!);
249 }
250 }
251}
252
253/// Builds a widget tree that can depend on the parent widget's size.
254///
255/// Similar to the [Builder] widget except that the framework calls the [builder]
256/// function at layout time and provides the parent widget's constraints. This
257/// is useful when the parent constrains the child's size and doesn't depend on
258/// the child's intrinsic size. The [LayoutBuilder]'s final size will match its
259/// child's size.
260///
261/// {@macro flutter.widgets.ConstrainedLayoutBuilder}
262///
263/// {@youtube 560 315 https://www.youtube.com/watch?v=IYDVcriKjsw}
264///
265/// If the child should be smaller than the parent, consider wrapping the child
266/// in an [Align] widget. If the child might want to be bigger, consider
267/// wrapping it in a [SingleChildScrollView] or [OverflowBox].
268///
269/// {@tool dartpad}
270/// This example uses a [LayoutBuilder] to build a different widget depending on the available width. Resize the
271/// DartPad window to see [LayoutBuilder] in action!
272///
273/// ** See code in examples/api/lib/widgets/layout_builder/layout_builder.0.dart **
274/// {@end-tool}
275///
276/// See also:
277///
278/// * [SliverLayoutBuilder], the sliver counterpart of this widget.
279/// * [Builder], which calls a `builder` function at build time.
280/// * [StatefulBuilder], which passes its `builder` function a `setState` callback.
281/// * [CustomSingleChildLayout], which positions its child during layout.
282/// * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
283class LayoutBuilder extends ConstrainedLayoutBuilder<BoxConstraints> {
284 /// Creates a widget that defers its building until layout.
285 const LayoutBuilder({
286 super.key,
287 required super.builder,
288 });
289
290 @override
291 RenderObject createRenderObject(BuildContext context) => _RenderLayoutBuilder();
292}
293
294class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<RenderBox>, RenderConstrainedLayoutBuilder<BoxConstraints, RenderBox> {
295 @override
296 double computeMinIntrinsicWidth(double height) {
297 assert(_debugThrowIfNotCheckingIntrinsics());
298 return 0.0;
299 }
300
301 @override
302 double computeMaxIntrinsicWidth(double height) {
303 assert(_debugThrowIfNotCheckingIntrinsics());
304 return 0.0;
305 }
306
307 @override
308 double computeMinIntrinsicHeight(double width) {
309 assert(_debugThrowIfNotCheckingIntrinsics());
310 return 0.0;
311 }
312
313 @override
314 double computeMaxIntrinsicHeight(double width) {
315 assert(_debugThrowIfNotCheckingIntrinsics());
316 return 0.0;
317 }
318
319 @override
320 Size computeDryLayout(BoxConstraints constraints) {
321 assert(debugCannotComputeDryLayout(reason:
322 'Calculating the dry layout would require running the layout callback '
323 'speculatively, which might mutate the live render object tree.',
324 ));
325 return Size.zero;
326 }
327
328 @override
329 void performLayout() {
330 final BoxConstraints constraints = this.constraints;
331 rebuildIfNecessary();
332 if (child != null) {
333 child!.layout(constraints, parentUsesSize: true);
334 size = constraints.constrain(child!.size);
335 } else {
336 size = constraints.biggest;
337 }
338 }
339
340 @override
341 double? computeDistanceToActualBaseline(TextBaseline baseline) {
342 if (child != null) {
343 return child!.getDistanceToActualBaseline(baseline);
344 }
345 return super.computeDistanceToActualBaseline(baseline);
346 }
347
348 @override
349 bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
350 return child?.hitTest(result, position: position) ?? false;
351 }
352
353 @override
354 void paint(PaintingContext context, Offset offset) {
355 if (child != null) {
356 context.paintChild(child!, offset);
357 }
358 }
359
360 bool _debugThrowIfNotCheckingIntrinsics() {
361 assert(() {
362 if (!RenderObject.debugCheckingIntrinsics) {
363 throw FlutterError(
364 'LayoutBuilder does not support returning intrinsic dimensions.\n'
365 'Calculating the intrinsic dimensions would require running the layout '
366 'callback speculatively, which might mutate the live render object tree.',
367 );
368 }
369 return true;
370 }());
371
372 return true;
373 }
374}
375
376FlutterErrorDetails _reportException(
377 DiagnosticsNode context,
378 Object exception,
379 StackTrace stack, {
380 InformationCollector? informationCollector,
381}) {
382 final FlutterErrorDetails details = FlutterErrorDetails(
383 exception: exception,
384 stack: stack,
385 library: 'widgets library',
386 context: context,
387 informationCollector: informationCollector,
388 );
389 FlutterError.reportError(details);
390 return details;
391}
392