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
5/// @docImport 'basic.dart';
6/// @docImport 'single_child_scroll_view.dart';
7/// @docImport 'sliver_layout_builder.dart';
8library;
9
10import 'package:flutter/foundation.dart';
11import 'package:flutter/rendering.dart';
12import 'package:flutter/scheduler.dart';
13
14import 'debug.dart';
15import 'framework.dart';
16
17/// The signature of the [LayoutBuilder] builder function.
18typedef LayoutWidgetBuilder = Widget Function(BuildContext context, BoxConstraints constraints);
19
20/// An abstract superclass for widgets that defer their building until layout.
21///
22/// Similar to the [Builder] widget except that the implementation calls the [builder]
23/// function at layout time and provides the [LayoutInfoType] that is required to
24/// configure the child widget subtree.
25///
26/// This is useful when the child widget tree relies on information that are only
27/// available during layout, and doesn't depend on the child's intrinsic size.
28///
29/// The [LayoutInfoType] should typically be immutable. The equality of the
30/// [LayoutInfoType] type is used by the implementation to avoid unnecessary
31/// rebuilds: if the new [LayoutInfoType] computed during layout is the same as
32/// (defined by `LayoutInfoType.==`) the previous [LayoutInfoType], the
33/// implementation will try to avoid calling the [builder] again unless
34/// [updateShouldRebuild] returns true. The corresponding [RenderObject] produced
35/// by this widget retains the most up-to-date [LayoutInfoType] for this purpose,
36/// which may keep a [LayoutInfoType] object in memory until the widget is removed
37/// from the tree.
38///
39/// Subclasses must return a [RenderObject] that mixes in [RenderAbstractLayoutBuilderMixin].
40abstract class AbstractLayoutBuilder<LayoutInfoType> extends RenderObjectWidget {
41 /// Creates a widget that defers its building until layout.
42 const AbstractLayoutBuilder({super.key});
43
44 /// Called at layout time to construct the widget tree.
45 ///
46 /// The builder must not return null.
47 Widget Function(BuildContext context, LayoutInfoType layoutInfo) get builder;
48
49 @override
50 RenderObjectElement createElement() => _LayoutBuilderElement<LayoutInfoType>(this);
51
52 /// Whether [builder] needs to be called again even if the layout constraints
53 /// are the same.
54 ///
55 /// When this widget's configuration is updated, the [builder] callback most
56 /// likely needs to be called to build this widget's child. However,
57 /// subclasses may provide ways in which the widget can be updated without
58 /// needing to rebuild the child. Such subclasses can use this method to tell
59 /// the framework when the child widget should be rebuilt.
60 ///
61 /// When this method is called by the framework, the newly configured widget
62 /// is asked if it requires a rebuild, and it is passed the old widget as a
63 /// parameter.
64 ///
65 /// See also:
66 ///
67 /// * [State.setState] and [State.didUpdateWidget], which talk about widget
68 /// configuration changes and how they're triggered.
69 /// * [Element.update], the method that actually updates the widget's
70 /// configuration.
71 @protected
72 bool updateShouldRebuild(covariant AbstractLayoutBuilder<LayoutInfoType> oldWidget) => true;
73
74 @override
75 RenderAbstractLayoutBuilderMixin<LayoutInfoType, RenderObject> createRenderObject(
76 BuildContext context,
77 );
78
79 // updateRenderObject is redundant with the logic in the LayoutBuilderElement below.
80}
81
82/// A specialized [AbstractLayoutBuilder] whose widget subtree depends on the
83/// incoming [ConstraintType] that will be imposed on the widget.
84///
85/// {@template flutter.widgets.ConstrainedLayoutBuilder}
86/// The [builder] function is called in the following situations:
87///
88/// * The first time the widget is laid out.
89/// * When the parent widget passes different layout constraints.
90/// * When the parent widget updates this widget and [updateShouldRebuild] returns `true`.
91/// * When the dependencies that the [builder] function subscribes to change.
92///
93/// The [builder] function is _not_ called during layout if the parent passes
94/// the same constraints repeatedly.
95///
96/// In the event that an ancestor skips the layout of this subtree so the
97/// constraints become outdated, the `builder` rebuilds with the last known
98/// constraints.
99/// {@endtemplate}
100abstract class ConstrainedLayoutBuilder<ConstraintType extends Constraints>
101 extends AbstractLayoutBuilder<ConstraintType> {
102 /// Creates a widget that defers its building until layout.
103 const ConstrainedLayoutBuilder({super.key, required this.builder});
104
105 @override
106 final Widget Function(BuildContext context, ConstraintType constraints) builder;
107}
108
109class _LayoutBuilderElement<LayoutInfoType> extends RenderObjectElement {
110 _LayoutBuilderElement(AbstractLayoutBuilder<LayoutInfoType> super.widget);
111
112 @override
113 RenderAbstractLayoutBuilderMixin<LayoutInfoType, RenderObject> get renderObject =>
114 super.renderObject as RenderAbstractLayoutBuilderMixin<LayoutInfoType, RenderObject>;
115
116 Element? _child;
117
118 @override
119 BuildScope get buildScope => _buildScope;
120
121 late final BuildScope _buildScope = BuildScope(scheduleRebuild: _scheduleRebuild);
122
123 // To schedule a rebuild, markNeedsLayout needs to be called on this Element's
124 // render object (as the rebuilding is done in its performLayout call). However,
125 // the render tree should typically be kept clean during the postFrameCallbacks
126 // and the idle phase, so the layout data can be safely read.
127 bool _deferredCallbackScheduled = false;
128 void _scheduleRebuild() {
129 if (_deferredCallbackScheduled) {
130 return;
131 }
132
133 final bool deferMarkNeedsLayout = switch (SchedulerBinding.instance.schedulerPhase) {
134 SchedulerPhase.idle || SchedulerPhase.postFrameCallbacks => true,
135 SchedulerPhase.transientCallbacks ||
136 SchedulerPhase.midFrameMicrotasks ||
137 SchedulerPhase.persistentCallbacks => false,
138 };
139 if (!deferMarkNeedsLayout) {
140 renderObject.scheduleLayoutCallback();
141 return;
142 }
143 _deferredCallbackScheduled = true;
144 SchedulerBinding.instance.scheduleFrameCallback(_frameCallback);
145 }
146
147 void _frameCallback(Duration timestamp) {
148 _deferredCallbackScheduled = false;
149 // This method is only called when the render tree is stable, if the Element
150 // is deactivated it will never be reincorporated back to the tree.
151 if (mounted) {
152 renderObject.scheduleLayoutCallback();
153 }
154 }
155
156 @override
157 void visitChildren(ElementVisitor visitor) {
158 if (_child != null) {
159 visitor(_child!);
160 }
161 }
162
163 @override
164 void forgetChild(Element child) {
165 assert(child == _child);
166 _child = null;
167 super.forgetChild(child);
168 }
169
170 @override
171 void mount(Element? parent, Object? newSlot) {
172 super.mount(parent, newSlot); // Creates the renderObject.
173 renderObject._updateCallback(_rebuildWithConstraints);
174 }
175
176 @override
177 void update(AbstractLayoutBuilder<LayoutInfoType> newWidget) {
178 assert(widget != newWidget);
179 final AbstractLayoutBuilder<LayoutInfoType> oldWidget =
180 widget as AbstractLayoutBuilder<LayoutInfoType>;
181 super.update(newWidget);
182 assert(widget == newWidget);
183
184 renderObject._updateCallback(_rebuildWithConstraints);
185 if (newWidget.updateShouldRebuild(oldWidget)) {
186 _needsBuild = true;
187 renderObject.scheduleLayoutCallback();
188 }
189 }
190
191 @override
192 void markNeedsBuild() {
193 // Calling super.markNeedsBuild is not needed. This Element does not need
194 // to performRebuild since this call already does what performRebuild does,
195 // So the element is clean as soon as this method returns and does not have
196 // to be added to the dirty list or marked as dirty.
197 renderObject.scheduleLayoutCallback();
198 _needsBuild = true;
199 }
200
201 @override
202 void performRebuild() {
203 // This gets called if markNeedsBuild() is called on us.
204 // That might happen if, e.g., our builder uses Inherited widgets.
205
206 // Force the callback to be called, even if the layout constraints are the
207 // same. This is because that callback may depend on the updated widget
208 // configuration, or an inherited widget.
209 renderObject.scheduleLayoutCallback();
210 _needsBuild = true;
211 super.performRebuild(); // Calls widget.updateRenderObject (a no-op in this case).
212 }
213
214 @override
215 void unmount() {
216 renderObject._callback = null;
217 super.unmount();
218 }
219
220 // The LayoutInfoType that was used to invoke the layout callback with last time,
221 // during layout. The `_previousLayoutInfo` value is compared to the new one
222 // to determine whether [LayoutBuilderBase.builder] needs to be called.
223 LayoutInfoType? _previousLayoutInfo;
224 bool _needsBuild = true;
225
226 void _rebuildWithConstraints(Constraints _) {
227 final LayoutInfoType layoutInfo = renderObject.layoutInfo;
228 @pragma('vm:notify-debugger-on-exception')
229 void updateChildCallback() {
230 Widget built;
231 try {
232 assert(layoutInfo == renderObject.layoutInfo);
233 built = (widget as AbstractLayoutBuilder<LayoutInfoType>).builder(this, layoutInfo);
234 debugWidgetBuilderValue(widget, built);
235 } catch (e, stack) {
236 built = ErrorWidget.builder(
237 _reportException(
238 ErrorDescription('building $widget'),
239 e,
240 stack,
241 informationCollector: () => <DiagnosticsNode>[
242 if (kDebugMode) DiagnosticsDebugCreator(DebugCreator(this)),
243 ],
244 ),
245 );
246 }
247 try {
248 _child = updateChild(_child, built, null);
249 assert(_child != null);
250 } catch (e, stack) {
251 built = ErrorWidget.builder(
252 _reportException(
253 ErrorDescription('building $widget'),
254 e,
255 stack,
256 informationCollector: () => <DiagnosticsNode>[
257 if (kDebugMode) DiagnosticsDebugCreator(DebugCreator(this)),
258 ],
259 ),
260 );
261 _child = updateChild(null, built, slot);
262 } finally {
263 _needsBuild = false;
264 _previousLayoutInfo = layoutInfo;
265 }
266 }
267
268 final VoidCallback? callback = _needsBuild || (layoutInfo != _previousLayoutInfo)
269 ? updateChildCallback
270 : null;
271 owner!.buildScope(this, callback);
272 }
273
274 @override
275 void insertRenderObjectChild(RenderObject child, Object? slot) {
276 final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
277 assert(slot == null);
278 assert(renderObject.debugValidateChild(child));
279 renderObject.child = child;
280 assert(renderObject == this.renderObject);
281 }
282
283 @override
284 void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) {
285 assert(false);
286 }
287
288 @override
289 void removeRenderObjectChild(RenderObject child, Object? slot) {
290 final RenderAbstractLayoutBuilderMixin<LayoutInfoType, RenderObject> renderObject =
291 this.renderObject;
292 assert(renderObject.child == child);
293 renderObject.child = null;
294 assert(renderObject == this.renderObject);
295 }
296}
297
298/// Generic mixin for [RenderObject]s created by an [AbstractLayoutBuilder] with
299/// the the same `LayoutInfoType`.
300///
301/// Provides a [layoutCallback] implementation which, if needed, invokes
302/// [AbstractLayoutBuilder]'s builder callback.
303///
304/// Implementers can override the [layoutInfo] implementation with a value
305/// that is safe to access in [layoutCallback], which is called in
306/// [performLayout]. The default [layoutInfo] returns the incoming
307/// [Constraints].
308///
309/// This mixin replaces [RenderConstrainedLayoutBuilder].
310mixin RenderAbstractLayoutBuilderMixin<LayoutInfoType, ChildType extends RenderObject>
311 on RenderObjectWithChildMixin<ChildType>, RenderObjectWithLayoutCallbackMixin {
312 LayoutCallback<Constraints>? _callback;
313
314 /// Change the layout callback.
315 void _updateCallback(LayoutCallback<Constraints> value) {
316 if (value == _callback) {
317 return;
318 }
319 _callback = value;
320 scheduleLayoutCallback();
321 }
322
323 /// Invokes the builder callback supplied via [AbstractLayoutBuilder] and
324 /// rebuilds the [AbstractLayoutBuilder]'s widget tree, if needed.
325 ///
326 /// No further work will be done if [layoutInfo] has not changed since the last
327 /// time this method was called, and [AbstractLayoutBuilder.updateShouldRebuild]
328 /// returned `false` when the widget was rebuilt.
329 ///
330 /// This method should typically be called as soon as possible in the class's
331 /// [performLayout] implementation, before any layout work is done.
332 @visibleForOverriding
333 @override
334 void layoutCallback() => _callback!(constraints);
335
336 /// The information to invoke the [AbstractLayoutBuilder.builder] callback with.
337 ///
338 /// This is typically the information that are only made available in
339 /// [performLayout], which is inaccessible for regular [Builder] widget,
340 /// such as the incoming [Constraints], which are the default value.
341 @protected
342 LayoutInfoType get layoutInfo => constraints as LayoutInfoType;
343}
344
345/// Generic mixin for [RenderObject]s created by an [AbstractLayoutBuilder] with
346/// the the same `LayoutInfoType`.
347///
348/// Use [RenderAbstractLayoutBuilderMixin] instead, which replaces this mixin.
349typedef RenderConstrainedLayoutBuilder<LayoutInfoType, ChildType extends RenderObject> =
350 RenderAbstractLayoutBuilderMixin<LayoutInfoType, ChildType>;
351
352/// Builds a widget tree that can depend on the parent widget's size.
353///
354/// Similar to the [Builder] widget except that the framework calls the [builder]
355/// function at layout time and provides the parent widget's constraints. This
356/// is useful when the parent constrains the child's size and doesn't depend on
357/// the child's intrinsic size. The [LayoutBuilder]'s final size will match its
358/// child's size.
359///
360/// {@macro flutter.widgets.ConstrainedLayoutBuilder}
361///
362/// {@youtube 560 315 https://www.youtube.com/watch?v=IYDVcriKjsw}
363///
364/// If the child should be smaller than the parent, consider wrapping the child
365/// in an [Align] widget. If the child might want to be bigger, consider
366/// wrapping it in a [SingleChildScrollView] or [OverflowBox].
367///
368/// {@tool dartpad}
369/// This example uses a [LayoutBuilder] to build a different widget depending on the available width. Resize the
370/// DartPad window to see [LayoutBuilder] in action!
371///
372/// ** See code in examples/api/lib/widgets/layout_builder/layout_builder.0.dart **
373/// {@end-tool}
374///
375/// See also:
376///
377/// * [SliverLayoutBuilder], the sliver counterpart of this widget.
378/// * [Builder], which calls a `builder` function at build time.
379/// * [StatefulBuilder], which passes its `builder` function a `setState` callback.
380/// * [CustomSingleChildLayout], which positions its child during layout.
381/// * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
382class LayoutBuilder extends ConstrainedLayoutBuilder<BoxConstraints> {
383 /// Creates a widget that defers its building until layout.
384 const LayoutBuilder({super.key, required super.builder});
385
386 @override
387 RenderAbstractLayoutBuilderMixin<BoxConstraints, RenderBox> createRenderObject(
388 BuildContext context,
389 ) => _RenderLayoutBuilder();
390}
391
392class _RenderLayoutBuilder extends RenderBox
393 with
394 RenderObjectWithChildMixin<RenderBox>,
395 RenderObjectWithLayoutCallbackMixin,
396 RenderAbstractLayoutBuilderMixin<BoxConstraints, RenderBox> {
397 @override
398 double computeMinIntrinsicWidth(double height) {
399 assert(_debugThrowIfNotCheckingIntrinsics());
400 return 0.0;
401 }
402
403 @override
404 double computeMaxIntrinsicWidth(double height) {
405 assert(_debugThrowIfNotCheckingIntrinsics());
406 return 0.0;
407 }
408
409 @override
410 double computeMinIntrinsicHeight(double width) {
411 assert(_debugThrowIfNotCheckingIntrinsics());
412 return 0.0;
413 }
414
415 @override
416 double computeMaxIntrinsicHeight(double width) {
417 assert(_debugThrowIfNotCheckingIntrinsics());
418 return 0.0;
419 }
420
421 @override
422 Size computeDryLayout(BoxConstraints constraints) {
423 assert(
424 debugCannotComputeDryLayout(
425 reason:
426 'Calculating the dry layout would require running the layout callback '
427 'speculatively, which might mutate the live render object tree.',
428 ),
429 );
430 return Size.zero;
431 }
432
433 @override
434 double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
435 assert(
436 debugCannotComputeDryLayout(
437 reason:
438 'Calculating the dry baseline would require running the layout callback '
439 'speculatively, which might mutate the live render object tree.',
440 ),
441 );
442 return null;
443 }
444
445 @override
446 void performLayout() {
447 final BoxConstraints constraints = this.constraints;
448 runLayoutCallback();
449 if (child != null) {
450 child!.layout(constraints, parentUsesSize: true);
451 size = constraints.constrain(child!.size);
452 } else {
453 size = constraints.biggest;
454 }
455 }
456
457 @override
458 double? computeDistanceToActualBaseline(TextBaseline baseline) {
459 return child?.getDistanceToActualBaseline(baseline) ??
460 super.computeDistanceToActualBaseline(baseline);
461 }
462
463 @override
464 bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
465 return child?.hitTest(result, position: position) ?? false;
466 }
467
468 @override
469 void paint(PaintingContext context, Offset offset) {
470 if (child != null) {
471 context.paintChild(child!, offset);
472 }
473 }
474
475 bool _debugThrowIfNotCheckingIntrinsics() {
476 assert(() {
477 if (!RenderObject.debugCheckingIntrinsics) {
478 throw FlutterError(
479 'LayoutBuilder does not support returning intrinsic dimensions.\n'
480 'Calculating the intrinsic dimensions would require running the layout '
481 'callback speculatively, which might mutate the live render object tree.',
482 );
483 }
484 return true;
485 }());
486
487 return true;
488 }
489}
490
491FlutterErrorDetails _reportException(
492 DiagnosticsNode context,
493 Object exception,
494 StackTrace stack, {
495 InformationCollector? informationCollector,
496}) {
497 final FlutterErrorDetails details = FlutterErrorDetails(
498 exception: exception,
499 stack: stack,
500 library: 'widgets library',
501 context: context,
502 informationCollector: informationCollector,
503 );
504 FlutterError.reportError(details);
505 return details;
506}
507