| 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 'package:flutter/material.dart'; |
| 6 | /// |
| 7 | /// @docImport 'animated_scroll_view.dart'; |
| 8 | /// @docImport 'container.dart'; |
| 9 | /// @docImport 'implicit_animations.dart'; |
| 10 | /// @docImport 'scroll_view.dart'; |
| 11 | /// @docImport 'sliver_fill.dart'; |
| 12 | /// @docImport 'sliver_persistent_header.dart'; |
| 13 | /// @docImport 'sliver_prototype_extent_list.dart'; |
| 14 | /// @docImport 'text.dart'; |
| 15 | /// @docImport 'two_dimensional_viewport.dart'; |
| 16 | /// @docImport 'viewport.dart'; |
| 17 | /// @docImport 'visibility.dart'; |
| 18 | library; |
| 19 | |
| 20 | import 'dart:collection' show HashMap, SplayTreeMap; |
| 21 | import 'dart:math' as math; |
| 22 | |
| 23 | import 'package:flutter/foundation.dart'; |
| 24 | import 'package:flutter/rendering.dart'; |
| 25 | |
| 26 | import 'automatic_keep_alive.dart'; |
| 27 | import 'basic.dart'; |
| 28 | import 'framework.dart'; |
| 29 | import 'scroll_delegate.dart'; |
| 30 | |
| 31 | /// A base class for slivers that have [KeepAlive] children. |
| 32 | /// |
| 33 | /// See also: |
| 34 | /// |
| 35 | /// * [KeepAlive], which marks whether its child widget should be kept alive. |
| 36 | /// * [SliverChildBuilderDelegate] and [SliverChildListDelegate], slivers |
| 37 | /// which make use of the keep alive functionality through the |
| 38 | /// `addAutomaticKeepAlives` property. |
| 39 | /// * [SliverGrid] and [SliverList], two sliver widgets that are commonly |
| 40 | /// wrapped with [KeepAlive] widgets to preserve their sliver child subtrees. |
| 41 | abstract class SliverWithKeepAliveWidget extends RenderObjectWidget { |
| 42 | /// Initializes fields for subclasses. |
| 43 | const SliverWithKeepAliveWidget({super.key}); |
| 44 | |
| 45 | @override |
| 46 | RenderSliverWithKeepAliveMixin createRenderObject(BuildContext context); |
| 47 | } |
| 48 | |
| 49 | /// A base class for slivers that have multiple box children. |
| 50 | /// |
| 51 | /// Helps subclasses build their children lazily using a [SliverChildDelegate]. |
| 52 | /// |
| 53 | /// The widgets returned by the [delegate] are cached and the delegate is only |
| 54 | /// consulted again if it changes and the new delegate's |
| 55 | /// [SliverChildDelegate.shouldRebuild] method returns true. |
| 56 | abstract class SliverMultiBoxAdaptorWidget extends SliverWithKeepAliveWidget { |
| 57 | /// Initializes fields for subclasses. |
| 58 | const SliverMultiBoxAdaptorWidget({super.key, required this.delegate}); |
| 59 | |
| 60 | /// {@template flutter.widgets.SliverMultiBoxAdaptorWidget.delegate} |
| 61 | /// The delegate that provides the children for this widget. |
| 62 | /// |
| 63 | /// The children are constructed lazily using this delegate to avoid creating |
| 64 | /// more children than are visible through the [Viewport]. |
| 65 | /// |
| 66 | /// ## Using more than one delegate in a [Viewport] |
| 67 | /// |
| 68 | /// If multiple delegates are used in a single scroll view, the first child of |
| 69 | /// each delegate will always be laid out, even if it extends beyond the |
| 70 | /// currently viewable area. This is because at least one child is required in |
| 71 | /// order to estimate the max scroll offset for the whole scroll view, as it |
| 72 | /// uses the currently built children to estimate the remaining children's |
| 73 | /// extent. |
| 74 | /// |
| 75 | /// See also: |
| 76 | /// |
| 77 | /// * [SliverChildBuilderDelegate] and [SliverChildListDelegate], which are |
| 78 | /// commonly used subclasses of [SliverChildDelegate] that use a builder |
| 79 | /// callback and an explicit child list, respectively. |
| 80 | /// {@endtemplate} |
| 81 | final SliverChildDelegate delegate; |
| 82 | |
| 83 | @override |
| 84 | SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this); |
| 85 | |
| 86 | @override |
| 87 | RenderSliverMultiBoxAdaptor createRenderObject(BuildContext context); |
| 88 | |
| 89 | /// Returns an estimate of the max scroll extent for all the children. |
| 90 | /// |
| 91 | /// Subclasses should override this function if they have additional |
| 92 | /// information about their max scroll extent. |
| 93 | /// |
| 94 | /// This is used by [SliverMultiBoxAdaptorElement] to implement part of the |
| 95 | /// [RenderSliverBoxChildManager] API. |
| 96 | /// |
| 97 | /// The default implementation defers to [delegate] via its |
| 98 | /// [SliverChildDelegate.estimateMaxScrollOffset] method. |
| 99 | double? estimateMaxScrollOffset( |
| 100 | SliverConstraints? constraints, |
| 101 | int firstIndex, |
| 102 | int lastIndex, |
| 103 | double leadingScrollOffset, |
| 104 | double trailingScrollOffset, |
| 105 | ) { |
| 106 | assert(lastIndex >= firstIndex); |
| 107 | return delegate.estimateMaxScrollOffset( |
| 108 | firstIndex, |
| 109 | lastIndex, |
| 110 | leadingScrollOffset, |
| 111 | trailingScrollOffset, |
| 112 | ); |
| 113 | } |
| 114 | |
| 115 | @override |
| 116 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 117 | super.debugFillProperties(properties); |
| 118 | properties.add(DiagnosticsProperty<SliverChildDelegate>('delegate' , delegate)); |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | /// A sliver that places multiple box children in a linear array along the main |
| 123 | /// axis. |
| 124 | /// |
| 125 | /// _To learn more about slivers, see [CustomScrollView.slivers]._ |
| 126 | /// |
| 127 | /// Each child is forced to have the [SliverConstraints.crossAxisExtent] in the |
| 128 | /// cross axis but determines its own main axis extent. |
| 129 | /// |
| 130 | /// [SliverList] determines its scroll offset by "dead reckoning" because |
| 131 | /// children outside the visible part of the sliver are not materialized, which |
| 132 | /// means [SliverList] cannot learn their main axis extent. Instead, newly |
| 133 | /// materialized children are placed adjacent to existing children. |
| 134 | /// |
| 135 | /// {@youtube 560 315 https://www.youtube.com/watch?v=ORiTTaVY6mM} |
| 136 | /// |
| 137 | /// If the children have a fixed extent in the main axis, consider using |
| 138 | /// [SliverFixedExtentList] rather than [SliverList] because |
| 139 | /// [SliverFixedExtentList] does not need to perform layout on its children to |
| 140 | /// obtain their extent in the main axis and is therefore more efficient. |
| 141 | /// |
| 142 | /// {@macro flutter.widgets.SliverChildDelegate.lifecycle} |
| 143 | /// |
| 144 | /// See also: |
| 145 | /// |
| 146 | /// * <https://docs.flutter.dev/ui/layout/scrolling/slivers>, a description |
| 147 | /// of what slivers are and how to use them. |
| 148 | /// * [SliverFixedExtentList], which is more efficient for children with |
| 149 | /// the same extent in the main axis. |
| 150 | /// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList] |
| 151 | /// except that it uses a prototype list item instead of a pixel value to define |
| 152 | /// the main axis extent of each item. |
| 153 | /// * [SliverAnimatedList], which animates items added to or removed from a |
| 154 | /// list. |
| 155 | /// * [SliverGrid], which places multiple children in a two dimensional grid. |
| 156 | /// * [SliverAnimatedGrid], a sliver which animates items when they are |
| 157 | /// inserted into or removed from a grid. |
| 158 | class SliverList extends SliverMultiBoxAdaptorWidget { |
| 159 | /// Creates a sliver that places box children in a linear array. |
| 160 | const SliverList({super.key, required super.delegate}); |
| 161 | |
| 162 | /// A sliver that places multiple box children in a linear array along the main |
| 163 | /// axis. |
| 164 | /// |
| 165 | /// This constructor is appropriate for sliver lists with a large (or |
| 166 | /// infinite) number of children because the builder is called only for those |
| 167 | /// children that are actually visible. |
| 168 | /// |
| 169 | /// Providing a non-null `itemCount` improves the ability of the [SliverList] |
| 170 | /// to estimate the maximum scroll extent. |
| 171 | /// |
| 172 | /// `itemBuilder` will be called only with indices greater than or equal to |
| 173 | /// zero and less than `itemCount`. |
| 174 | /// |
| 175 | /// {@macro flutter.widgets.ListView.builder.itemBuilder} |
| 176 | /// |
| 177 | /// {@macro flutter.widgets.PageView.findChildIndexCallback} |
| 178 | /// |
| 179 | /// The `addAutomaticKeepAlives` argument corresponds to the |
| 180 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| 181 | /// `addRepaintBoundaries` argument corresponds to the |
| 182 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| 183 | /// `addSemanticIndexes` argument corresponds to the |
| 184 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| 185 | /// |
| 186 | /// {@tool snippet} |
| 187 | /// This example, which would be provided in [CustomScrollView.slivers], |
| 188 | /// shows an infinite number of items in varying shades of blue: |
| 189 | /// |
| 190 | /// ```dart |
| 191 | /// SliverList.builder( |
| 192 | /// itemBuilder: (BuildContext context, int index) { |
| 193 | /// return Container( |
| 194 | /// alignment: Alignment.center, |
| 195 | /// color: Colors.lightBlue[100 * (index % 9)], |
| 196 | /// child: Text('list item $index'), |
| 197 | /// ); |
| 198 | /// }, |
| 199 | /// ) |
| 200 | /// ``` |
| 201 | /// {@end-tool} |
| 202 | SliverList.builder({ |
| 203 | super.key, |
| 204 | required NullableIndexedWidgetBuilder itemBuilder, |
| 205 | ChildIndexGetter? findChildIndexCallback, |
| 206 | int? itemCount, |
| 207 | bool addAutomaticKeepAlives = true, |
| 208 | bool addRepaintBoundaries = true, |
| 209 | bool addSemanticIndexes = true, |
| 210 | }) : super( |
| 211 | delegate: SliverChildBuilderDelegate( |
| 212 | itemBuilder, |
| 213 | findChildIndexCallback: findChildIndexCallback, |
| 214 | childCount: itemCount, |
| 215 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
| 216 | addRepaintBoundaries: addRepaintBoundaries, |
| 217 | addSemanticIndexes: addSemanticIndexes, |
| 218 | ), |
| 219 | ); |
| 220 | |
| 221 | /// A sliver that places multiple box children, separated by box widgets, in a |
| 222 | /// linear array along the main axis. |
| 223 | /// |
| 224 | /// This constructor is appropriate for sliver lists with a large (or |
| 225 | /// infinite) number of children because the builder is called only for those |
| 226 | /// children that are actually visible. |
| 227 | /// |
| 228 | /// Providing a non-null `itemCount` improves the ability of the [SliverList] |
| 229 | /// to estimate the maximum scroll extent. |
| 230 | /// |
| 231 | /// `itemBuilder` will be called only with indices greater than or equal to |
| 232 | /// zero and less than `itemCount`. |
| 233 | /// |
| 234 | /// {@macro flutter.widgets.ListView.builder.itemBuilder} |
| 235 | /// |
| 236 | /// {@macro flutter.widgets.PageView.findChildIndexCallback} |
| 237 | /// |
| 238 | /// |
| 239 | /// The `separatorBuilder` is similar to `itemBuilder`, except it is the widget |
| 240 | /// that gets placed between itemBuilder(context, index) and itemBuilder(context, index + 1). |
| 241 | /// |
| 242 | /// The `addAutomaticKeepAlives` argument corresponds to the |
| 243 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| 244 | /// `addRepaintBoundaries` argument corresponds to the |
| 245 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| 246 | /// `addSemanticIndexes` argument corresponds to the |
| 247 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| 248 | /// |
| 249 | /// {@tool snippet} |
| 250 | /// This example shows how to create a [SliverList] whose [Container] items |
| 251 | /// are separated by [Divider]s. The [SliverList] would be provided in |
| 252 | /// [CustomScrollView.slivers]. |
| 253 | /// |
| 254 | /// ```dart |
| 255 | /// SliverList.separated( |
| 256 | /// itemBuilder: (BuildContext context, int index) { |
| 257 | /// return Container( |
| 258 | /// alignment: Alignment.center, |
| 259 | /// color: Colors.lightBlue[100 * (index % 9)], |
| 260 | /// child: Text('list item $index'), |
| 261 | /// ); |
| 262 | /// }, |
| 263 | /// separatorBuilder: (BuildContext context, int index) => const Divider(), |
| 264 | /// ) |
| 265 | /// ``` |
| 266 | /// {@end-tool} |
| 267 | SliverList.separated({ |
| 268 | super.key, |
| 269 | required NullableIndexedWidgetBuilder itemBuilder, |
| 270 | ChildIndexGetter? findChildIndexCallback, |
| 271 | required NullableIndexedWidgetBuilder separatorBuilder, |
| 272 | int? itemCount, |
| 273 | bool addAutomaticKeepAlives = true, |
| 274 | bool addRepaintBoundaries = true, |
| 275 | bool addSemanticIndexes = true, |
| 276 | }) : super( |
| 277 | delegate: SliverChildBuilderDelegate( |
| 278 | (BuildContext context, int index) { |
| 279 | final int itemIndex = index ~/ 2; |
| 280 | final Widget? widget; |
| 281 | if (index.isEven) { |
| 282 | widget = itemBuilder(context, itemIndex); |
| 283 | } else { |
| 284 | widget = separatorBuilder(context, itemIndex); |
| 285 | assert(() { |
| 286 | if (widget == null) { |
| 287 | throw FlutterError('separatorBuilder cannot return null.' ); |
| 288 | } |
| 289 | return true; |
| 290 | }()); |
| 291 | } |
| 292 | return widget; |
| 293 | }, |
| 294 | findChildIndexCallback: findChildIndexCallback, |
| 295 | childCount: itemCount == null ? null : math.max(0, itemCount * 2 - 1), |
| 296 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
| 297 | addRepaintBoundaries: addRepaintBoundaries, |
| 298 | addSemanticIndexes: addSemanticIndexes, |
| 299 | semanticIndexCallback: (Widget _, int index) { |
| 300 | return index.isEven ? index ~/ 2 : null; |
| 301 | }, |
| 302 | ), |
| 303 | ); |
| 304 | |
| 305 | /// A sliver that places multiple box children in a linear array along the main |
| 306 | /// axis. |
| 307 | /// |
| 308 | /// This constructor uses a list of [Widget]s to build the sliver. |
| 309 | /// |
| 310 | /// The `addAutomaticKeepAlives` argument corresponds to the |
| 311 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| 312 | /// `addRepaintBoundaries` argument corresponds to the |
| 313 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| 314 | /// `addSemanticIndexes` argument corresponds to the |
| 315 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| 316 | /// |
| 317 | /// {@tool snippet} |
| 318 | /// This example, which would be provided in [CustomScrollView.slivers], |
| 319 | /// shows a list containing two [Text] widgets: |
| 320 | /// |
| 321 | /// ```dart |
| 322 | /// SliverList.list( |
| 323 | /// children: const <Widget>[ |
| 324 | /// Text('Hello'), |
| 325 | /// Text('World!'), |
| 326 | /// ], |
| 327 | /// ); |
| 328 | /// ``` |
| 329 | /// {@end-tool} |
| 330 | SliverList.list({ |
| 331 | super.key, |
| 332 | required List<Widget> children, |
| 333 | bool addAutomaticKeepAlives = true, |
| 334 | bool addRepaintBoundaries = true, |
| 335 | bool addSemanticIndexes = true, |
| 336 | }) : super( |
| 337 | delegate: SliverChildListDelegate( |
| 338 | children, |
| 339 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
| 340 | addRepaintBoundaries: addRepaintBoundaries, |
| 341 | addSemanticIndexes: addSemanticIndexes, |
| 342 | ), |
| 343 | ); |
| 344 | |
| 345 | @override |
| 346 | SliverMultiBoxAdaptorElement createElement() => |
| 347 | SliverMultiBoxAdaptorElement(this, replaceMovedChildren: true); |
| 348 | |
| 349 | @override |
| 350 | RenderSliverList createRenderObject(BuildContext context) { |
| 351 | final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; |
| 352 | return RenderSliverList(childManager: element); |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | /// A sliver that places multiple box children with the same main axis extent in |
| 357 | /// a linear array. |
| 358 | /// |
| 359 | /// _To learn more about slivers, see [CustomScrollView.slivers]._ |
| 360 | /// |
| 361 | /// [SliverFixedExtentList] places its children in a linear array along the main |
| 362 | /// axis starting at offset zero and without gaps. Each child is forced to have |
| 363 | /// the [itemExtent] in the main axis and the |
| 364 | /// [SliverConstraints.crossAxisExtent] in the cross axis. |
| 365 | /// |
| 366 | /// [SliverFixedExtentList] is more efficient than [SliverList] because |
| 367 | /// [SliverFixedExtentList] does not need to perform layout on its children to |
| 368 | /// obtain their extent in the main axis. |
| 369 | /// |
| 370 | /// {@tool snippet} |
| 371 | /// |
| 372 | /// This example, which would be inserted into a [CustomScrollView.slivers] |
| 373 | /// list, shows an infinite number of items in varying shades of blue: |
| 374 | /// |
| 375 | /// ```dart |
| 376 | /// SliverFixedExtentList( |
| 377 | /// itemExtent: 50.0, |
| 378 | /// delegate: SliverChildBuilderDelegate( |
| 379 | /// (BuildContext context, int index) { |
| 380 | /// return Container( |
| 381 | /// alignment: Alignment.center, |
| 382 | /// color: Colors.lightBlue[100 * (index % 9)], |
| 383 | /// child: Text('list item $index'), |
| 384 | /// ); |
| 385 | /// }, |
| 386 | /// ), |
| 387 | /// ) |
| 388 | /// ``` |
| 389 | /// {@end-tool} |
| 390 | /// |
| 391 | /// {@macro flutter.widgets.SliverChildDelegate.lifecycle} |
| 392 | /// |
| 393 | /// See also: |
| 394 | /// |
| 395 | /// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList] |
| 396 | /// except that it uses a prototype list item instead of a pixel value to define |
| 397 | /// the main axis extent of each item. |
| 398 | /// * [SliverVariedExtentList], which supports children with varying (but known |
| 399 | /// upfront) extents. |
| 400 | /// * [SliverFillViewport], which determines the [itemExtent] based on |
| 401 | /// [SliverConstraints.viewportMainAxisExtent]. |
| 402 | /// * [SliverList], which does not require its children to have the same |
| 403 | /// extent in the main axis. |
| 404 | class SliverFixedExtentList extends SliverMultiBoxAdaptorWidget { |
| 405 | /// Creates a sliver that places box children with the same main axis extent |
| 406 | /// in a linear array. |
| 407 | const SliverFixedExtentList({super.key, required super.delegate, required this.itemExtent}); |
| 408 | |
| 409 | /// A sliver that places multiple box children in a linear array along the main |
| 410 | /// axis. |
| 411 | /// |
| 412 | /// [SliverFixedExtentList] places its children in a linear array along the main |
| 413 | /// axis starting at offset zero and without gaps. Each child is forced to have |
| 414 | /// the [itemExtent] in the main axis and the |
| 415 | /// [SliverConstraints.crossAxisExtent] in the cross axis. |
| 416 | /// |
| 417 | /// This constructor is appropriate for sliver lists with a large (or |
| 418 | /// infinite) number of children whose extent is already determined. |
| 419 | /// |
| 420 | /// Providing a non-null `itemCount` improves the ability of the [SliverFixedExtentList] |
| 421 | /// to estimate the maximum scroll extent. |
| 422 | /// |
| 423 | /// `itemBuilder` will be called only with indices greater than or equal to |
| 424 | /// zero and less than `itemCount`. |
| 425 | /// |
| 426 | /// {@macro flutter.widgets.ListView.builder.itemBuilder} |
| 427 | /// |
| 428 | /// The `itemExtent` argument is the extent of each item. |
| 429 | /// |
| 430 | /// {@macro flutter.widgets.PageView.findChildIndexCallback} |
| 431 | /// |
| 432 | /// The `addAutomaticKeepAlives` argument corresponds to the |
| 433 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| 434 | /// `addRepaintBoundaries` argument corresponds to the |
| 435 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| 436 | /// `addSemanticIndexes` argument corresponds to the |
| 437 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| 438 | /// {@tool snippet} |
| 439 | /// |
| 440 | /// This example, which would be inserted into a [CustomScrollView.slivers] |
| 441 | /// list, shows an infinite number of items in varying shades of blue: |
| 442 | /// |
| 443 | /// ```dart |
| 444 | /// SliverFixedExtentList.builder( |
| 445 | /// itemExtent: 50.0, |
| 446 | /// itemBuilder: (BuildContext context, int index) { |
| 447 | /// return Container( |
| 448 | /// alignment: Alignment.center, |
| 449 | /// color: Colors.lightBlue[100 * (index % 9)], |
| 450 | /// child: Text('list item $index'), |
| 451 | /// ); |
| 452 | /// }, |
| 453 | /// ) |
| 454 | /// ``` |
| 455 | /// {@end-tool} |
| 456 | SliverFixedExtentList.builder({ |
| 457 | super.key, |
| 458 | required NullableIndexedWidgetBuilder itemBuilder, |
| 459 | required this.itemExtent, |
| 460 | ChildIndexGetter? findChildIndexCallback, |
| 461 | int? itemCount, |
| 462 | bool addAutomaticKeepAlives = true, |
| 463 | bool addRepaintBoundaries = true, |
| 464 | bool addSemanticIndexes = true, |
| 465 | }) : super( |
| 466 | delegate: SliverChildBuilderDelegate( |
| 467 | itemBuilder, |
| 468 | findChildIndexCallback: findChildIndexCallback, |
| 469 | childCount: itemCount, |
| 470 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
| 471 | addRepaintBoundaries: addRepaintBoundaries, |
| 472 | addSemanticIndexes: addSemanticIndexes, |
| 473 | ), |
| 474 | ); |
| 475 | |
| 476 | /// A sliver that places multiple box children in a linear array along the main |
| 477 | /// axis. |
| 478 | /// |
| 479 | /// [SliverFixedExtentList] places its children in a linear array along the main |
| 480 | /// axis starting at offset zero and without gaps. Each child is forced to have |
| 481 | /// the [itemExtent] in the main axis and the |
| 482 | /// [SliverConstraints.crossAxisExtent] in the cross axis. |
| 483 | /// |
| 484 | /// This constructor uses a list of [Widget]s to build the sliver. |
| 485 | /// |
| 486 | /// The `addAutomaticKeepAlives` argument corresponds to the |
| 487 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| 488 | /// `addRepaintBoundaries` argument corresponds to the |
| 489 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| 490 | /// `addSemanticIndexes` argument corresponds to the |
| 491 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| 492 | /// |
| 493 | /// {@tool snippet} |
| 494 | /// This example, which would be inserted into a [CustomScrollView.slivers] |
| 495 | /// list, shows an infinite number of items in varying shades of blue: |
| 496 | /// |
| 497 | /// ```dart |
| 498 | /// SliverFixedExtentList.list( |
| 499 | /// itemExtent: 50.0, |
| 500 | /// children: const <Widget>[ |
| 501 | /// Text('Hello'), |
| 502 | /// Text('World!'), |
| 503 | /// ], |
| 504 | /// ); |
| 505 | /// ``` |
| 506 | /// {@end-tool} |
| 507 | SliverFixedExtentList.list({ |
| 508 | super.key, |
| 509 | required List<Widget> children, |
| 510 | required this.itemExtent, |
| 511 | bool addAutomaticKeepAlives = true, |
| 512 | bool addRepaintBoundaries = true, |
| 513 | bool addSemanticIndexes = true, |
| 514 | }) : super( |
| 515 | delegate: SliverChildListDelegate( |
| 516 | children, |
| 517 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
| 518 | addRepaintBoundaries: addRepaintBoundaries, |
| 519 | addSemanticIndexes: addSemanticIndexes, |
| 520 | ), |
| 521 | ); |
| 522 | |
| 523 | /// The extent the children are forced to have in the main axis. |
| 524 | final double itemExtent; |
| 525 | |
| 526 | @override |
| 527 | RenderSliverFixedExtentList createRenderObject(BuildContext context) { |
| 528 | final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; |
| 529 | return RenderSliverFixedExtentList(childManager: element, itemExtent: itemExtent); |
| 530 | } |
| 531 | |
| 532 | @override |
| 533 | void updateRenderObject(BuildContext context, RenderSliverFixedExtentList renderObject) { |
| 534 | renderObject.itemExtent = itemExtent; |
| 535 | } |
| 536 | } |
| 537 | |
| 538 | /// A sliver that places its box children in a linear array and constrains them |
| 539 | /// to have the corresponding extent returned by [itemExtentBuilder]. |
| 540 | /// |
| 541 | /// _To learn more about slivers, see [CustomScrollView.slivers]._ |
| 542 | /// |
| 543 | /// [SliverVariedExtentList] arranges its children in a line along |
| 544 | /// the main axis starting at offset zero and without gaps. Each child is |
| 545 | /// constrained to the corresponding extent along the main axis |
| 546 | /// and the [SliverConstraints.crossAxisExtent] along the cross axis. |
| 547 | /// |
| 548 | /// [SliverVariedExtentList] is more efficient than [SliverList] because |
| 549 | /// [SliverVariedExtentList] does not need to lay out its children to obtain |
| 550 | /// their extent along the main axis. It's a little more flexible than |
| 551 | /// [SliverFixedExtentList] because this allow the children to have different extents. |
| 552 | /// |
| 553 | /// See also: |
| 554 | /// |
| 555 | /// * [SliverFixedExtentList], whose children are forced to a given pixel |
| 556 | /// extent. |
| 557 | /// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList] |
| 558 | /// except that it uses a prototype list item instead of a pixel value to define |
| 559 | /// the main axis extent of each item. |
| 560 | /// * [SliverList], which does not require its children to have the same |
| 561 | /// extent in the main axis. |
| 562 | /// * [SliverFillViewport], which sizes its children based on the |
| 563 | /// size of the viewport, regardless of what else is in the scroll view. |
| 564 | class SliverVariedExtentList extends SliverMultiBoxAdaptorWidget { |
| 565 | /// Creates a sliver that places box children with the same main axis extent |
| 566 | /// in a linear array. |
| 567 | const SliverVariedExtentList({ |
| 568 | super.key, |
| 569 | required super.delegate, |
| 570 | required this.itemExtentBuilder, |
| 571 | }); |
| 572 | |
| 573 | /// A sliver that places multiple box children in a linear array along the main |
| 574 | /// axis. |
| 575 | /// |
| 576 | /// [SliverVariedExtentList] places its children in a linear array along the main |
| 577 | /// axis starting at offset zero and without gaps. Each child is forced to have |
| 578 | /// the returned extent of [itemExtentBuilder] in the main axis and the |
| 579 | /// [SliverConstraints.crossAxisExtent] in the cross axis. |
| 580 | /// |
| 581 | /// This constructor is appropriate for sliver lists with a large (or |
| 582 | /// infinite) number of children whose extent is already determined. |
| 583 | /// |
| 584 | /// Providing a non-null `itemCount` improves the ability of the [SliverVariedExtentList] |
| 585 | /// to estimate the maximum scroll extent. |
| 586 | SliverVariedExtentList.builder({ |
| 587 | super.key, |
| 588 | required NullableIndexedWidgetBuilder itemBuilder, |
| 589 | required this.itemExtentBuilder, |
| 590 | ChildIndexGetter? findChildIndexCallback, |
| 591 | int? itemCount, |
| 592 | bool addAutomaticKeepAlives = true, |
| 593 | bool addRepaintBoundaries = true, |
| 594 | bool addSemanticIndexes = true, |
| 595 | }) : super( |
| 596 | delegate: SliverChildBuilderDelegate( |
| 597 | itemBuilder, |
| 598 | findChildIndexCallback: findChildIndexCallback, |
| 599 | childCount: itemCount, |
| 600 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
| 601 | addRepaintBoundaries: addRepaintBoundaries, |
| 602 | addSemanticIndexes: addSemanticIndexes, |
| 603 | ), |
| 604 | ); |
| 605 | |
| 606 | /// A sliver that places multiple box children in a linear array along the main |
| 607 | /// axis. |
| 608 | /// |
| 609 | /// [SliverVariedExtentList] places its children in a linear array along the main |
| 610 | /// axis starting at offset zero and without gaps. Each child is forced to have |
| 611 | /// the returned extent of [itemExtentBuilder] in the main axis and the |
| 612 | /// [SliverConstraints.crossAxisExtent] in the cross axis. |
| 613 | /// |
| 614 | /// This constructor uses a list of [Widget]s to build the sliver. |
| 615 | SliverVariedExtentList.list({ |
| 616 | super.key, |
| 617 | required List<Widget> children, |
| 618 | required this.itemExtentBuilder, |
| 619 | bool addAutomaticKeepAlives = true, |
| 620 | bool addRepaintBoundaries = true, |
| 621 | bool addSemanticIndexes = true, |
| 622 | }) : super( |
| 623 | delegate: SliverChildListDelegate( |
| 624 | children, |
| 625 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
| 626 | addRepaintBoundaries: addRepaintBoundaries, |
| 627 | addSemanticIndexes: addSemanticIndexes, |
| 628 | ), |
| 629 | ); |
| 630 | |
| 631 | /// The children extent builder. |
| 632 | /// |
| 633 | /// Should return null if asked to build an item extent with a greater index than |
| 634 | /// exists. |
| 635 | final ItemExtentBuilder itemExtentBuilder; |
| 636 | |
| 637 | @override |
| 638 | RenderSliverVariedExtentList createRenderObject(BuildContext context) { |
| 639 | final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; |
| 640 | return RenderSliverVariedExtentList( |
| 641 | childManager: element, |
| 642 | itemExtentBuilder: itemExtentBuilder, |
| 643 | ); |
| 644 | } |
| 645 | |
| 646 | @override |
| 647 | void updateRenderObject(BuildContext context, RenderSliverVariedExtentList renderObject) { |
| 648 | renderObject.itemExtentBuilder = itemExtentBuilder; |
| 649 | } |
| 650 | } |
| 651 | |
| 652 | /// A sliver that places multiple box children in a two dimensional arrangement. |
| 653 | /// |
| 654 | /// _To learn more about slivers, see [CustomScrollView.slivers]._ |
| 655 | /// |
| 656 | /// [SliverGrid] places its children in arbitrary positions determined by |
| 657 | /// [gridDelegate]. Each child is forced to have the size specified by the |
| 658 | /// [gridDelegate]. |
| 659 | /// |
| 660 | /// The main axis direction of a grid is the direction in which it scrolls; the |
| 661 | /// cross axis direction is the orthogonal direction. |
| 662 | /// |
| 663 | /// {@youtube 560 315 https://www.youtube.com/watch?v=ORiTTaVY6mM} |
| 664 | /// |
| 665 | /// {@tool snippet} |
| 666 | /// |
| 667 | /// This example, which would be inserted into a [CustomScrollView.slivers] |
| 668 | /// list, shows twenty boxes in a pretty teal grid: |
| 669 | /// |
| 670 | /// ```dart |
| 671 | /// SliverGrid( |
| 672 | /// gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( |
| 673 | /// maxCrossAxisExtent: 200.0, |
| 674 | /// mainAxisSpacing: 10.0, |
| 675 | /// crossAxisSpacing: 10.0, |
| 676 | /// childAspectRatio: 4.0, |
| 677 | /// ), |
| 678 | /// delegate: SliverChildBuilderDelegate( |
| 679 | /// (BuildContext context, int index) { |
| 680 | /// return Container( |
| 681 | /// alignment: Alignment.center, |
| 682 | /// color: Colors.teal[100 * (index % 9)], |
| 683 | /// child: Text('grid item $index'), |
| 684 | /// ); |
| 685 | /// }, |
| 686 | /// childCount: 20, |
| 687 | /// ), |
| 688 | /// ) |
| 689 | /// ``` |
| 690 | /// {@end-tool} |
| 691 | /// |
| 692 | /// {@macro flutter.widgets.SliverChildDelegate.lifecycle} |
| 693 | /// |
| 694 | /// See also: |
| 695 | /// |
| 696 | /// * [SliverList], which places its children in a linear array. |
| 697 | /// * [SliverFixedExtentList], which places its children in a linear |
| 698 | /// array with a fixed extent in the main axis. |
| 699 | /// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList] |
| 700 | /// except that it uses a prototype list item instead of a pixel value to define |
| 701 | /// the main axis extent of each item. |
| 702 | class SliverGrid extends SliverMultiBoxAdaptorWidget { |
| 703 | /// Creates a sliver that places multiple box children in a two dimensional |
| 704 | /// arrangement. |
| 705 | const SliverGrid({super.key, required super.delegate, required this.gridDelegate}); |
| 706 | |
| 707 | /// A sliver that creates a 2D array of widgets that are created on demand. |
| 708 | /// |
| 709 | /// This constructor is appropriate for sliver grids with a large (or |
| 710 | /// infinite) number of children because the builder is called only for those |
| 711 | /// children that are actually visible. |
| 712 | /// |
| 713 | /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] |
| 714 | /// to estimate the maximum scroll extent. |
| 715 | /// |
| 716 | /// `itemBuilder` will be called only with indices greater than or equal to |
| 717 | /// zero and less than `itemCount`. |
| 718 | /// |
| 719 | /// {@macro flutter.widgets.ListView.builder.itemBuilder} |
| 720 | /// |
| 721 | /// {@macro flutter.widgets.PageView.findChildIndexCallback} |
| 722 | /// |
| 723 | /// The [gridDelegate] argument is required. |
| 724 | /// |
| 725 | /// The `addAutomaticKeepAlives` argument corresponds to the |
| 726 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| 727 | /// `addRepaintBoundaries` argument corresponds to the |
| 728 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| 729 | /// `addSemanticIndexes` argument corresponds to the |
| 730 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| 731 | SliverGrid.builder({ |
| 732 | super.key, |
| 733 | required this.gridDelegate, |
| 734 | required NullableIndexedWidgetBuilder itemBuilder, |
| 735 | ChildIndexGetter? findChildIndexCallback, |
| 736 | int? itemCount, |
| 737 | bool addAutomaticKeepAlives = true, |
| 738 | bool addRepaintBoundaries = true, |
| 739 | bool addSemanticIndexes = true, |
| 740 | }) : super( |
| 741 | delegate: SliverChildBuilderDelegate( |
| 742 | itemBuilder, |
| 743 | findChildIndexCallback: findChildIndexCallback, |
| 744 | childCount: itemCount, |
| 745 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
| 746 | addRepaintBoundaries: addRepaintBoundaries, |
| 747 | addSemanticIndexes: addSemanticIndexes, |
| 748 | ), |
| 749 | ); |
| 750 | |
| 751 | /// Creates a sliver that places multiple box children in a two dimensional |
| 752 | /// arrangement with a fixed number of tiles in the cross axis. |
| 753 | /// |
| 754 | /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate], |
| 755 | /// and a [SliverChildListDelegate] as the [delegate]. |
| 756 | /// |
| 757 | /// See also: |
| 758 | /// |
| 759 | /// * [GridView.count], the equivalent constructor for [GridView] widgets. |
| 760 | SliverGrid.count({ |
| 761 | super.key, |
| 762 | required int crossAxisCount, |
| 763 | double mainAxisSpacing = 0.0, |
| 764 | double crossAxisSpacing = 0.0, |
| 765 | double childAspectRatio = 1.0, |
| 766 | List<Widget> children = const <Widget>[], |
| 767 | }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( |
| 768 | crossAxisCount: crossAxisCount, |
| 769 | mainAxisSpacing: mainAxisSpacing, |
| 770 | crossAxisSpacing: crossAxisSpacing, |
| 771 | childAspectRatio: childAspectRatio, |
| 772 | ), |
| 773 | super(delegate: SliverChildListDelegate(children)); |
| 774 | |
| 775 | /// Creates a sliver that places multiple box children in a two dimensional |
| 776 | /// arrangement with tiles that each have a maximum cross-axis extent. |
| 777 | /// |
| 778 | /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate], |
| 779 | /// and a [SliverChildListDelegate] as the [delegate]. |
| 780 | /// |
| 781 | /// See also: |
| 782 | /// |
| 783 | /// * [GridView.extent], the equivalent constructor for [GridView] widgets. |
| 784 | SliverGrid.extent({ |
| 785 | super.key, |
| 786 | required double maxCrossAxisExtent, |
| 787 | double mainAxisSpacing = 0.0, |
| 788 | double crossAxisSpacing = 0.0, |
| 789 | double childAspectRatio = 1.0, |
| 790 | List<Widget> children = const <Widget>[], |
| 791 | }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent( |
| 792 | maxCrossAxisExtent: maxCrossAxisExtent, |
| 793 | mainAxisSpacing: mainAxisSpacing, |
| 794 | crossAxisSpacing: crossAxisSpacing, |
| 795 | childAspectRatio: childAspectRatio, |
| 796 | ), |
| 797 | super(delegate: SliverChildListDelegate(children)); |
| 798 | |
| 799 | /// The delegate that controls the size and position of the children. |
| 800 | final SliverGridDelegate gridDelegate; |
| 801 | |
| 802 | @override |
| 803 | RenderSliverGrid createRenderObject(BuildContext context) { |
| 804 | final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; |
| 805 | return RenderSliverGrid(childManager: element, gridDelegate: gridDelegate); |
| 806 | } |
| 807 | |
| 808 | @override |
| 809 | void updateRenderObject(BuildContext context, RenderSliverGrid renderObject) { |
| 810 | renderObject.gridDelegate = gridDelegate; |
| 811 | } |
| 812 | |
| 813 | @override |
| 814 | double estimateMaxScrollOffset( |
| 815 | SliverConstraints? constraints, |
| 816 | int firstIndex, |
| 817 | int lastIndex, |
| 818 | double leadingScrollOffset, |
| 819 | double trailingScrollOffset, |
| 820 | ) { |
| 821 | return super.estimateMaxScrollOffset( |
| 822 | constraints, |
| 823 | firstIndex, |
| 824 | lastIndex, |
| 825 | leadingScrollOffset, |
| 826 | trailingScrollOffset, |
| 827 | ) ?? |
| 828 | gridDelegate.getLayout(constraints!).computeMaxScrollOffset(delegate.estimatedChildCount!); |
| 829 | } |
| 830 | } |
| 831 | |
| 832 | /// An element that lazily builds children for a [SliverMultiBoxAdaptorWidget]. |
| 833 | /// |
| 834 | /// Implements [RenderSliverBoxChildManager], which lets this element manage |
| 835 | /// the children of subclasses of [RenderSliverMultiBoxAdaptor]. |
| 836 | class SliverMultiBoxAdaptorElement extends RenderObjectElement |
| 837 | implements RenderSliverBoxChildManager { |
| 838 | /// Creates an element that lazily builds children for the given widget. |
| 839 | /// |
| 840 | /// If `replaceMovedChildren` is set to true, a new child is proactively |
| 841 | /// inflate for the index that was previously occupied by a child that moved |
| 842 | /// to a new index. The layout offset of the moved child is copied over to the |
| 843 | /// new child. RenderObjects, that depend on the layout offset of existing |
| 844 | /// children during [RenderObject.performLayout] should set this to true |
| 845 | /// (example: [RenderSliverList]). For RenderObjects that figure out the |
| 846 | /// layout offset of their children without looking at the layout offset of |
| 847 | /// existing children this should be set to false (example: |
| 848 | /// [RenderSliverFixedExtentList]) to avoid inflating unnecessary children. |
| 849 | SliverMultiBoxAdaptorElement( |
| 850 | SliverMultiBoxAdaptorWidget super.widget, { |
| 851 | bool replaceMovedChildren = false, |
| 852 | }) : _replaceMovedChildren = replaceMovedChildren; |
| 853 | |
| 854 | final bool _replaceMovedChildren; |
| 855 | |
| 856 | @override |
| 857 | RenderSliverMultiBoxAdaptor get renderObject => super.renderObject as RenderSliverMultiBoxAdaptor; |
| 858 | |
| 859 | @override |
| 860 | void update(covariant SliverMultiBoxAdaptorWidget newWidget) { |
| 861 | final SliverMultiBoxAdaptorWidget oldWidget = widget as SliverMultiBoxAdaptorWidget; |
| 862 | super.update(newWidget); |
| 863 | final SliverChildDelegate newDelegate = newWidget.delegate; |
| 864 | final SliverChildDelegate oldDelegate = oldWidget.delegate; |
| 865 | if (newDelegate != oldDelegate && |
| 866 | (newDelegate.runtimeType != oldDelegate.runtimeType || |
| 867 | newDelegate.shouldRebuild(oldDelegate))) { |
| 868 | performRebuild(); |
| 869 | } |
| 870 | } |
| 871 | |
| 872 | final SplayTreeMap<int, Element?> _childElements = SplayTreeMap<int, Element?>(); |
| 873 | RenderBox? _currentBeforeChild; |
| 874 | |
| 875 | @override |
| 876 | void performRebuild() { |
| 877 | super.performRebuild(); |
| 878 | _currentBeforeChild = null; |
| 879 | bool childrenUpdated = false; |
| 880 | assert(_currentlyUpdatingChildIndex == null); |
| 881 | try { |
| 882 | final SplayTreeMap<int, Element?> newChildren = SplayTreeMap<int, Element?>(); |
| 883 | final Map<int, double> indexToLayoutOffset = HashMap<int, double>(); |
| 884 | final SliverMultiBoxAdaptorWidget adaptorWidget = widget as SliverMultiBoxAdaptorWidget; |
| 885 | void processElement(int index) { |
| 886 | _currentlyUpdatingChildIndex = index; |
| 887 | if (_childElements[index] != null && _childElements[index] != newChildren[index]) { |
| 888 | // This index has an old child that isn't used anywhere and should be deactivated. |
| 889 | _childElements[index] = updateChild(_childElements[index], null, index); |
| 890 | childrenUpdated = true; |
| 891 | } |
| 892 | final Element? newChild = updateChild( |
| 893 | newChildren[index], |
| 894 | _build(index, adaptorWidget), |
| 895 | index, |
| 896 | ); |
| 897 | if (newChild != null) { |
| 898 | childrenUpdated = childrenUpdated || _childElements[index] != newChild; |
| 899 | _childElements[index] = newChild; |
| 900 | final SliverMultiBoxAdaptorParentData parentData = |
| 901 | newChild.renderObject!.parentData! as SliverMultiBoxAdaptorParentData; |
| 902 | if (index == 0) { |
| 903 | parentData.layoutOffset = 0.0; |
| 904 | } else if (indexToLayoutOffset.containsKey(index)) { |
| 905 | parentData.layoutOffset = indexToLayoutOffset[index]; |
| 906 | } |
| 907 | if (!parentData.keptAlive) { |
| 908 | _currentBeforeChild = newChild.renderObject as RenderBox?; |
| 909 | } |
| 910 | } else { |
| 911 | childrenUpdated = true; |
| 912 | _childElements.remove(index); |
| 913 | } |
| 914 | } |
| 915 | |
| 916 | for (final int index in _childElements.keys.toList()) { |
| 917 | final Key? key = _childElements[index]!.widget.key; |
| 918 | final int? newIndex = key == null ? null : adaptorWidget.delegate.findIndexByKey(key); |
| 919 | final SliverMultiBoxAdaptorParentData? childParentData = |
| 920 | _childElements[index]!.renderObject?.parentData as SliverMultiBoxAdaptorParentData?; |
| 921 | |
| 922 | if (childParentData != null && childParentData.layoutOffset != null) { |
| 923 | indexToLayoutOffset[index] = childParentData.layoutOffset!; |
| 924 | } |
| 925 | |
| 926 | if (newIndex != null && newIndex != index) { |
| 927 | // The layout offset of the child being moved is no longer accurate. |
| 928 | if (childParentData != null) { |
| 929 | childParentData.layoutOffset = null; |
| 930 | } |
| 931 | |
| 932 | newChildren[newIndex] = _childElements[index]; |
| 933 | if (_replaceMovedChildren) { |
| 934 | // We need to make sure the original index gets processed. |
| 935 | newChildren.putIfAbsent(index, () => null); |
| 936 | } |
| 937 | // We do not want the remapped child to get deactivated during processElement. |
| 938 | _childElements.remove(index); |
| 939 | } else { |
| 940 | newChildren.putIfAbsent(index, () => _childElements[index]); |
| 941 | } |
| 942 | } |
| 943 | |
| 944 | renderObject.debugChildIntegrityEnabled = |
| 945 | false; // Moving children will temporary violate the integrity. |
| 946 | newChildren.keys.forEach(processElement); |
| 947 | // An element rebuild only updates existing children. The underflow check |
| 948 | // is here to make sure we look ahead one more child if we were at the end |
| 949 | // of the child list before the update. By doing so, we can update the max |
| 950 | // scroll offset during the layout phase. Otherwise, the layout phase may |
| 951 | // be skipped, and the scroll view may be stuck at the previous max |
| 952 | // scroll offset. |
| 953 | // |
| 954 | // This logic is not needed if any existing children has been updated, |
| 955 | // because we will not skip the layout phase if that happens. |
| 956 | if (!childrenUpdated && _didUnderflow) { |
| 957 | final int lastKey = _childElements.lastKey() ?? -1; |
| 958 | final int rightBoundary = lastKey + 1; |
| 959 | newChildren[rightBoundary] = _childElements[rightBoundary]; |
| 960 | processElement(rightBoundary); |
| 961 | } |
| 962 | } finally { |
| 963 | _currentlyUpdatingChildIndex = null; |
| 964 | renderObject.debugChildIntegrityEnabled = true; |
| 965 | } |
| 966 | } |
| 967 | |
| 968 | Widget? _build(int index, SliverMultiBoxAdaptorWidget widget) { |
| 969 | return widget.delegate.build(this, index); |
| 970 | } |
| 971 | |
| 972 | @override |
| 973 | void createChild(int index, {required RenderBox? after}) { |
| 974 | assert(_currentlyUpdatingChildIndex == null); |
| 975 | owner!.buildScope(this, () { |
| 976 | final bool insertFirst = after == null; |
| 977 | assert(insertFirst || _childElements[index - 1] != null); |
| 978 | _currentBeforeChild = insertFirst |
| 979 | ? null |
| 980 | : (_childElements[index - 1]!.renderObject as RenderBox?); |
| 981 | Element? newChild; |
| 982 | try { |
| 983 | final SliverMultiBoxAdaptorWidget adaptorWidget = widget as SliverMultiBoxAdaptorWidget; |
| 984 | _currentlyUpdatingChildIndex = index; |
| 985 | newChild = updateChild(_childElements[index], _build(index, adaptorWidget), index); |
| 986 | } finally { |
| 987 | _currentlyUpdatingChildIndex = null; |
| 988 | } |
| 989 | if (newChild != null) { |
| 990 | _childElements[index] = newChild; |
| 991 | } else { |
| 992 | _childElements.remove(index); |
| 993 | } |
| 994 | }); |
| 995 | } |
| 996 | |
| 997 | @override |
| 998 | Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) { |
| 999 | final SliverMultiBoxAdaptorParentData? oldParentData = |
| 1000 | child?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?; |
| 1001 | final Element? newChild = super.updateChild(child, newWidget, newSlot); |
| 1002 | final SliverMultiBoxAdaptorParentData? newParentData = |
| 1003 | newChild?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?; |
| 1004 | |
| 1005 | // Preserve the old layoutOffset if the renderObject was swapped out. |
| 1006 | if (oldParentData != newParentData && oldParentData != null && newParentData != null) { |
| 1007 | newParentData.layoutOffset = oldParentData.layoutOffset; |
| 1008 | } |
| 1009 | return newChild; |
| 1010 | } |
| 1011 | |
| 1012 | @override |
| 1013 | void forgetChild(Element child) { |
| 1014 | assert(child.slot != null); |
| 1015 | assert(_childElements.containsKey(child.slot)); |
| 1016 | _childElements.remove(child.slot); |
| 1017 | super.forgetChild(child); |
| 1018 | } |
| 1019 | |
| 1020 | @override |
| 1021 | void removeChild(RenderBox child) { |
| 1022 | final int index = renderObject.indexOf(child); |
| 1023 | assert(_currentlyUpdatingChildIndex == null); |
| 1024 | assert(index >= 0); |
| 1025 | owner!.buildScope(this, () { |
| 1026 | assert(_childElements.containsKey(index)); |
| 1027 | try { |
| 1028 | _currentlyUpdatingChildIndex = index; |
| 1029 | final Element? result = updateChild(_childElements[index], null, index); |
| 1030 | assert(result == null); |
| 1031 | } finally { |
| 1032 | _currentlyUpdatingChildIndex = null; |
| 1033 | } |
| 1034 | _childElements.remove(index); |
| 1035 | assert(!_childElements.containsKey(index)); |
| 1036 | }); |
| 1037 | } |
| 1038 | |
| 1039 | static double _extrapolateMaxScrollOffset( |
| 1040 | int firstIndex, |
| 1041 | int lastIndex, |
| 1042 | double leadingScrollOffset, |
| 1043 | double trailingScrollOffset, |
| 1044 | int childCount, |
| 1045 | ) { |
| 1046 | if (lastIndex == childCount - 1) { |
| 1047 | return trailingScrollOffset; |
| 1048 | } |
| 1049 | final int reifiedCount = lastIndex - firstIndex + 1; |
| 1050 | final double averageExtent = (trailingScrollOffset - leadingScrollOffset) / reifiedCount; |
| 1051 | final int remainingCount = childCount - lastIndex - 1; |
| 1052 | return trailingScrollOffset + averageExtent * remainingCount; |
| 1053 | } |
| 1054 | |
| 1055 | @override |
| 1056 | double estimateMaxScrollOffset( |
| 1057 | SliverConstraints? constraints, { |
| 1058 | int? firstIndex, |
| 1059 | int? lastIndex, |
| 1060 | double? leadingScrollOffset, |
| 1061 | double? trailingScrollOffset, |
| 1062 | }) { |
| 1063 | final int? childCount = estimatedChildCount; |
| 1064 | if (childCount == null) { |
| 1065 | return double.infinity; |
| 1066 | } |
| 1067 | return (widget as SliverMultiBoxAdaptorWidget).estimateMaxScrollOffset( |
| 1068 | constraints, |
| 1069 | firstIndex!, |
| 1070 | lastIndex!, |
| 1071 | leadingScrollOffset!, |
| 1072 | trailingScrollOffset!, |
| 1073 | ) ?? |
| 1074 | _extrapolateMaxScrollOffset( |
| 1075 | firstIndex, |
| 1076 | lastIndex, |
| 1077 | leadingScrollOffset, |
| 1078 | trailingScrollOffset, |
| 1079 | childCount, |
| 1080 | ); |
| 1081 | } |
| 1082 | |
| 1083 | @override |
| 1084 | int? get estimatedChildCount => |
| 1085 | (widget as SliverMultiBoxAdaptorWidget).delegate.estimatedChildCount; |
| 1086 | |
| 1087 | @override |
| 1088 | int get childCount { |
| 1089 | int? result = estimatedChildCount; |
| 1090 | if (result == null) { |
| 1091 | // Since childCount was called, we know that we reached the end of |
| 1092 | // the list (as in, _build return null once), so we know that the |
| 1093 | // list is finite. |
| 1094 | // Let's do an open-ended binary search to find the end of the list |
| 1095 | // manually. |
| 1096 | int lo = 0; |
| 1097 | int hi = 1; |
| 1098 | final SliverMultiBoxAdaptorWidget adaptorWidget = widget as SliverMultiBoxAdaptorWidget; |
| 1099 | const int max = kIsWeb |
| 1100 | ? 9007199254740992 // max safe integer on JS (from 0 to this number x != x+1) |
| 1101 | : ((1 << 63) - 1); |
| 1102 | while (_build(hi - 1, adaptorWidget) != null) { |
| 1103 | lo = hi - 1; |
| 1104 | if (hi < max ~/ 2) { |
| 1105 | hi *= 2; |
| 1106 | } else if (hi < max) { |
| 1107 | hi = max; |
| 1108 | } else { |
| 1109 | throw FlutterError( |
| 1110 | 'Could not find the number of children in ${adaptorWidget.delegate}.\n' |
| 1111 | "The childCount getter was called (implying that the delegate's builder returned null " |
| 1112 | 'for a positive index), but even building the child with index $hi (the maximum ' |
| 1113 | 'possible integer) did not return null. Consider implementing childCount to avoid ' |
| 1114 | 'the cost of searching for the final child.' , |
| 1115 | ); |
| 1116 | } |
| 1117 | } |
| 1118 | while (hi - lo > 1) { |
| 1119 | final int mid = (hi - lo) ~/ 2 + lo; |
| 1120 | if (_build(mid - 1, adaptorWidget) == null) { |
| 1121 | hi = mid; |
| 1122 | } else { |
| 1123 | lo = mid; |
| 1124 | } |
| 1125 | } |
| 1126 | result = lo; |
| 1127 | } |
| 1128 | return result; |
| 1129 | } |
| 1130 | |
| 1131 | @override |
| 1132 | void didStartLayout() { |
| 1133 | assert(debugAssertChildListLocked()); |
| 1134 | } |
| 1135 | |
| 1136 | @override |
| 1137 | void didFinishLayout() { |
| 1138 | assert(debugAssertChildListLocked()); |
| 1139 | final int firstIndex = _childElements.firstKey() ?? 0; |
| 1140 | final int lastIndex = _childElements.lastKey() ?? 0; |
| 1141 | (widget as SliverMultiBoxAdaptorWidget).delegate.didFinishLayout(firstIndex, lastIndex); |
| 1142 | } |
| 1143 | |
| 1144 | int? _currentlyUpdatingChildIndex; |
| 1145 | |
| 1146 | @override |
| 1147 | bool debugAssertChildListLocked() { |
| 1148 | assert(_currentlyUpdatingChildIndex == null); |
| 1149 | return true; |
| 1150 | } |
| 1151 | |
| 1152 | @override |
| 1153 | void didAdoptChild(RenderBox child) { |
| 1154 | assert(_currentlyUpdatingChildIndex != null); |
| 1155 | final SliverMultiBoxAdaptorParentData childParentData = |
| 1156 | child.parentData! as SliverMultiBoxAdaptorParentData; |
| 1157 | childParentData.index = _currentlyUpdatingChildIndex; |
| 1158 | } |
| 1159 | |
| 1160 | bool _didUnderflow = false; |
| 1161 | |
| 1162 | @override |
| 1163 | void setDidUnderflow(bool value) { |
| 1164 | _didUnderflow = value; |
| 1165 | } |
| 1166 | |
| 1167 | @override |
| 1168 | void insertRenderObjectChild(covariant RenderObject child, int slot) { |
| 1169 | assert(_currentlyUpdatingChildIndex == slot); |
| 1170 | assert(renderObject.debugValidateChild(child)); |
| 1171 | renderObject.insert(child as RenderBox, after: _currentBeforeChild); |
| 1172 | assert(() { |
| 1173 | final SliverMultiBoxAdaptorParentData childParentData = |
| 1174 | child.parentData! as SliverMultiBoxAdaptorParentData; |
| 1175 | assert(slot == childParentData.index); |
| 1176 | return true; |
| 1177 | }()); |
| 1178 | } |
| 1179 | |
| 1180 | @override |
| 1181 | void moveRenderObjectChild(covariant RenderObject child, int oldSlot, int newSlot) { |
| 1182 | assert(_currentlyUpdatingChildIndex == newSlot); |
| 1183 | renderObject.move(child as RenderBox, after: _currentBeforeChild); |
| 1184 | } |
| 1185 | |
| 1186 | @override |
| 1187 | void removeRenderObjectChild(covariant RenderObject child, int slot) { |
| 1188 | assert(_currentlyUpdatingChildIndex != null); |
| 1189 | renderObject.remove(child as RenderBox); |
| 1190 | } |
| 1191 | |
| 1192 | @override |
| 1193 | void visitChildren(ElementVisitor visitor) { |
| 1194 | // The toList() is to make a copy so that the underlying list can be modified by |
| 1195 | // the visitor: |
| 1196 | assert(!_childElements.values.any((Element? child) => child == null)); |
| 1197 | _childElements.values.cast<Element>().toList().forEach(visitor); |
| 1198 | } |
| 1199 | |
| 1200 | @override |
| 1201 | void debugVisitOnstageChildren(ElementVisitor visitor) { |
| 1202 | _childElements.values |
| 1203 | .cast<Element>() |
| 1204 | .where((Element child) { |
| 1205 | final SliverMultiBoxAdaptorParentData parentData = |
| 1206 | child.renderObject!.parentData! as SliverMultiBoxAdaptorParentData; |
| 1207 | final double itemExtent = switch (renderObject.constraints.axis) { |
| 1208 | Axis.horizontal => child.renderObject!.paintBounds.width, |
| 1209 | Axis.vertical => child.renderObject!.paintBounds.height, |
| 1210 | }; |
| 1211 | |
| 1212 | return parentData.layoutOffset != null && |
| 1213 | parentData.layoutOffset! < |
| 1214 | renderObject.constraints.scrollOffset + |
| 1215 | renderObject.constraints.remainingPaintExtent && |
| 1216 | parentData.layoutOffset! + itemExtent > renderObject.constraints.scrollOffset; |
| 1217 | }) |
| 1218 | .forEach(visitor); |
| 1219 | } |
| 1220 | } |
| 1221 | |
| 1222 | /// A sliver widget that makes its sliver child partially transparent. |
| 1223 | /// |
| 1224 | /// This class paints its sliver child into an intermediate buffer and then |
| 1225 | /// blends the sliver back into the scene partially transparent. |
| 1226 | /// |
| 1227 | /// For values of opacity other than 0.0 and 1.0, this class is relatively |
| 1228 | /// expensive because it requires painting the sliver child into an intermediate |
| 1229 | /// buffer. For the value 0.0, the sliver child is not painted at all. |
| 1230 | /// For the value 1.0, the sliver child is painted immediately without an |
| 1231 | /// intermediate buffer. |
| 1232 | /// |
| 1233 | /// {@tool dartpad} |
| 1234 | /// |
| 1235 | /// This example shows a [SliverList] when the `_visible` member field is true, |
| 1236 | /// and hides it when it is false. |
| 1237 | /// |
| 1238 | /// This is more efficient than adding and removing the sliver child widget from |
| 1239 | /// the tree on demand, but it does not affect how much the list scrolls (the |
| 1240 | /// [SliverList] is still present, merely invisible). |
| 1241 | /// |
| 1242 | /// ** See code in examples/api/lib/widgets/sliver/sliver_opacity.1.dart ** |
| 1243 | /// {@end-tool} |
| 1244 | /// |
| 1245 | /// See also: |
| 1246 | /// |
| 1247 | /// * [Opacity], which can apply a uniform alpha effect to its child using the |
| 1248 | /// [RenderBox] layout protocol. |
| 1249 | /// * [AnimatedOpacity], which uses an animation internally to efficiently |
| 1250 | /// animate [Opacity]. |
| 1251 | /// * [SliverVisibility], which can hide a child more efficiently (albeit less |
| 1252 | /// subtly, because it is either visible or hidden, rather than allowing |
| 1253 | /// fractional opacity values). Specifically, the [SliverVisibility.maintain] |
| 1254 | /// constructor is equivalent to using a sliver opacity widget with values of |
| 1255 | /// `0.0` or `1.0`. |
| 1256 | class SliverOpacity extends SingleChildRenderObjectWidget { |
| 1257 | /// Creates a sliver that makes its sliver child partially transparent. |
| 1258 | /// |
| 1259 | /// The [opacity] argument must be between zero and one, inclusive. |
| 1260 | const SliverOpacity({ |
| 1261 | super.key, |
| 1262 | required this.opacity, |
| 1263 | this.alwaysIncludeSemantics = false, |
| 1264 | Widget? sliver, |
| 1265 | }) : assert(opacity >= 0.0 && opacity <= 1.0), |
| 1266 | super(child: sliver); |
| 1267 | |
| 1268 | /// The fraction to scale the sliver child's alpha value. |
| 1269 | /// |
| 1270 | /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent |
| 1271 | /// (i.e. invisible). |
| 1272 | /// |
| 1273 | /// Values 1.0 and 0.0 are painted with a fast path. Other values |
| 1274 | /// require painting the sliver child into an intermediate buffer, which is |
| 1275 | /// expensive. |
| 1276 | final double opacity; |
| 1277 | |
| 1278 | /// Whether the semantic information of the sliver child is always included. |
| 1279 | /// |
| 1280 | /// Defaults to false. |
| 1281 | /// |
| 1282 | /// When true, regardless of the opacity settings, the sliver child semantic |
| 1283 | /// information is exposed as if the widget were fully visible. This is |
| 1284 | /// useful in cases where labels may be hidden during animations that |
| 1285 | /// would otherwise contribute relevant semantics. |
| 1286 | final bool alwaysIncludeSemantics; |
| 1287 | |
| 1288 | @override |
| 1289 | RenderSliverOpacity createRenderObject(BuildContext context) { |
| 1290 | return RenderSliverOpacity(opacity: opacity, alwaysIncludeSemantics: alwaysIncludeSemantics); |
| 1291 | } |
| 1292 | |
| 1293 | @override |
| 1294 | void updateRenderObject(BuildContext context, RenderSliverOpacity renderObject) { |
| 1295 | renderObject |
| 1296 | ..opacity = opacity |
| 1297 | ..alwaysIncludeSemantics = alwaysIncludeSemantics; |
| 1298 | } |
| 1299 | |
| 1300 | @override |
| 1301 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 1302 | super.debugFillProperties(properties); |
| 1303 | properties.add(DiagnosticsProperty<double>('opacity' , opacity)); |
| 1304 | properties.add( |
| 1305 | FlagProperty( |
| 1306 | 'alwaysIncludeSemantics' , |
| 1307 | value: alwaysIncludeSemantics, |
| 1308 | ifTrue: 'alwaysIncludeSemantics' , |
| 1309 | ), |
| 1310 | ); |
| 1311 | } |
| 1312 | } |
| 1313 | |
| 1314 | /// A sliver widget that is invisible during hit testing. |
| 1315 | /// |
| 1316 | /// When [ignoring] is true, this widget (and its subtree) is invisible |
| 1317 | /// to hit testing. It still consumes space during layout and paints its sliver |
| 1318 | /// child as usual. It just cannot be the target of located events, because it |
| 1319 | /// returns false from [RenderSliver.hitTest]. |
| 1320 | /// |
| 1321 | /// ## Semantics |
| 1322 | /// |
| 1323 | /// Using this class may also affect how the semantics subtree underneath is |
| 1324 | /// collected. |
| 1325 | /// |
| 1326 | /// {@macro flutter.widgets.IgnorePointer.semantics} |
| 1327 | /// |
| 1328 | /// {@macro flutter.widgets.IgnorePointer.ignoringSemantics} |
| 1329 | /// |
| 1330 | /// See also: |
| 1331 | /// |
| 1332 | /// * [IgnorePointer], the equivalent widget for boxes. |
| 1333 | class SliverIgnorePointer extends SingleChildRenderObjectWidget { |
| 1334 | /// Creates a sliver widget that is invisible to hit testing. |
| 1335 | const SliverIgnorePointer({ |
| 1336 | super.key, |
| 1337 | this.ignoring = true, |
| 1338 | @Deprecated( |
| 1339 | 'Create a custom sliver ignore pointer widget instead. ' |
| 1340 | 'This feature was deprecated after v3.8.0-12.0.pre.' , |
| 1341 | ) |
| 1342 | this.ignoringSemantics, |
| 1343 | Widget? sliver, |
| 1344 | }) : super(child: sliver); |
| 1345 | |
| 1346 | /// Whether this sliver is ignored during hit testing. |
| 1347 | /// |
| 1348 | /// Regardless of whether this sliver is ignored during hit testing, it will |
| 1349 | /// still consume space during layout and be visible during painting. |
| 1350 | /// |
| 1351 | /// {@macro flutter.widgets.IgnorePointer.semantics} |
| 1352 | final bool ignoring; |
| 1353 | |
| 1354 | /// Whether the semantics of this sliver is ignored when compiling the |
| 1355 | /// semantics tree. |
| 1356 | /// |
| 1357 | /// {@macro flutter.widgets.IgnorePointer.ignoringSemantics} |
| 1358 | @Deprecated( |
| 1359 | 'Create a custom sliver ignore pointer widget instead. ' |
| 1360 | 'This feature was deprecated after v3.8.0-12.0.pre.' , |
| 1361 | ) |
| 1362 | final bool? ignoringSemantics; |
| 1363 | |
| 1364 | @override |
| 1365 | RenderSliverIgnorePointer createRenderObject(BuildContext context) { |
| 1366 | return RenderSliverIgnorePointer(ignoring: ignoring, ignoringSemantics: ignoringSemantics); |
| 1367 | } |
| 1368 | |
| 1369 | @override |
| 1370 | void updateRenderObject(BuildContext context, RenderSliverIgnorePointer renderObject) { |
| 1371 | renderObject |
| 1372 | ..ignoring = ignoring |
| 1373 | ..ignoringSemantics = ignoringSemantics; |
| 1374 | } |
| 1375 | |
| 1376 | @override |
| 1377 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 1378 | super.debugFillProperties(properties); |
| 1379 | properties.add(DiagnosticsProperty<bool>('ignoring' , ignoring)); |
| 1380 | properties.add( |
| 1381 | DiagnosticsProperty<bool>('ignoringSemantics' , ignoringSemantics, defaultValue: null), |
| 1382 | ); |
| 1383 | } |
| 1384 | } |
| 1385 | |
| 1386 | /// A sliver that lays its sliver child out as if it was in the tree, but |
| 1387 | /// without painting anything, without making the sliver child available for hit |
| 1388 | /// testing, and without taking any room in the parent. |
| 1389 | /// |
| 1390 | /// Animations continue to run in offstage sliver children, and therefore use |
| 1391 | /// battery and CPU time, regardless of whether the animations end up being |
| 1392 | /// visible. |
| 1393 | /// |
| 1394 | /// To hide a sliver widget from view while it is |
| 1395 | /// not needed, prefer removing the widget from the tree entirely rather than |
| 1396 | /// keeping it alive in an [Offstage] subtree. |
| 1397 | /// |
| 1398 | /// See also: |
| 1399 | /// |
| 1400 | /// * [Offstage], the equivalent widget for boxes. |
| 1401 | class SliverOffstage extends SingleChildRenderObjectWidget { |
| 1402 | /// Creates a sliver that visually hides its sliver child. |
| 1403 | const SliverOffstage({super.key, this.offstage = true, Widget? sliver}) : super(child: sliver); |
| 1404 | |
| 1405 | /// Whether the sliver child is hidden from the rest of the tree. |
| 1406 | /// |
| 1407 | /// If true, the sliver child is laid out as if it was in the tree, but |
| 1408 | /// without painting anything, without making the child available for hit |
| 1409 | /// testing, and without taking any room in the parent. |
| 1410 | /// |
| 1411 | /// If false, the sliver child is included in the tree as normal. |
| 1412 | final bool offstage; |
| 1413 | |
| 1414 | @override |
| 1415 | RenderSliverOffstage createRenderObject(BuildContext context) => |
| 1416 | RenderSliverOffstage(offstage: offstage); |
| 1417 | |
| 1418 | @override |
| 1419 | void updateRenderObject(BuildContext context, RenderSliverOffstage renderObject) { |
| 1420 | renderObject.offstage = offstage; |
| 1421 | } |
| 1422 | |
| 1423 | @override |
| 1424 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 1425 | super.debugFillProperties(properties); |
| 1426 | properties.add(DiagnosticsProperty<bool>('offstage' , offstage)); |
| 1427 | } |
| 1428 | |
| 1429 | @override |
| 1430 | SingleChildRenderObjectElement createElement() => _SliverOffstageElement(this); |
| 1431 | } |
| 1432 | |
| 1433 | class _SliverOffstageElement extends SingleChildRenderObjectElement { |
| 1434 | _SliverOffstageElement(SliverOffstage super.widget); |
| 1435 | |
| 1436 | @override |
| 1437 | void debugVisitOnstageChildren(ElementVisitor visitor) { |
| 1438 | if (!(widget as SliverOffstage).offstage) { |
| 1439 | super.debugVisitOnstageChildren(visitor); |
| 1440 | } |
| 1441 | } |
| 1442 | } |
| 1443 | |
| 1444 | /// Mark a child as needing to stay alive even when it's in a lazy list that |
| 1445 | /// would otherwise remove it. |
| 1446 | /// |
| 1447 | /// This widget is used in [RenderAbstractViewport]s, such as [Viewport] or |
| 1448 | /// [TwoDimensionalViewport], to manage the lifecycle of widgets that need to |
| 1449 | /// remain alive even when scrolled out of view. |
| 1450 | /// |
| 1451 | /// The [SliverChildBuilderDelegate] and [SliverChildListDelegate] delegates, |
| 1452 | /// used with [SliverList] and [SliverGrid], as well as the scroll view |
| 1453 | /// counterparts [ListView] and [GridView], have an `addAutomaticKeepAlives` |
| 1454 | /// feature, which is enabled by default. This feature inserts |
| 1455 | /// [AutomaticKeepAlive] widgets around each child, which in turn configure |
| 1456 | /// [KeepAlive] widgets in response to [KeepAliveNotification]s. |
| 1457 | /// |
| 1458 | /// The same `addAutomaticKeepAlives` feature is supported by |
| 1459 | /// [TwoDimensionalChildBuilderDelegate] and [TwoDimensionalChildListDelegate]. |
| 1460 | /// |
| 1461 | /// Keep-alive behavior can be managed by using [KeepAlive] directly or by |
| 1462 | /// relying on notifications. For convenience, [AutomaticKeepAliveClientMixin] |
| 1463 | /// may be mixed into a [State] subclass. Further details are available in the |
| 1464 | /// documentation for [AutomaticKeepAliveClientMixin]. |
| 1465 | /// |
| 1466 | /// {@tool dartpad} |
| 1467 | /// This sample demonstrates how to use the [KeepAlive] widget |
| 1468 | /// to preserve the state of individual list items in a [ListView] when they are |
| 1469 | /// scrolled out of view. |
| 1470 | /// |
| 1471 | /// By default, [ListView.builder] only keeps the widgets currently visible in |
| 1472 | /// the viewport alive. When an item scrolls out of view, it may be disposed to |
| 1473 | /// free up resources. This can cause the state of [StatefulWidget]s to be lost |
| 1474 | /// if not explicitly preserved. |
| 1475 | /// |
| 1476 | /// In this example, each item in the list is a [StatefulWidget] that maintains |
| 1477 | /// a counter. Tapping the "+" button increments the counter. To selectively |
| 1478 | /// preserve the state, each item is wrapped in a [KeepAlive] widget, with the |
| 1479 | /// keepAlive parameter set based on the item’s index: |
| 1480 | /// |
| 1481 | /// - For even-indexed items, `keepAlive: true`, so their state is preserved |
| 1482 | /// even when scrolled off-screen. |
| 1483 | /// - For odd-indexed items, `keepAlive: false`, so their state is discarded |
| 1484 | /// when they are no longer visible. |
| 1485 | /// |
| 1486 | /// ** See code in examples/api/lib/widgets/keep_alive/keep_alive.0.dart ** |
| 1487 | /// {@end-tool} |
| 1488 | /// |
| 1489 | /// See also: |
| 1490 | /// |
| 1491 | /// * [AutomaticKeepAlive], which allows subtrees to request to be kept alive |
| 1492 | /// in lazy lists. |
| 1493 | /// * [AutomaticKeepAliveClientMixin], which is a mixin with convenience |
| 1494 | /// methods for clients of [AutomaticKeepAlive]. Used with [State] |
| 1495 | /// subclasses. |
| 1496 | class KeepAlive extends ParentDataWidget<KeepAliveParentDataMixin> { |
| 1497 | /// Marks a child as needing to remain alive. |
| 1498 | const KeepAlive({super.key, required this.keepAlive, required super.child}); |
| 1499 | |
| 1500 | /// Whether to keep the child alive. |
| 1501 | /// |
| 1502 | /// If this is false, it is as if this widget was omitted. |
| 1503 | final bool keepAlive; |
| 1504 | |
| 1505 | @override |
| 1506 | void applyParentData(RenderObject renderObject) { |
| 1507 | assert(renderObject.parentData is KeepAliveParentDataMixin); |
| 1508 | final KeepAliveParentDataMixin parentData = |
| 1509 | renderObject.parentData! as KeepAliveParentDataMixin; |
| 1510 | if (parentData.keepAlive != keepAlive) { |
| 1511 | // No need to redo layout if it became true. |
| 1512 | parentData.keepAlive = keepAlive; |
| 1513 | if (!keepAlive) { |
| 1514 | renderObject.parent?.markNeedsLayout(); |
| 1515 | } |
| 1516 | } |
| 1517 | } |
| 1518 | |
| 1519 | // We only return true if [keepAlive] is true, because turning _off_ keep |
| 1520 | // alive requires a layout to do the garbage collection (but turning it on |
| 1521 | // requires nothing, since by definition the widget is already alive and won't |
| 1522 | // go away _unless_ we do a layout). |
| 1523 | @override |
| 1524 | bool debugCanApplyOutOfTurn() => keepAlive; |
| 1525 | |
| 1526 | @override |
| 1527 | Type get debugTypicalAncestorWidgetClass => throw FlutterError( |
| 1528 | 'Multiple Types are supported, use debugTypicalAncestorWidgetDescription.' , |
| 1529 | ); |
| 1530 | |
| 1531 | @override |
| 1532 | String get debugTypicalAncestorWidgetDescription => |
| 1533 | 'SliverWithKeepAliveWidget or TwoDimensionalViewport' ; |
| 1534 | |
| 1535 | @override |
| 1536 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 1537 | super.debugFillProperties(properties); |
| 1538 | properties.add(DiagnosticsProperty<bool>('keepAlive' , keepAlive)); |
| 1539 | } |
| 1540 | } |
| 1541 | |
| 1542 | /// A sliver that constrains the cross axis extent of its sliver child. |
| 1543 | /// |
| 1544 | /// The [SliverConstrainedCrossAxis] takes a [maxExtent] parameter and uses it as |
| 1545 | /// the cross axis extent of the [SliverConstraints] passed to the sliver child. |
| 1546 | /// The widget ensures that the [maxExtent] is a nonnegative value. |
| 1547 | /// |
| 1548 | /// This is useful when you want to apply a custom cross-axis extent constraint |
| 1549 | /// to a sliver child, as slivers typically consume the full cross axis extent. |
| 1550 | /// |
| 1551 | /// This widget also sets its parent data's [SliverPhysicalParentData.crossAxisFlex] |
| 1552 | /// to 0, so that it informs [SliverCrossAxisGroup] that it should not flex |
| 1553 | /// in the cross axis direction. |
| 1554 | /// |
| 1555 | /// {@tool dartpad} |
| 1556 | /// In this sample the [SliverConstrainedCrossAxis] sizes its child so that the |
| 1557 | /// cross axis extent takes up less space than the actual viewport. |
| 1558 | /// |
| 1559 | /// ** See code in examples/api/lib/widgets/sliver/sliver_constrained_cross_axis.0.dart ** |
| 1560 | /// {@end-tool} |
| 1561 | /// |
| 1562 | /// See also: |
| 1563 | /// |
| 1564 | /// * [SliverCrossAxisGroup], the widget which makes use of 0 flex factor set by |
| 1565 | /// this widget. |
| 1566 | class SliverConstrainedCrossAxis extends StatelessWidget { |
| 1567 | /// Creates a sliver that constrains the cross axis extent of its sliver child. |
| 1568 | /// |
| 1569 | /// The [maxExtent] parameter is required and must be nonnegative. |
| 1570 | const SliverConstrainedCrossAxis({super.key, required this.maxExtent, required this.sliver}); |
| 1571 | |
| 1572 | /// The cross axis extent to apply to the sliver child. |
| 1573 | /// |
| 1574 | /// This value must be nonnegative. |
| 1575 | final double maxExtent; |
| 1576 | |
| 1577 | /// The widget below this widget in the tree. |
| 1578 | /// |
| 1579 | /// Must be a sliver. |
| 1580 | final Widget sliver; |
| 1581 | |
| 1582 | @override |
| 1583 | Widget build(BuildContext context) { |
| 1584 | return _SliverZeroFlexParentDataWidget( |
| 1585 | sliver: _SliverConstrainedCrossAxis(maxExtent: maxExtent, sliver: sliver), |
| 1586 | ); |
| 1587 | } |
| 1588 | } |
| 1589 | |
| 1590 | class _SliverZeroFlexParentDataWidget extends ParentDataWidget<SliverPhysicalParentData> { |
| 1591 | const _SliverZeroFlexParentDataWidget({required Widget sliver}) : super(child: sliver); |
| 1592 | |
| 1593 | @override |
| 1594 | void applyParentData(RenderObject renderObject) { |
| 1595 | assert(renderObject.parentData is SliverPhysicalParentData); |
| 1596 | final SliverPhysicalParentData parentData = |
| 1597 | renderObject.parentData! as SliverPhysicalParentData; |
| 1598 | bool needsLayout = false; |
| 1599 | if (parentData.crossAxisFlex != 0) { |
| 1600 | parentData.crossAxisFlex = 0; |
| 1601 | needsLayout = true; |
| 1602 | } |
| 1603 | |
| 1604 | if (needsLayout) { |
| 1605 | renderObject.parent?.markNeedsLayout(); |
| 1606 | } |
| 1607 | } |
| 1608 | |
| 1609 | @override |
| 1610 | Type get debugTypicalAncestorWidgetClass => SliverCrossAxisGroup; |
| 1611 | } |
| 1612 | |
| 1613 | class _SliverConstrainedCrossAxis extends SingleChildRenderObjectWidget { |
| 1614 | const _SliverConstrainedCrossAxis({required this.maxExtent, required Widget sliver}) |
| 1615 | : assert(maxExtent >= 0.0), |
| 1616 | super(child: sliver); |
| 1617 | |
| 1618 | /// The cross axis extent to apply to the sliver child. |
| 1619 | /// |
| 1620 | /// This value must be nonnegative. |
| 1621 | final double maxExtent; |
| 1622 | |
| 1623 | @override |
| 1624 | RenderSliverConstrainedCrossAxis createRenderObject(BuildContext context) { |
| 1625 | return RenderSliverConstrainedCrossAxis(maxExtent: maxExtent); |
| 1626 | } |
| 1627 | |
| 1628 | @override |
| 1629 | void updateRenderObject(BuildContext context, RenderSliverConstrainedCrossAxis renderObject) { |
| 1630 | renderObject.maxExtent = maxExtent; |
| 1631 | } |
| 1632 | } |
| 1633 | |
| 1634 | /// Set a flex factor for allocating space in the cross axis direction. |
| 1635 | /// |
| 1636 | /// This is a [ParentDataWidget] to be used in [SliverCrossAxisGroup]. |
| 1637 | /// After all slivers with null or zero flex (e.g. [SliverConstrainedCrossAxis]) |
| 1638 | /// are laid out (which should determine their own [SliverGeometry.crossAxisExtent]), |
| 1639 | /// the remaining space is laid out among the slivers with nonzero flex |
| 1640 | /// proportionally to their flex value. |
| 1641 | class SliverCrossAxisExpanded extends ParentDataWidget<SliverPhysicalContainerParentData> { |
| 1642 | /// Creates an object that assigns a [flex] value to the child sliver. |
| 1643 | /// |
| 1644 | /// The provided [flex] value must be greater than 0. |
| 1645 | const SliverCrossAxisExpanded({super.key, required this.flex, required Widget sliver}) |
| 1646 | : assert(flex > 0 && flex < double.infinity), |
| 1647 | super(child: sliver); |
| 1648 | |
| 1649 | /// Flex value for allocating cross axis extent left after laying out the children with |
| 1650 | /// constrained cross axis. The children with flex values will have the remaining extent |
| 1651 | /// allocated proportionally to their flex value. This must an integer between |
| 1652 | /// 0 and infinity, exclusive. |
| 1653 | final int flex; |
| 1654 | |
| 1655 | @override |
| 1656 | void applyParentData(RenderObject renderObject) { |
| 1657 | assert(renderObject.parentData is SliverPhysicalContainerParentData); |
| 1658 | assert(renderObject.parent is RenderSliverCrossAxisGroup); |
| 1659 | final SliverPhysicalParentData parentData = |
| 1660 | renderObject.parentData! as SliverPhysicalParentData; |
| 1661 | bool needsLayout = false; |
| 1662 | |
| 1663 | if (parentData.crossAxisFlex != flex) { |
| 1664 | parentData.crossAxisFlex = flex; |
| 1665 | needsLayout = true; |
| 1666 | } |
| 1667 | |
| 1668 | if (needsLayout) { |
| 1669 | renderObject.parent?.markNeedsLayout(); |
| 1670 | } |
| 1671 | } |
| 1672 | |
| 1673 | @override |
| 1674 | Type get debugTypicalAncestorWidgetClass => SliverCrossAxisGroup; |
| 1675 | } |
| 1676 | |
| 1677 | /// A sliver that places multiple sliver children in a linear array along |
| 1678 | /// the cross axis. |
| 1679 | /// |
| 1680 | /// ## Layout algorithm |
| 1681 | /// |
| 1682 | /// _This section describes how the framework causes [RenderSliverCrossAxisGroup] |
| 1683 | /// to position its children._ |
| 1684 | /// |
| 1685 | /// Layout for a [RenderSliverCrossAxisGroup] has four steps: |
| 1686 | /// |
| 1687 | /// 1. Layout each child with a null or zero flex factor with cross axis constraint |
| 1688 | /// being whatever cross axis space is remaining after laying out any previous |
| 1689 | /// sliver. Slivers with null or zero flex factor should determine their own |
| 1690 | /// [SliverGeometry.crossAxisExtent]. For example, the [SliverConstrainedCrossAxis] |
| 1691 | /// widget uses either [SliverConstrainedCrossAxis.maxExtent] or |
| 1692 | /// [SliverConstraints.crossAxisExtent], deciding between whichever is smaller. |
| 1693 | /// 2. Divide up the remaining cross axis space among the children with non-zero flex |
| 1694 | /// factors according to their flex factor. For example, a child with a flex |
| 1695 | /// factor of 2.0 will receive twice the amount of cross axis space as a child |
| 1696 | /// with a flex factor 1.0. |
| 1697 | /// 3. Layout each of the remaining children with the cross axis constraint |
| 1698 | /// allocated in the previous step. |
| 1699 | /// 4. Set the geometry to that of whichever child has the longest |
| 1700 | /// [SliverGeometry.scrollExtent] with the [SliverGeometry.crossAxisExtent] adjusted |
| 1701 | /// to [SliverConstraints.crossAxisExtent]. |
| 1702 | /// |
| 1703 | /// {@tool dartpad} |
| 1704 | /// In this sample the [SliverCrossAxisGroup] sizes its three [children] so that |
| 1705 | /// the first normal [SliverList] has a flex factor of 1, the second [SliverConstrainedCrossAxis] |
| 1706 | /// has a flex factor of 0 and a maximum cross axis extent of 200.0, and the third |
| 1707 | /// [SliverCrossAxisExpanded] has a flex factor of 2. |
| 1708 | /// |
| 1709 | /// ** See code in examples/api/lib/widgets/sliver/sliver_cross_axis_group.0.dart ** |
| 1710 | /// {@end-tool} |
| 1711 | /// |
| 1712 | /// See also: |
| 1713 | /// |
| 1714 | /// * [SliverCrossAxisExpanded], which is the [ParentDataWidget] for setting a flex |
| 1715 | /// value to a widget. |
| 1716 | /// * [SliverConstrainedCrossAxis], which is a [RenderObjectWidget] for setting |
| 1717 | /// an extent to constrain the widget to. |
| 1718 | /// * [SliverMainAxisGroup], which is the [RenderObjectWidget] for laying out |
| 1719 | /// multiple slivers along the main axis. |
| 1720 | class SliverCrossAxisGroup extends MultiChildRenderObjectWidget { |
| 1721 | /// Creates a sliver that places sliver children in a linear array along |
| 1722 | /// the cross axis. |
| 1723 | const SliverCrossAxisGroup({super.key, required List<Widget> slivers}) : super(children: slivers); |
| 1724 | |
| 1725 | @override |
| 1726 | RenderSliverCrossAxisGroup createRenderObject(BuildContext context) { |
| 1727 | return RenderSliverCrossAxisGroup(); |
| 1728 | } |
| 1729 | } |
| 1730 | |
| 1731 | /// A sliver that places multiple sliver children in a linear array along |
| 1732 | /// the main axis, one after another. |
| 1733 | /// |
| 1734 | /// ## Layout algorithm |
| 1735 | /// |
| 1736 | /// _This section describes how the framework causes [RenderSliverMainAxisGroup] |
| 1737 | /// to position its children._ |
| 1738 | /// |
| 1739 | /// Layout for a [RenderSliverMainAxisGroup] has four steps: |
| 1740 | /// |
| 1741 | /// 1. Keep track of an offset variable which is the total [SliverGeometry.scrollExtent] |
| 1742 | /// of the slivers laid out so far. |
| 1743 | /// 2. To determine the constraints for the next sliver child to layout, calculate the |
| 1744 | /// amount of paint extent occupied from 0.0 to the offset variable and subtract this from |
| 1745 | /// [SliverConstraints.remainingPaintExtent] minus to use as the child's |
| 1746 | /// [SliverConstraints.remainingPaintExtent]. For the [SliverConstraints.scrollOffset], |
| 1747 | /// take the provided constraint's value and subtract out the offset variable, using |
| 1748 | /// 0.0 if negative. |
| 1749 | /// 3. Once we finish laying out all the slivers, this offset variable represents |
| 1750 | /// the total [SliverGeometry.scrollExtent] of the sliver group. Since it is possible |
| 1751 | /// for specialized slivers to try to paint itself outside of the bounds of the |
| 1752 | /// sliver group's scroll extent (see [SliverPersistentHeader]), we must do a |
| 1753 | /// second pass to set a [SliverPhysicalParentData.paintOffset] to make sure it |
| 1754 | /// is within the bounds of the sliver group. |
| 1755 | /// 4. Finally, set the [RenderSliverMainAxisGroup.geometry] with the total |
| 1756 | /// [SliverGeometry.scrollExtent], [SliverGeometry.paintExtent] calculated from |
| 1757 | /// the constraints and [SliverGeometry.scrollExtent], and [SliverGeometry.maxPaintExtent]. |
| 1758 | /// |
| 1759 | /// {@tool dartpad} |
| 1760 | /// In this sample the [CustomScrollView] renders a [SliverMainAxisGroup] and a |
| 1761 | /// [SliverToBoxAdapter] with some content. The [SliverMainAxisGroup] renders a |
| 1762 | /// [SliverAppBar], [SliverList], and [SliverToBoxAdapter]. Notice that when the |
| 1763 | /// [SliverMainAxisGroup] goes out of view, so does the pinned [SliverAppBar]. |
| 1764 | /// |
| 1765 | /// ** See code in examples/api/lib/widgets/sliver/sliver_main_axis_group.0.dart ** |
| 1766 | /// {@end-tool} |
| 1767 | /// |
| 1768 | /// See also: |
| 1769 | /// |
| 1770 | /// * [SliverPersistentHeader], which is a [RenderObjectWidget] which may require |
| 1771 | /// adjustment to its [SliverPhysicalParentData.paintOffset] to make it fit |
| 1772 | /// within the computed [SliverGeometry.scrollExtent] of the [SliverMainAxisGroup]. |
| 1773 | /// * [SliverCrossAxisGroup], which is the [RenderObjectWidget] for laying out |
| 1774 | /// multiple slivers along the cross axis. |
| 1775 | class SliverMainAxisGroup extends MultiChildRenderObjectWidget { |
| 1776 | /// Creates a sliver that places sliver children in a linear array along |
| 1777 | /// the main axis. |
| 1778 | const SliverMainAxisGroup({super.key, required List<Widget> slivers}) : super(children: slivers); |
| 1779 | |
| 1780 | @override |
| 1781 | MultiChildRenderObjectElement createElement() => _SliverMainAxisGroupElement(this); |
| 1782 | |
| 1783 | @override |
| 1784 | RenderSliverMainAxisGroup createRenderObject(BuildContext context) { |
| 1785 | return RenderSliverMainAxisGroup(); |
| 1786 | } |
| 1787 | } |
| 1788 | |
| 1789 | class _SliverMainAxisGroupElement extends MultiChildRenderObjectElement { |
| 1790 | _SliverMainAxisGroupElement(SliverMainAxisGroup super.widget); |
| 1791 | |
| 1792 | @override |
| 1793 | void debugVisitOnstageChildren(ElementVisitor visitor) { |
| 1794 | children |
| 1795 | .where((Element e) { |
| 1796 | final RenderSliver renderSliver = e.renderObject! as RenderSliver; |
| 1797 | return renderSliver.geometry!.visible; |
| 1798 | }) |
| 1799 | .forEach(visitor); |
| 1800 | } |
| 1801 | } |
| 1802 | |
| 1803 | /// A sliver that ensures its sliver child is included in the semantics tree. |
| 1804 | /// |
| 1805 | /// This sliver ensures that its child sliver is still visited by the [RenderViewport] |
| 1806 | /// when constructing the semantics tree, and is not clipped out of the semantics tree by |
| 1807 | /// the [RenderViewport] when it is outside the current viewport and outside the cache extent. |
| 1808 | /// |
| 1809 | /// The child sliver may still be excluded from the semantics tree if its [RenderSliver] does |
| 1810 | /// not provide a valid [RenderSliver.semanticBounds]. This sliver does not guarantee its |
| 1811 | /// child sliver is laid out. |
| 1812 | /// |
| 1813 | /// Be mindful when positioning [SliverEnsureSemantics] in a [CustomScrollView] after slivers that build |
| 1814 | /// their children lazily, like [SliverList]. Lazy slivers might underestimate the total scrollable size (scroll |
| 1815 | /// extent) before the [SliverEnsureSemantics] widget. This inaccuracy can cause problems for assistive |
| 1816 | /// technologies (e.g., screen readers), which rely on a correct scroll extent to navigate properly; they |
| 1817 | /// might fail to scroll accurately to the content wrapped by [SliverEnsureSemantics]. |
| 1818 | /// |
| 1819 | /// To avoid this potential issue and ensure the scroll extent is calculated accurately up to this sliver, |
| 1820 | /// it's recommended to use slivers that can determine their extent precisely beforehand. Instead of |
| 1821 | /// [SliverList], consider using [SliverFixedExtentList], [SliverVariedExtentList], or |
| 1822 | /// [SliverPrototypeExtentList]. If using [SliverGrid], ensure it employs a delegate such as |
| 1823 | /// [SliverGridDelegateWithFixedCrossAxisCount] or [SliverGridDelegateWithMaxCrossAxisExtent]. |
| 1824 | /// Using these alternatives guarantees that the scrollable area's size is known accurately, allowing |
| 1825 | /// assistive technologies to function correctly with [SliverEnsureSemantics]. |
| 1826 | /// |
| 1827 | /// {@tool dartpad} |
| 1828 | /// This example shows how to use [SliverEnsureSemantics] to keep certain headers and lists |
| 1829 | /// available to assistive technologies while they are outside the current viewport and cache extent. |
| 1830 | /// |
| 1831 | /// ** See code in examples/api/lib/widgets/sliver/sliver_ensure_semantics.0.dart ** |
| 1832 | /// {@end-tool} |
| 1833 | // TODO(Renzo-Olivares): Investigate potential solutions for revealing off screen items, https://github.com/flutter/flutter/issues/166703. |
| 1834 | class SliverEnsureSemantics extends SingleChildRenderObjectWidget { |
| 1835 | /// Creates a sliver that ensures its sliver child is included in the semantics tree. |
| 1836 | const SliverEnsureSemantics({super.key, required Widget sliver}) : super(child: sliver); |
| 1837 | |
| 1838 | @override |
| 1839 | RenderObject createRenderObject(BuildContext context) => _RenderSliverEnsureSemantics(); |
| 1840 | } |
| 1841 | |
| 1842 | /// Ensures its sliver child is included in the semantics tree. |
| 1843 | class _RenderSliverEnsureSemantics extends RenderProxySliver { |
| 1844 | @override |
| 1845 | bool get ensureSemantics => true; |
| 1846 | } |
| 1847 | |