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:
242 () => <DiagnosticsNode>[
243 if (kDebugMode) DiagnosticsDebugCreator(DebugCreator(this)),
244 ],
245 ),
246 );
247 }
248 try {
249 _child = updateChild(_child, built, null);
250 assert(_child != null);
251 } catch (e, stack) {
252 built = ErrorWidget.builder(
253 _reportException(
254 ErrorDescription('building $widget'),
255 e,
256 stack,
257 informationCollector:
258 () => <DiagnosticsNode>[
259 if (kDebugMode) DiagnosticsDebugCreator(DebugCreator(this)),
260 ],
261 ),
262 );
263 _child = updateChild(null, built, slot);
264 } finally {
265 _needsBuild = false;
266 _previousLayoutInfo = layoutInfo;
267 }
268 }
269
270 final VoidCallback? callback =
271 _needsBuild || (layoutInfo != _previousLayoutInfo) ? updateChildCallback : null;
272 owner!.buildScope(this, callback);
273 }
274
275 @override
276 void insertRenderObjectChild(RenderObject child, Object? slot) {
277 final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
278 assert(slot == null);
279 assert(renderObject.debugValidateChild(child));
280 renderObject.child = child;
281 assert(renderObject == this.renderObject);
282 }
283
284 @override
285 void moveRenderObjectChild(RenderObject child, Object? oldSlot, Object? newSlot) {
286 assert(false);
287 }
288
289 @override
290 void removeRenderObjectChild(RenderObject child, Object? slot) {
291 final RenderAbstractLayoutBuilderMixin<LayoutInfoType, RenderObject> renderObject =
292 this.renderObject;
293 assert(renderObject.child == child);
294 renderObject.child = null;
295 assert(renderObject == this.renderObject);
296 }
297}
298
299/// Generic mixin for [RenderObject]s created by an [AbstractLayoutBuilder] with
300/// the the same `LayoutInfoType`.
301///
302/// Provides a [layoutCallback] implementation which, if needed, invokes
303/// [AbstractLayoutBuilder]'s builder callback.
304///
305/// Implementers can override the [layoutInfo] implementation with a value
306/// that is safe to access in [layoutCallback], which is called in
307/// [performLayout]. The default [layoutInfo] returns the incoming
308/// [Constraints].
309///
310/// This mixin replaces [RenderConstrainedLayoutBuilder].
311mixin RenderAbstractLayoutBuilderMixin<LayoutInfoType, ChildType extends RenderObject>
312 on RenderObjectWithChildMixin<ChildType>, RenderObjectWithLayoutCallbackMixin {
313 LayoutCallback<Constraints>? _callback;
314
315 /// Change the layout callback.
316 void _updateCallback(LayoutCallback<Constraints> value) {
317 if (value == _callback) {
318 return;
319 }
320 _callback = value;
321 scheduleLayoutCallback();
322 }
323
324 /// Invokes the builder callback supplied via [AbstractLayoutBuilder] and
325 /// rebuilds the [AbstractLayoutBuilder]'s widget tree, if needed.
326 ///
327 /// No further work will be done if [layoutInfo] has not changed since the last
328 /// time this method was called, and [AbstractLayoutBuilder.updateShouldRebuild]
329 /// returned `false` when the widget was rebuilt.
330 ///
331 /// This method should typically be called as soon as possible in the class's
332 /// [performLayout] implementation, before any layout work is done.
333 @visibleForOverriding
334 @override
335 void layoutCallback() => _callback!(constraints);
336
337 /// The information to invoke the [AbstractLayoutBuilder.builder] callback with.
338 ///
339 /// This is typically the information that are only made available in
340 /// [performLayout], which is inaccessible for regular [Builder] widget,
341 /// such as the incoming [Constraints], which are the default value.
342 @protected
343 LayoutInfoType get layoutInfo => constraints as LayoutInfoType;
344}
345
346/// Generic mixin for [RenderObject]s created by an [AbstractLayoutBuilder] with
347/// the the same `LayoutInfoType`.
348///
349/// Use [RenderAbstractLayoutBuilderMixin] instead, which replaces this mixin.
350typedef RenderConstrainedLayoutBuilder<LayoutInfoType, ChildType extends RenderObject> =
351 RenderAbstractLayoutBuilderMixin<LayoutInfoType, ChildType>;
352
353/// Builds a widget tree that can depend on the parent widget's size.
354///
355/// Similar to the [Builder] widget except that the framework calls the [builder]
356/// function at layout time and provides the parent widget's constraints. This
357/// is useful when the parent constrains the child's size and doesn't depend on
358/// the child's intrinsic size. The [LayoutBuilder]'s final size will match its
359/// child's size.
360///
361/// {@macro flutter.widgets.ConstrainedLayoutBuilder}
362///
363/// {@youtube 560 315 https://www.youtube.com/watch?v=IYDVcriKjsw}
364///
365/// If the child should be smaller than the parent, consider wrapping the child
366/// in an [Align] widget. If the child might want to be bigger, consider
367/// wrapping it in a [SingleChildScrollView] or [OverflowBox].
368///
369/// {@tool dartpad}
370/// This example uses a [LayoutBuilder] to build a different widget depending on the available width. Resize the
371/// DartPad window to see [LayoutBuilder] in action!
372///
373/// ** See code in examples/api/lib/widgets/layout_builder/layout_builder.0.dart **
374/// {@end-tool}
375///
376/// See also:
377///
378/// * [SliverLayoutBuilder], the sliver counterpart of this widget.
379/// * [Builder], which calls a `builder` function at build time.
380/// * [StatefulBuilder], which passes its `builder` function a `setState` callback.
381/// * [CustomSingleChildLayout], which positions its child during layout.
382/// * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
383class LayoutBuilder extends ConstrainedLayoutBuilder<BoxConstraints> {
384 /// Creates a widget that defers its building until layout.
385 const LayoutBuilder({super.key, required super.builder});
386
387 @override
388 RenderAbstractLayoutBuilderMixin<BoxConstraints, RenderBox> createRenderObject(
389 BuildContext context,
390 ) => _RenderLayoutBuilder();
391}
392
393class _RenderLayoutBuilder extends RenderBox
394 with
395 RenderObjectWithChildMixin<RenderBox>,
396 RenderObjectWithLayoutCallbackMixin,
397 RenderAbstractLayoutBuilderMixin<BoxConstraints, RenderBox> {
398 @override
399 double computeMinIntrinsicWidth(double height) {
400 assert(_debugThrowIfNotCheckingIntrinsics());
401 return 0.0;
402 }
403
404 @override
405 double computeMaxIntrinsicWidth(double height) {
406 assert(_debugThrowIfNotCheckingIntrinsics());
407 return 0.0;
408 }
409
410 @override
411 double computeMinIntrinsicHeight(double width) {
412 assert(_debugThrowIfNotCheckingIntrinsics());
413 return 0.0;
414 }
415
416 @override
417 double computeMaxIntrinsicHeight(double width) {
418 assert(_debugThrowIfNotCheckingIntrinsics());
419 return 0.0;
420 }
421
422 @override
423 Size computeDryLayout(BoxConstraints constraints) {
424 assert(
425 debugCannotComputeDryLayout(
426 reason:
427 'Calculating the dry layout would require running the layout callback '
428 'speculatively, which might mutate the live render object tree.',
429 ),
430 );
431 return Size.zero;
432 }
433
434 @override
435 double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
436 assert(
437 debugCannotComputeDryLayout(
438 reason:
439 'Calculating the dry baseline would require running the layout callback '
440 'speculatively, which might mutate the live render object tree.',
441 ),
442 );
443 return null;
444 }
445
446 @override
447 void performLayout() {
448 final BoxConstraints constraints = this.constraints;
449 runLayoutCallback();
450 if (child != null) {
451 child!.layout(constraints, parentUsesSize: true);
452 size = constraints.constrain(child!.size);
453 } else {
454 size = constraints.biggest;
455 }
456 }
457
458 @override
459 double? computeDistanceToActualBaseline(TextBaseline baseline) {
460 return child?.getDistanceToActualBaseline(baseline) ??
461 super.computeDistanceToActualBaseline(baseline);
462 }
463
464 @override
465 bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
466 return child?.hitTest(result, position: position) ?? false;
467 }
468
469 @override
470 void paint(PaintingContext context, Offset offset) {
471 if (child != null) {
472 context.paintChild(child!, offset);
473 }
474 }
475
476 bool _debugThrowIfNotCheckingIntrinsics() {
477 assert(() {
478 if (!RenderObject.debugCheckingIntrinsics) {
479 throw FlutterError(
480 'LayoutBuilder does not support returning intrinsic dimensions.\n'
481 'Calculating the intrinsic dimensions would require running the layout '
482 'callback speculatively, which might mutate the live render object tree.',
483 );
484 }
485 return true;
486 }());
487
488 return true;
489 }
490}
491
492FlutterErrorDetails _reportException(
493 DiagnosticsNode context,
494 Object exception,
495 StackTrace stack, {
496 InformationCollector? informationCollector,
497}) {
498 final FlutterErrorDetails details = FlutterErrorDetails(
499 exception: exception,
500 stack: stack,
501 library: 'widgets library',
502 context: context,
503 informationCollector: informationCollector,
504 );
505 FlutterError.reportError(details);
506 return details;
507}
508

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com