| 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'; |
| 8 | library; |
| 9 | |
| 10 | import 'package:flutter/foundation.dart'; |
| 11 | import 'package:flutter/rendering.dart'; |
| 12 | import 'package:flutter/scheduler.dart'; |
| 13 | |
| 14 | import 'debug.dart'; |
| 15 | import 'framework.dart'; |
| 16 | |
| 17 | /// The signature of the [LayoutBuilder] builder function. |
| 18 | typedef 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]. |
| 40 | abstract 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} |
| 100 | abstract 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 | |
| 109 | class _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]. |
| 310 | mixin 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. |
| 349 | typedef 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/). |
| 382 | class 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 | |
| 392 | class _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 | |
| 491 | FlutterErrorDetails _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 | |