scroll_view.dart 77.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Adam Barth's avatar
Adam Barth committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:math' as math;

Adam Barth's avatar
Adam Barth committed
7
import 'package:flutter/rendering.dart';
8
import 'package:flutter/gestures.dart';
Adam Barth's avatar
Adam Barth committed
9 10

import 'basic.dart';
11
import 'debug.dart';
12 13
import 'focus_manager.dart';
import 'focus_scope.dart';
14
import 'framework.dart';
15
import 'media_query.dart';
16
import 'notification_listener.dart';
17
import 'primary_scroll_controller.dart';
18
import 'scroll_controller.dart';
19
import 'scroll_notification.dart';
20
import 'scroll_physics.dart';
Adam Barth's avatar
Adam Barth committed
21 22
import 'scrollable.dart';
import 'sliver.dart';
23
import 'viewport.dart';
Adam Barth's avatar
Adam Barth committed
24

25
// Examples can assume:
26
// late int itemCount;
27

28 29 30
/// A representation of how a [ScrollView] should dismiss the on-screen
/// keyboard.
enum ScrollViewKeyboardDismissBehavior {
31
  /// `manual` means there is no automatic dismissal of the on-screen keyboard.
32 33 34 35 36 37 38
  /// It is up to the client to dismiss the keyboard.
  manual,
  /// `onDrag` means that the [ScrollView] will dismiss an on-screen keyboard
  /// when a drag begins.
  onDrag,
}

39 40 41 42 43 44 45 46 47 48 49 50 51
/// A widget that scrolls.
///
/// Scrollable widgets consist of three pieces:
///
///  1. A [Scrollable] widget, which listens for various user gestures and
///     implements the interaction design for scrolling.
///  2. A viewport widget, such as [Viewport] or [ShrinkWrappingViewport], which
///     implements the visual design for scrolling by displaying only a portion
///     of the widgets inside the scroll view.
///  3. One or more slivers, which are widgets that can be composed to created
///     various scrolling effects, such as lists, grids, and expanding headers.
///
/// [ScrollView] helps orchestrate these pieces by creating the [Scrollable] and
52
/// the viewport and deferring to its subclass to create the slivers.
53
///
54 55 56
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
57 58 59 60 61 62 63 64 65 66
/// See also:
///
///  * [ListView], which is a commonly used [ScrollView] that displays a
///    scrolling, linear list of child widgets.
///  * [PageView], which is a scrolling list of child widgets that are each the
///    size of the viewport.
///  * [GridView], which is a [ScrollView] that displays a scrolling, 2D array
///    of child widgets.
///  * [CustomScrollView], which is a [ScrollView] that creates custom scroll
///    effects using slivers.
67
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
68
///    the scroll position without using a [ScrollController].
69
abstract class ScrollView extends StatelessWidget {
70 71 72
  /// Creates a widget that scrolls.
  ///
  /// If the [primary] argument is true, the [controller] must be null.
73 74 75 76 77 78
  ///
  /// If the [shrinkWrap] argument is true, the [center] argument must be null.
  ///
  /// The [scrollDirection], [reverse], and [shrinkWrap] arguments must not be null.
  ///
  /// The [anchor] argument must be non-null and in the range 0.0 to 1.0.
79
  const ScrollView({
80
    Key? key,
81 82
    this.scrollDirection = Axis.vertical,
    this.reverse = false,
83
    this.controller,
84 85
    bool? primary,
    ScrollPhysics? physics,
86
    this.shrinkWrap = false,
87 88
    this.center,
    this.anchor = 0.0,
89
    this.cacheExtent,
90
    this.semanticChildCount,
91
    this.dragStartBehavior = DragStartBehavior.start,
92
    this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
93
    this.restorationId,
94
    this.clipBehavior = Clip.hardEdge,
95 96
  }) : assert(scrollDirection != null),
       assert(reverse != null),
97
       assert(shrinkWrap != null),
98
       assert(dragStartBehavior != null),
99
       assert(clipBehavior != null),
100
       assert(!(controller != null && primary == true),
101
           'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. '
102
           'You cannot both set primary to true and pass an explicit controller.'
103
       ),
104 105 106
       assert(!shrinkWrap || center == null),
       assert(anchor != null),
       assert(anchor >= 0.0 && anchor <= 1.0),
107
       assert(semanticChildCount == null || semanticChildCount >= 0),
108 109
       primary = primary ?? controller == null && identical(scrollDirection, Axis.vertical),
       physics = physics ?? (primary == true || (primary == null && controller == null && identical(scrollDirection, Axis.vertical)) ? const AlwaysScrollableScrollPhysics() : null),
110
       super(key: key);
Adam Barth's avatar
Adam Barth committed
111

112
  /// {@template flutter.widgets.scroll_view.scrollDirection}
113 114 115
  /// The axis along which the scroll view scrolls.
  ///
  /// Defaults to [Axis.vertical].
116
  /// {@endtemplate}
Adam Barth's avatar
Adam Barth committed
117 118
  final Axis scrollDirection;

119
  /// {@template flutter.widgets.scroll_view.reverse}
120
  /// Whether the scroll view scrolls in the reading direction.
121 122 123 124 125 126
  ///
  /// For example, if the reading direction is left-to-right and
  /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from
  /// left to right when [reverse] is false and from right to left when
  /// [reverse] is true.
  ///
Adam Barth's avatar
Adam Barth committed
127
  /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
128 129 130 131
  /// scrolls from top to bottom when [reverse] is false and from bottom to top
  /// when [reverse] is true.
  ///
  /// Defaults to false.
132
  /// {@endtemplate}
133 134
  final bool reverse;

135
  /// {@template flutter.widgets.scroll_view.controller}
136 137 138 139
  /// An object that can be used to control the position to which this scroll
  /// view is scrolled.
  ///
  /// Must be null if [primary] is true.
140 141 142 143 144 145 146 147
  ///
  /// A [ScrollController] serves several purposes. It can be used to control
  /// the initial scroll position (see [ScrollController.initialScrollOffset]).
  /// It can be used to control whether the scroll view should automatically
  /// save and restore its scroll position in the [PageStorage] (see
  /// [ScrollController.keepScrollOffset]). It can be used to read the current
  /// scroll position (see [ScrollController.offset]), or change it (see
  /// [ScrollController.animateTo]).
148
  /// {@endtemplate}
149
  final ScrollController? controller;
150

151
  /// {@template flutter.widgets.scroll_view.primary}
152 153 154
  /// Whether this is the primary scroll view associated with the parent
  /// [PrimaryScrollController].
  ///
155 156 157 158
  /// When this is true, the scroll view is scrollable even if it does not have
  /// sufficient content to actually scroll. Otherwise, by default the user can
  /// only scroll the view if it has sufficient content. See [physics].
  ///
159 160 161 162 163
  /// Also when true, the scroll view is used for default [ScrollAction]s. If a
  /// ScrollAction is not handled by an otherwise focused part of the application,
  /// the ScrollAction will be evaluated using this scroll view, for example,
  /// when executing [Shortcuts] key events like page up and down.
  ///
164
  /// On iOS, this also identifies the scroll view that will scroll to top in
165
  /// response to a tap in the status bar.
166
  /// {@endtemplate}
167
  ///
168 169
  /// Defaults to true when [scrollDirection] is [Axis.vertical] and
  /// [controller] is null.
170 171
  final bool primary;

172
  /// {@template flutter.widgets.scroll_view.physics}
173 174 175 176 177
  /// How the scroll view should respond to user input.
  ///
  /// For example, determines how the scroll view continues to animate after the
  /// user stops dragging the scroll view.
  ///
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
  /// Defaults to matching platform conventions. Furthermore, if [primary] is
  /// false, then the user cannot scroll if there is insufficient content to
  /// scroll, while if [primary] is true, they can always attempt to scroll.
  ///
  /// To force the scroll view to always be scrollable even if there is
  /// insufficient content, as if [primary] was true but without necessarily
  /// setting it to true, provide an [AlwaysScrollableScrollPhysics] physics
  /// object, as in:
  ///
  /// ```dart
  ///   physics: const AlwaysScrollableScrollPhysics(),
  /// ```
  ///
  /// To force the scroll view to use the default platform conventions and not
  /// be scrollable if there is insufficient content, regardless of the value of
  /// [primary], provide an explicit [ScrollPhysics] object, as in:
  ///
  /// ```dart
  ///   physics: const ScrollPhysics(),
  /// ```
198 199 200 201 202 203 204 205 206
  ///
  /// The physics can be changed dynamically (by providing a new object in a
  /// subsequent build), but new physics will only take effect if the _class_ of
  /// the provided object changes. Merely constructing a new instance with a
  /// different configuration is insufficient to cause the physics to be
  /// reapplied. (This is because the final object used is generated
  /// dynamically, which can be relatively expensive, and it would be
  /// inefficient to speculatively create this object each frame to see if the
  /// physics should be updated.)
207
  /// {@endtemplate}
208
  final ScrollPhysics? physics;
Adam Barth's avatar
Adam Barth committed
209

210
  /// {@template flutter.widgets.scroll_view.shrinkWrap}
211 212 213 214 215 216 217 218 219 220 221 222 223 224
  /// Whether the extent of the scroll view in the [scrollDirection] should be
  /// determined by the contents being viewed.
  ///
  /// If the scroll view does not shrink wrap, then the scroll view will expand
  /// to the maximum allowed size in the [scrollDirection]. If the scroll view
  /// has unbounded constraints in the [scrollDirection], then [shrinkWrap] must
  /// be true.
  ///
  /// Shrink wrapping the content of the scroll view is significantly more
  /// expensive than expanding to the maximum allowed size because the content
  /// can expand and contract during scrolling, which means the size of the
  /// scroll view needs to be recomputed whenever the scroll position changes.
  ///
  /// Defaults to false.
225
  /// {@endtemplate}
226 227
  final bool shrinkWrap;

228 229
  /// The first child in the [GrowthDirection.forward] growth direction.
  ///
230 231 232 233 234
  /// Children after [center] will be placed in the [AxisDirection] determined
  /// by [scrollDirection] and [reverse] relative to the [center]. Children
  /// before [center] will be placed in the opposite of the axis direction
  /// relative to the [center]. This makes the [center] the inflection point of
  /// the growth direction.
235 236 237 238 239 240 241 242 243 244
  ///
  /// The [center] must be the key of one of the slivers built by [buildSlivers].
  ///
  /// Of the built-in subclasses of [ScrollView], only [CustomScrollView]
  /// supports [center]; for that class, the given key must be the key of one of
  /// the slivers in the [CustomScrollView.slivers] list.
  ///
  /// See also:
  ///
  ///  * [anchor], which controls where the [center] as aligned in the viewport.
245
  final Key? center;
246

247
  /// {@template flutter.widgets.scroll_view.anchor}
248 249
  /// The relative position of the zero scroll offset.
  ///
250 251 252 253 254 255
  /// For example, if [anchor] is 0.5 and the [AxisDirection] determined by
  /// [scrollDirection] and [reverse] is [AxisDirection.down] or
  /// [AxisDirection.up], then the zero scroll offset is vertically centered
  /// within the viewport. If the [anchor] is 1.0, and the axis direction is
  /// [AxisDirection.right], then the zero scroll offset is on the left edge of
  /// the viewport.
256
  /// {@endtemplate}
257 258
  final double anchor;

259
  /// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
260
  final double? cacheExtent;
261

262 263 264 265
  /// The number of children that will contribute semantic information.
  ///
  /// Some subtypes of [ScrollView] can infer this value automatically. For
  /// example [ListView] will use the number of widgets in the child list,
266
  /// while the [ListView.separated] constructor will use half that amount.
267 268 269 270 271 272 273 274
  ///
  /// For [CustomScrollView] and other types which do not receive a builder
  /// or list of widgets, the child count must be explicitly provided. If the
  /// number is unknown or unbounded this should be left unset or set to null.
  ///
  /// See also:
  ///
  ///  * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property.
275
  final int? semanticChildCount;
276

277 278 279
  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
  final DragStartBehavior dragStartBehavior;

280
  /// {@template flutter.widgets.scroll_view.keyboardDismissBehavior}
281 282
  /// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will
  /// dismiss the keyboard automatically.
283
  /// {@endtemplate}
284 285
  final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;

286
  /// {@macro flutter.widgets.scrollable.restorationId}
287
  final String? restorationId;
288

289
  /// {@macro flutter.material.Material.clipBehavior}
290 291 292 293
  ///
  /// Defaults to [Clip.hardEdge].
  final Clip clipBehavior;

294 295 296 297 298
  /// Returns the [AxisDirection] in which the scroll view scrolls.
  ///
  /// Combines the [scrollDirection] with the [reverse] boolean to obtain the
  /// concrete [AxisDirection].
  ///
299
  /// If the [scrollDirection] is [Axis.horizontal], the ambient
300
  /// [Directionality] is also considered when selecting the concrete
301 302 303 304
  /// [AxisDirection]. For example, if the ambient [Directionality] is
  /// [TextDirection.rtl], then the non-reversed [AxisDirection] is
  /// [AxisDirection.left] and the reversed [AxisDirection] is
  /// [AxisDirection.right].
305 306
  @protected
  AxisDirection getDirection(BuildContext context) {
307
    return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
308 309
  }

310 311
  /// Build the list of widgets to place inside the viewport.
  ///
312 313
  /// Subclasses should override this method to build the slivers for the inside
  /// of the viewport.
314
  @protected
315
  List<Widget> buildSlivers(BuildContext context);
Adam Barth's avatar
Adam Barth committed
316

317 318 319 320 321
  /// Build the viewport.
  ///
  /// Subclasses may override this method to change how the viewport is built.
  /// The default implementation uses a [ShrinkWrappingViewport] if [shrinkWrap]
  /// is true, and a regular [Viewport] otherwise.
322 323 324 325 326 327 328 329
  ///
  /// The `offset` argument is the value obtained from
  /// [Scrollable.viewportBuilder].
  ///
  /// The `axisDirection` argument is the value obtained from [getDirection],
  /// which by default uses [scrollDirection] and [reverse].
  ///
  /// The `slivers` argument is the value obtained from [buildSlivers].
330 331 332 333 334 335 336
  @protected
  Widget buildViewport(
    BuildContext context,
    ViewportOffset offset,
    AxisDirection axisDirection,
    List<Widget> slivers,
  ) {
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
    assert(() {
      switch (axisDirection) {
        case AxisDirection.up:
        case AxisDirection.down:
          return debugCheckHasDirectionality(
            context,
            why: 'to determine the cross-axis direction of the scroll view',
            hint: 'Vertical scroll views create Viewport widgets that try to determine their cross axis direction '
                  'from the ambient Directionality.',
          );
        case AxisDirection.left:
        case AxisDirection.right:
          return true;
      }
    }());
352
    if (shrinkWrap) {
353
      return ShrinkWrappingViewport(
354 355 356
        axisDirection: axisDirection,
        offset: offset,
        slivers: slivers,
357
        clipBehavior: clipBehavior,
358 359
      );
    }
360
    return Viewport(
361 362 363
      axisDirection: axisDirection,
      offset: offset,
      slivers: slivers,
364
      cacheExtent: cacheExtent,
365 366
      center: center,
      anchor: anchor,
367
      clipBehavior: clipBehavior,
368 369 370
    );
  }

371 372
  @override
  Widget build(BuildContext context) {
373 374
    final List<Widget> slivers = buildSlivers(context);
    final AxisDirection axisDirection = getDirection(context);
375

376
    final ScrollController? scrollController =
377
        primary ? PrimaryScrollController.of(context) : controller;
378
    final Scrollable scrollable = Scrollable(
379
      dragStartBehavior: dragStartBehavior,
380
      axisDirection: axisDirection,
381
      controller: scrollController,
Adam Barth's avatar
Adam Barth committed
382
      physics: physics,
383
      semanticChildCount: semanticChildCount,
384
      restorationId: restorationId,
385
      viewportBuilder: (BuildContext context, ViewportOffset offset) {
386 387
        return buildViewport(context, offset, axisDirection, slivers);
      },
388
    );
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
    final Widget scrollableResult = primary && scrollController != null
        ? PrimaryScrollController.none(child: scrollable)
        : scrollable;

    if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
      return NotificationListener<ScrollUpdateNotification>(
        child: scrollableResult,
        onNotification: (ScrollUpdateNotification notification) {
          final FocusScopeNode focusScope = FocusScope.of(context);
          if (notification.dragDetails != null && focusScope.hasFocus) {
            focusScope.unfocus();
          }
          return false;
        },
      );
    } else {
      return scrollableResult;
    }
407
  }
408 409

  @override
410 411
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
412 413 414 415 416 417
    properties.add(EnumProperty<Axis>('scrollDirection', scrollDirection));
    properties.add(FlagProperty('reverse', value: reverse, ifTrue: 'reversed', showName: true));
    properties.add(DiagnosticsProperty<ScrollController>('controller', controller, showName: false, defaultValue: null));
    properties.add(FlagProperty('primary', value: primary, ifTrue: 'using primary controller', showName: true));
    properties.add(DiagnosticsProperty<ScrollPhysics>('physics', physics, showName: false, defaultValue: null));
    properties.add(FlagProperty('shrinkWrap', value: shrinkWrap, ifTrue: 'shrink-wrapping', showName: true));
418 419 420
  }
}

421 422 423 424 425 426 427 428
/// A [ScrollView] that creates custom scroll effects using slivers.
///
/// A [CustomScrollView] lets you supply [slivers] directly to create various
/// scrolling effects, such as lists, grids, and expanding headers. For example,
/// to create a scroll view that contains an expanding app bar followed by a
/// list and a grid, use a list of three slivers: [SliverAppBar], [SliverList],
/// and [SliverGrid].
///
429 430
/// [Widget]s in these [slivers] must produce [RenderSliver] objects.
///
431 432 433
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
434 435
/// {@animation 400 376 https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_scroll_view.mp4}
///
436
/// {@tool snippet}
437 438 439 440 441
///
/// This sample code shows a scroll view that contains a flexible pinned app
/// bar, a grid, and an infinite list.
///
/// ```dart
442
/// CustomScrollView(
443 444 445 446
///   slivers: <Widget>[
///     const SliverAppBar(
///       pinned: true,
///       expandedHeight: 250.0,
447 448
///       flexibleSpace: FlexibleSpaceBar(
///         title: Text('Demo'),
449 450
///       ),
///     ),
451 452
///     SliverGrid(
///       gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
453 454 455 456 457
///         maxCrossAxisExtent: 200.0,
///         mainAxisSpacing: 10.0,
///         crossAxisSpacing: 10.0,
///         childAspectRatio: 4.0,
///       ),
458
///       delegate: SliverChildBuilderDelegate(
459
///         (BuildContext context, int index) {
460
///           return Container(
461
///             alignment: Alignment.center,
462
///             color: Colors.teal[100 * (index % 9)],
463
///             child: Text('Grid Item $index'),
464 465 466 467 468
///           );
///         },
///         childCount: 20,
///       ),
///     ),
469
///     SliverFixedExtentList(
470
///       itemExtent: 50.0,
471
///       delegate: SliverChildBuilderDelegate(
472
///         (BuildContext context, int index) {
473
///           return Container(
474
///             alignment: Alignment.center,
475
///             color: Colors.lightBlue[100 * (index % 9)],
476
///             child: Text('List Item $index'),
477 478 479 480 481 482 483
///           );
///         },
///       ),
///     ),
///   ],
/// )
/// ```
484
/// {@end-tool}
485
///
486
/// {@tool dartpad --template=stateful_widget_material}
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
///
/// By default, if items are inserted at the "top" of a scrolling container like
/// [ListView] or [CustomScrollView], the top item and all of the items below it
/// are scrolled downwards. In some applications, it's preferable to have the
/// top of the list just grow upwards, without changing the scroll position.
/// This example demonstrates how to do that with a [CustomScrollView] with
/// two [SliverList] children, and the [CustomScrollView.center] set to the key
/// of the bottom SliverList. The top one SliverList will grow upwards, and the
/// bottom SliverList will grow downwards.
///
/// ```dart
/// List<int> top = [];
/// List<int> bottom = [0];
///
/// @override
/// Widget build(BuildContext context) {
///   const Key centerKey = ValueKey('bottom-sliver-list');
///   return Scaffold(
///     appBar: AppBar(
///       title: const Text('Press on the plus to add items above and below'),
///       leading: IconButton(
///         icon: const Icon(Icons.add),
///         onPressed: () {
///           setState(() {
///             top.add(-top.length - 1);
///             bottom.add(bottom.length);
///           });
///         },
///       ),
///     ),
///     body: CustomScrollView(
///       center: centerKey,
///       slivers: <Widget>[
///         SliverList(
///           delegate: SliverChildBuilderDelegate(
///             (BuildContext context, int index) {
///               return Container(
///                 alignment: Alignment.center,
///                 color: Colors.blue[200 + top[index] % 4 * 100],
///                 height: 100 + top[index] % 4 * 20.0,
///                 child: Text('Item: ${top[index]}'),
///               );
///             },
///             childCount: top.length,
///           ),
///         ),
///         SliverList(
///           key: centerKey,
///           delegate: SliverChildBuilderDelegate(
///             (BuildContext context, int index) {
///               return Container(
///                 alignment: Alignment.center,
///                 color: Colors.blue[200 + bottom[index] % 4 * 100],
///                 height: 100 + bottom[index] % 4 * 20.0,
///                 child: Text('Item: ${bottom[index]}'),
///               );
///             },
///             childCount: bottom.length,
///           ),
///         ),
///       ],
///     ),
///   );
/// }
/// ```
/// {@end-tool}
///
554 555 556 557 558
/// ## Accessibility
///
/// A [CustomScrollView] can allow Talkback/VoiceOver to make announcements
/// to the user when the scroll state changes. For example, on Android an
/// announcement might be read as "showing items 1 to 10 of 23". To produce
559
/// this announcement, the scroll view needs three pieces of information:
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
///
///   * The first visible child index.
///   * The total number of children.
///   * The total number of visible children.
///
/// The last value can be computed exactly by the framework, however the first
/// two must be provided. Most of the higher-level scrollable widgets provide
/// this information automatically. For example, [ListView] provides each child
/// widget with a semantic index automatically and sets the semantic child
/// count to the length of the list.
///
/// To determine visible indexes, the scroll view needs a way to associate the
/// generated semantics of each scrollable item with a semantic index. This can
/// be done by wrapping the child widgets in an [IndexedSemantics].
///
575
/// This semantic index is not necessarily the same as the index of the widget in
Ian Hickson's avatar
Ian Hickson committed
576
/// the scrollable, because some widgets may not contribute semantic
577
/// information. Consider a [ListView.separated]: every other widget is a
578 579 580
/// divider with no semantic information. In this case, only odd numbered
/// widgets have a semantic index (equal to the index ~/ 2). Furthermore, the
/// total number of children in this example would be half the number of
581
/// widgets. (The [ListView.separated] constructor handles this
Ian Hickson's avatar
Ian Hickson committed
582
/// automatically; this is only used here as an example.)
583 584 585 586 587
///
/// The total number of visible children can be provided by the constructor
/// parameter `semanticChildCount`. This should always be the same as the
/// number of widgets wrapped in [IndexedSemantics].
///
588 589 590 591 592 593 594 595 596 597
/// See also:
///
///  * [SliverList], which is a sliver that displays linear list of children.
///  * [SliverFixedExtentList], which is a more efficient sliver that displays
///    linear list of children that have the same extent along the scroll axis.
///  * [SliverGrid], which is a sliver that displays a 2D array of children.
///  * [SliverPadding], which is a sliver that adds blank space around another
///    sliver.
///  * [SliverAppBar], which is a sliver that displays a header that can expand
///    and float as the scroll view scrolls.
598
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
599
///    the scroll position without using a [ScrollController].
600 601
///  * [IndexedSemantics], which allows annotating child lists with an index
///    for scroll announcements.
Adam Barth's avatar
Adam Barth committed
602
class CustomScrollView extends ScrollView {
603 604
  /// Creates a [ScrollView] that creates custom scroll effects using slivers.
  ///
605
  /// See the [ScrollView] constructor for more details on these arguments.
606
  const CustomScrollView({
607
    Key? key,
608 609
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
610 611 612
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
613
    bool shrinkWrap = false,
614
    Key? center,
615
    double anchor = 0.0,
616
    double? cacheExtent,
617
    this.slivers = const <Widget>[],
618
    int? semanticChildCount,
619
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
620
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
621
    String? restorationId,
622
    Clip clipBehavior = Clip.hardEdge,
Adam Barth's avatar
Adam Barth committed
623 624 625 626
  }) : super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
627
    controller: controller,
628
    primary: primary,
Adam Barth's avatar
Adam Barth committed
629 630
    physics: physics,
    shrinkWrap: shrinkWrap,
631 632
    center: center,
    anchor: anchor,
633
    cacheExtent: cacheExtent,
634
    semanticChildCount: semanticChildCount,
635
    dragStartBehavior: dragStartBehavior,
636
    keyboardDismissBehavior: keyboardDismissBehavior,
637
    restorationId: restorationId,
638
    clipBehavior: clipBehavior,
Adam Barth's avatar
Adam Barth committed
639 640
  );

641
  /// The slivers to place inside the viewport.
Adam Barth's avatar
Adam Barth committed
642 643 644 645 646 647
  final List<Widget> slivers;

  @override
  List<Widget> buildSlivers(BuildContext context) => slivers;
}

648
/// A [ScrollView] that uses a single child layout model.
649 650 651 652 653 654 655
///
/// See also:
///
///  * [ListView], which is a [BoxScrollView] that uses a linear layout model.
///  * [GridView], which is a [BoxScrollView] that uses a 2D layout model.
///  * [CustomScrollView], which can combine multiple child layout models into a
///    single scroll view.
656
abstract class BoxScrollView extends ScrollView {
657 658 659
  /// Creates a [ScrollView] uses a single child layout model.
  ///
  /// If the [primary] argument is true, the [controller] must be null.
660
  const BoxScrollView({
661
    Key? key,
662 663
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
664 665 666
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
667
    bool shrinkWrap = false,
668
    this.padding,
669 670
    double? cacheExtent,
    int? semanticChildCount,
671
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
672
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
673
    String? restorationId,
674
    Clip clipBehavior = Clip.hardEdge,
675 676 677 678
  }) : super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
679
    controller: controller,
680
    primary: primary,
681 682
    physics: physics,
    shrinkWrap: shrinkWrap,
683
    cacheExtent: cacheExtent,
684
    semanticChildCount: semanticChildCount,
685
    dragStartBehavior: dragStartBehavior,
686
    keyboardDismissBehavior: keyboardDismissBehavior,
687
    restorationId: restorationId,
688
    clipBehavior: clipBehavior,
689 690
  );

691
  /// The amount of space by which to inset the children.
692
  final EdgeInsetsGeometry? padding;
693 694 695 696

  @override
  List<Widget> buildSlivers(BuildContext context) {
    Widget sliver = buildChildLayout(context);
697
    EdgeInsetsGeometry? effectivePadding = padding;
698
    if (padding == null) {
699
      final MediaQueryData? mediaQuery = MediaQuery.maybeOf(context);
700 701 702 703 704 705 706 707 708 709 710
      if (mediaQuery != null) {
        // Automatically pad sliver with padding from MediaQuery.
        final EdgeInsets mediaQueryHorizontalPadding =
            mediaQuery.padding.copyWith(top: 0.0, bottom: 0.0);
        final EdgeInsets mediaQueryVerticalPadding =
            mediaQuery.padding.copyWith(left: 0.0, right: 0.0);
        // Consume the main axis padding with SliverPadding.
        effectivePadding = scrollDirection == Axis.vertical
            ? mediaQueryVerticalPadding
            : mediaQueryHorizontalPadding;
        // Leave behind the cross axis padding.
711
        sliver = MediaQuery(
712 713 714 715 716 717 718 719 720 721 722
          data: mediaQuery.copyWith(
            padding: scrollDirection == Axis.vertical
                ? mediaQueryHorizontalPadding
                : mediaQueryVerticalPadding,
          ),
          child: sliver,
        );
      }
    }

    if (effectivePadding != null)
723
      sliver = SliverPadding(padding: effectivePadding, sliver: sliver);
724 725 726
    return <Widget>[ sliver ];
  }

727
  /// Subclasses should override this method to build the layout model.
728 729 730 731
  @protected
  Widget buildChildLayout(BuildContext context);

  @override
732 733
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
734
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
735 736 737
  }
}

738
/// A scrollable list of widgets arranged linearly.
739
///
740 741
/// {@youtube 560 315 https://www.youtube.com/watch?v=KJpkjHGiI5A}
///
742 743 744 745 746 747 748 749 750 751
/// [ListView] is the most commonly used scrolling widget. It displays its
/// children one after another in the scroll direction. In the cross axis, the
/// children are required to fill the [ListView].
///
/// If non-null, the [itemExtent] forces the children to have the given extent
/// in the scroll direction. Specifying an [itemExtent] is more efficient than
/// letting the children determine their own extent because the scrolling
/// machinery can make use of the foreknowledge of the children's extent to save
/// work, for example when the scroll position changes drastically.
///
752
/// There are four options for constructing a [ListView]:
753
///
754
///  1. The default constructor takes an explicit [List<Widget>] of children. This
755 756 757 758 759
///     constructor is appropriate for list views with a small number of
///     children because constructing the [List] requires doing work for every
///     child that could possibly be displayed in the list view instead of just
///     those children that are actually visible.
///
760 761 762
///  2. The [ListView.builder] constructor takes an [IndexedWidgetBuilder], which
///     builds the children on demand. This constructor is appropriate for list views
///     with a large (or infinite) number of children because the builder is called
763 764
///     only for those children that are actually visible.
///
765 766 767 768 769 770 771
///  3. The [ListView.separated] constructor takes two [IndexedWidgetBuilder]s:
///     `itemBuilder` builds child items on demand, and `separatorBuilder`
///     similarly builds separator children which appear in between the child items.
///     This constructor is appropriate for list views with a fixed number of children.
///
///  4. The [ListView.custom] constructor takes a [SliverChildDelegate], which provides
///     the ability to customize additional aspects of the child model. For example,
772 773
///     a [SliverChildDelegate] can control the algorithm used to estimate the
///     size of children that are not actually visible.
774
///
775 776 777
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
778 779 780 781
/// By default, [ListView] will automatically pad the list's scrollable
/// extremities to avoid partial obstructions indicated by [MediaQuery]'s
/// padding. To avoid this behavior, override with a zero [padding] property.
///
782
/// {@tool snippet}
783 784 785
/// This example uses the default constructor for [ListView] which takes an
/// explicit [List<Widget>] of children. This [ListView]'s children are made up
/// of [Container]s with [Text].
786
///
787 788
/// ![A ListView of 3 amber colored containers with sample text.](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view.png)
///
789 790
/// ```dart
/// ListView(
791
///   padding: const EdgeInsets.all(8),
792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811
///   children: <Widget>[
///     Container(
///       height: 50,
///       color: Colors.amber[600],
///       child: const Center(child: Text('Entry A')),
///     ),
///     Container(
///       height: 50,
///       color: Colors.amber[500],
///       child: const Center(child: Text('Entry B')),
///     ),
///     Container(
///       height: 50,
///       color: Colors.amber[100],
///       child: const Center(child: Text('Entry C')),
///     ),
///   ],
/// )
/// ```
/// {@end-tool}
812
///
813
/// {@tool snippet}
814 815 816
/// This example mirrors the previous one, creating the same list using the
/// [ListView.builder] constructor. Using the [IndexedWidgetBuilder], children
/// are built lazily and can be infinite in number.
817
///
818 819
/// ![A ListView of 3 amber colored containers with sample text.](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view_builder.png)
///
820
/// ```dart
821 822 823
/// final List<String> entries = <String>['A', 'B', 'C'];
/// final List<int> colorCodes = <int>[600, 500, 100];
///
824
/// ListView.builder(
825
///   padding: const EdgeInsets.all(8),
826
///   itemCount: entries.length,
827
///   itemBuilder: (BuildContext context, int index) {
828 829 830 831 832 833 834 835 836
///     return Container(
///       height: 50,
///       color: Colors.amber[colorCodes[index]],
///       child: Center(child: Text('Entry ${entries[index]}')),
///     );
///   }
/// );
/// ```
/// {@end-tool}
837
///
838
/// {@tool snippet}
839 840 841 842
/// This example continues to build from our the previous ones, creating a
/// similar list using [ListView.separated]. Here, a [Divider] is used as a
/// separator.
///
843 844 845
/// ![A ListView of 3 amber colored containers with sample text and a Divider
/// between each of them.](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view_separated.png)
///
846 847 848 849 850
/// ```dart
/// final List<String> entries = <String>['A', 'B', 'C'];
/// final List<int> colorCodes = <int>[600, 500, 100];
///
/// ListView.separated(
851
///   padding: const EdgeInsets.all(8),
852 853 854 855 856 857 858
///   itemCount: entries.length,
///   itemBuilder: (BuildContext context, int index) {
///     return Container(
///       height: 50,
///       color: Colors.amber[colorCodes[index]],
///       child: Center(child: Text('Entry ${entries[index]}')),
///     );
859
///   },
860 861
///   separatorBuilder: (BuildContext context, int index) => const Divider(),
/// );
862
/// ```
863
/// {@end-tool}
864
///
865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
/// ## Child elements' lifecycle
///
/// ### Creation
///
/// While laying out the list, visible children's elements, states and render
/// objects will be created lazily based on existing widgets (such as when using
/// the default constructor) or lazily provided ones (such as when using the
/// [ListView.builder] constructor).
///
/// ### Destruction
///
/// When a child is scrolled out of view, the associated element subtree,
/// states and render objects are destroyed. A new child at the same position
/// in the list will be lazily recreated along with new elements, states and
/// render objects when it is scrolled back.
///
/// ### Destruction mitigation
///
/// In order to preserve state as child elements are scrolled in and out of
/// view, the following options are possible:
///
///  * Moving the ownership of non-trivial UI-state-driving business logic
///    out of the list child subtree. For instance, if a list contains posts
///    with their number of upvotes coming from a cached network response, store
///    the list of posts and upvote number in a data model outside the list. Let
///    the list child UI subtree be easily recreate-able from the
///    source-of-truth model object. Use [StatefulWidget]s in the child
///    widget subtree to store instantaneous UI state only.
///
///  * Letting [KeepAlive] be the root widget of the list child widget subtree
///    that needs to be preserved. The [KeepAlive] widget marks the child
896
///    subtree's top render object child for keepalive. When the associated top
897 898 899 900 901
///    render object is scrolled out of view, the list keeps the child's render
///    object (and by extension, its associated elements and states) in a cache
///    list instead of destroying them. When scrolled back into view, the render
///    object is repainted as-is (if it wasn't marked dirty in the interim).
///
902
///    This only works if `addAutomaticKeepAlives` and `addRepaintBoundaries`
903 904 905 906
///    are false since those parameters cause the [ListView] to wrap each child
///    widget subtree with other widgets.
///
///  * Using [AutomaticKeepAlive] widgets (inserted by default when
907
///    `addAutomaticKeepAlives` is true). [AutomaticKeepAlive] allows descendant
908 909 910
///    widgets to control whether the subtree is actually kept alive or not.
///    This behavior is in contrast with [KeepAlive], which will unconditionally keep
///    the subtree alive.
911 912 913
///
///    As an example, the [EditableText] widget signals its list child element
///    subtree to stay alive while its text field has input focus. If it doesn't
914
///    have focus and no other descendants signaled for keepalive via a
915 916 917 918 919
///    [KeepAliveNotification], the list child element subtree will be destroyed
///    when scrolled away.
///
///    [AutomaticKeepAlive] descendants typically signal it to be kept alive
///    by using the [AutomaticKeepAliveClientMixin], then implementing the
920 921
///    [AutomaticKeepAliveClientMixin.wantKeepAlive] getter and calling
///    [AutomaticKeepAliveClientMixin.updateKeepAlive].
922
///
923 924 925
/// ## Transitioning to [CustomScrollView]
///
/// A [ListView] is basically a [CustomScrollView] with a single [SliverList] in
926
/// its [CustomScrollView.slivers] property.
927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942
///
/// If [ListView] is no longer sufficient, for example because the scroll view
/// is to have both a list and a grid, or because the list is to be combined
/// with a [SliverAppBar], etc, it is straight-forward to port code from using
/// [ListView] to using [CustomScrollView] directly.
///
/// The [key], [scrollDirection], [reverse], [controller], [primary], [physics],
/// and [shrinkWrap] properties on [ListView] map directly to the identically
/// named properties on [CustomScrollView].
///
/// The [CustomScrollView.slivers] property should be a list containing either a
/// [SliverList] or a [SliverFixedExtentList]; the former if [itemExtent] on the
/// [ListView] was null, and the latter if [itemExtent] was not null.
///
/// The [childrenDelegate] property on [ListView] corresponds to the
/// [SliverList.delegate] (or [SliverFixedExtentList.delegate]) property. The
943
/// [ListView] constructor's `children` argument corresponds to the
944
/// [childrenDelegate] being a [SliverChildListDelegate] with that same
945
/// argument. The [ListView.builder] constructor's `itemBuilder` and
Ian Hickson's avatar
Ian Hickson committed
946 947
/// `itemCount` arguments correspond to the [childrenDelegate] being a
/// [SliverChildBuilderDelegate] with the equivalent arguments.
948 949 950 951 952
///
/// The [padding] property corresponds to having a [SliverPadding] in the
/// [CustomScrollView.slivers] property instead of the list itself, and having
/// the [SliverList] instead be a child of the [SliverPadding].
///
953 954 955
/// [CustomScrollView]s don't automatically avoid obstructions from [MediaQuery]
/// like [ListView]s do. To reproduce the behavior, wrap the slivers in
/// [SliverSafeArea]s.
956
///
957 958 959 960
/// Once code has been ported to use [CustomScrollView], other slivers, such as
/// [SliverGrid] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
/// list.
///
961
/// {@tool snippet}
962 963 964 965 966
///
/// Here are two brief snippets showing a [ListView] and its equivalent using
/// [CustomScrollView]:
///
/// ```dart
967
/// ListView(
968 969 970
///   shrinkWrap: true,
///   padding: const EdgeInsets.all(20.0),
///   children: <Widget>[
971
///     const Text("I'm dedicating every day to you"),
972 973 974 975 976 977
///     const Text('Domestic life was never quite my style'),
///     const Text('When you smile, you knock me out, I fall apart'),
///     const Text('And I thought I was so smart'),
///   ],
/// )
/// ```
978
/// {@end-tool}
979
/// {@tool snippet}
980 981
///
/// ```dart
982
/// CustomScrollView(
983 984
///   shrinkWrap: true,
///   slivers: <Widget>[
985
///     SliverPadding(
986
///       padding: const EdgeInsets.all(20.0),
987 988
///       sliver: SliverList(
///         delegate: SliverChildListDelegate(
989
///           <Widget>[
990
///             const Text("I'm dedicating every day to you"),
991 992 993 994 995 996 997 998 999 1000
///             const Text('Domestic life was never quite my style'),
///             const Text('When you smile, you knock me out, I fall apart'),
///             const Text('And I thought I was so smart'),
///           ],
///         ),
///       ),
///     ),
///   ],
/// )
/// ```
1001
/// {@end-tool}
1002
///
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
/// ## Special handling for an empty list
///
/// A common design pattern is to have a custom UI for an empty list. The best
/// way to achieve this in Flutter is just conditionally replacing the
/// [ListView] at build time with whatever widgets you need to show for the
/// empty list state:
///
/// {@tool snippet}
///
/// Example of simple empty list interface:
///
/// ```dart
/// Widget build(BuildContext context) {
///   return Scaffold(
///     appBar: AppBar(title: const Text('Empty List Test')),
///     body: itemCount > 0
///       ? ListView.builder(
///           itemCount: itemCount,
///           itemBuilder: (BuildContext context, int index) {
///             return ListTile(
///               title: Text('Item ${index + 1}'),
///             );
///           },
///         )
///       : Center(child: const Text('No items')),
///   );
/// }
/// ```
/// {@end-tool}
///
1033 1034 1035 1036 1037 1038
/// ## Selection of list items
///
/// `ListView` has no built-in notion of a selected item or items. For a small
/// example of how a caller might wire up basic item selection, see
/// [ListTile.selected].
///
1039 1040
/// See also:
///
1041 1042 1043 1044
///  * [SingleChildScrollView], which is a scrollable widget that has a single
///    child.
///  * [PageView], which is a scrolling list of child widgets that are each the
///    size of the viewport.
1045
///  * [GridView], which is a scrollable, 2D array of widgets.
1046 1047
///  * [CustomScrollView], which is a scrollable widget that creates custom
///    scroll effects using slivers.
1048 1049
///  * [ListBody], which arranges its children in a similar manner, but without
///    scrolling.
1050
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
1051
///    the scroll position without using a [ScrollController].
1052
///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
1053 1054 1055 1056 1057
///  * Cookbook: [Use lists](https://flutter.dev/docs/cookbook/lists/basic-list)
///  * Cookbook: [Work with long lists](https://flutter.dev/docs/cookbook/lists/long-lists)
///  * Cookbook: [Create a horizontal list](https://flutter.dev/docs/cookbook/lists/horizontal-list)
///  * Cookbook: [Create lists with different types of items](https://flutter.dev/docs/cookbook/lists/mixed-list)
///  * Cookbook: [Implement swipe to dismiss](https://flutter.dev/docs/cookbook/gestures/dismissible)
1058
class ListView extends BoxScrollView {
1059 1060 1061 1062 1063 1064
  /// Creates a scrollable, linear array of widgets from an explicit [List].
  ///
  /// This constructor is appropriate for list views with a small number of
  /// children because constructing the [List] requires doing work for every
  /// child that could possibly be displayed in the list view instead of just
  /// those children that are actually visible.
1065
  ///
1066 1067 1068 1069
  /// Like other widgets in the framework, this widget expects that
  /// the [children] list will not be mutated after it has been passed in here.
  /// See the documentation at [SliverChildListDelegate.children] for more details.
  ///
1070
  /// It is usually more efficient to create children on demand using
1071
  /// [ListView.builder] because it will create the widget children lazily as necessary.
1072
  ///
1073 1074 1075
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1076 1077 1078 1079
  /// [SliverChildListDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildListDelegate.addSemanticIndexes] property. None
  /// may be null.
1080
  ListView({
1081
    Key? key,
1082 1083
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1084 1085 1086
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1087
    bool shrinkWrap = false,
1088
    EdgeInsetsGeometry? padding,
1089
    this.itemExtent,
1090 1091
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1092
    bool addSemanticIndexes = true,
1093
    double? cacheExtent,
1094
    List<Widget> children = const <Widget>[],
1095
    int? semanticChildCount,
1096
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1097
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1098
    String? restorationId,
1099
    Clip clipBehavior = Clip.hardEdge,
1100
  }) : childrenDelegate = SliverChildListDelegate(
1101
         children,
1102
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1103
         addRepaintBoundaries: addRepaintBoundaries,
1104
         addSemanticIndexes: addSemanticIndexes,
1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? children.length,
         dragStartBehavior: dragStartBehavior,
1118
         keyboardDismissBehavior: keyboardDismissBehavior,
1119
         restorationId: restorationId,
1120
         clipBehavior: clipBehavior,
1121
       );
1122

1123 1124 1125 1126 1127 1128
  /// Creates a scrollable, linear array of widgets that are created on demand.
  ///
  /// This constructor is appropriate for list views with a large (or infinite)
  /// number of children because the builder is called only for those children
  /// that are actually visible.
  ///
1129
  /// Providing a non-null `itemCount` improves the ability of the [ListView] to
1130 1131
  /// estimate the maximum scroll extent.
  ///
1132 1133 1134
  /// The `itemBuilder` callback will be called only with indices greater than
  /// or equal to zero and less than `itemCount`.
  ///
1135 1136 1137 1138 1139 1140 1141
  /// The `itemBuilder` should always return a non-null widget, and actually
  /// create the widget instances when called. Avoid using a builder that
  /// returns a previously-constructed widget; if the list view's children are
  /// created in advance, or all at once when the [ListView] itself is created,
  /// it is more efficient to use the [ListView] constructor. Even more
  /// efficient, however, is to create the instances on demand using this
  /// constructor's `itemBuilder` callback.
1142
  ///
1143 1144 1145
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1146 1147 1148 1149
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None may be
  /// null.
1150 1151 1152 1153
  ///
  /// [ListView.builder] by default does not support child reordering. If
  /// you are planning to change child order at a later time, consider using
  /// [ListView] or [ListView.custom].
1154
  ListView.builder({
1155
    Key? key,
1156 1157
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1158 1159 1160
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1161
    bool shrinkWrap = false,
1162
    EdgeInsetsGeometry? padding,
1163
    this.itemExtent,
1164 1165
    required IndexedWidgetBuilder itemBuilder,
    int? itemCount,
1166 1167
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1168
    bool addSemanticIndexes = true,
1169 1170
    double? cacheExtent,
    int? semanticChildCount,
1171
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1172
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1173
    String? restorationId,
1174
    Clip clipBehavior = Clip.hardEdge,
1175
  }) : assert(itemCount == null || itemCount >= 0),
1176
       assert(semanticChildCount == null || semanticChildCount <= itemCount!),
1177
       childrenDelegate = SliverChildBuilderDelegate(
1178 1179
         itemBuilder,
         childCount: itemCount,
1180
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1181
         addRepaintBoundaries: addRepaintBoundaries,
1182
         addSemanticIndexes: addSemanticIndexes,
1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? itemCount,
         dragStartBehavior: dragStartBehavior,
1196
         keyboardDismissBehavior: keyboardDismissBehavior,
1197
         restorationId: restorationId,
1198
         clipBehavior: clipBehavior,
1199
       );
1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216

  /// Creates a fixed-length scrollable linear array of list "items" separated
  /// by list item "separators".
  ///
  /// This constructor is appropriate for list views with a large number of
  /// item and separator children because the builders are only called for
  /// the children that are actually visible.
  ///
  /// The `itemBuilder` callback will be called with indices greater than
  /// or equal to zero and less than `itemCount`.
  ///
  /// Separators only appear between list items: separator 0 appears after item
  /// 0 and the last separator appears before the last item.
  ///
  /// The `separatorBuilder` callback will be called with indices greater than
  /// or equal to zero and less than `itemCount - 1`.
  ///
1217 1218 1219 1220 1221
  /// The `itemBuilder` and `separatorBuilder` callbacks should always return a
  /// non-null widget, and actually create widget instances when called. Avoid
  /// using a builder that returns a previously-constructed widget; if the list
  /// view's children are created in advance, or all at once when the [ListView]
  /// itself is created, it is more efficient to use the [ListView] constructor.
1222
  ///
1223
  /// {@tool snippet}
1224 1225 1226 1227 1228
  ///
  /// This example shows how to create [ListView] whose [ListTile] list items
  /// are separated by [Divider]s.
  ///
  /// ```dart
1229
  /// ListView.separated(
1230
  ///   itemCount: 25,
1231
  ///   separatorBuilder: (BuildContext context, int index) => Divider(),
1232
  ///   itemBuilder: (BuildContext context, int index) {
1233 1234
  ///     return ListTile(
  ///       title: Text('item $index'),
1235 1236 1237 1238
  ///     );
  ///   },
  /// )
  /// ```
1239
  /// {@end-tool}
1240 1241 1242 1243
  ///
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1244 1245 1246 1247
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None may be
  /// null.
1248
  ListView.separated({
1249
    Key? key,
1250 1251
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1252 1253 1254
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1255
    bool shrinkWrap = false,
1256 1257 1258 1259
    EdgeInsetsGeometry? padding,
    required IndexedWidgetBuilder itemBuilder,
    required IndexedWidgetBuilder separatorBuilder,
    required int itemCount,
1260 1261
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1262
    bool addSemanticIndexes = true,
1263
    double? cacheExtent,
1264
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1265
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1266
    String? restorationId,
1267
    Clip clipBehavior = Clip.hardEdge,
1268 1269 1270 1271
  }) : assert(itemBuilder != null),
       assert(separatorBuilder != null),
       assert(itemCount != null && itemCount >= 0),
       itemExtent = null,
1272
       childrenDelegate = SliverChildBuilderDelegate(
1273 1274
         (BuildContext context, int index) {
           final int itemIndex = index ~/ 2;
1275
           final Widget widget;
1276 1277 1278 1279 1280
           if (index.isEven) {
             widget = itemBuilder(context, itemIndex);
           } else {
             widget = separatorBuilder(context, itemIndex);
             assert(() {
1281
               if (widget == null) { // ignore: dead_code
1282 1283 1284 1285 1286 1287
                 throw FlutterError('separatorBuilder cannot return null.');
               }
               return true;
             }());
           }
           return widget;
1288
         },
1289
         childCount: _computeActualChildCount(itemCount),
1290 1291
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
1292 1293 1294
         addSemanticIndexes: addSemanticIndexes,
         semanticIndexCallback: (Widget _, int index) {
           return index.isEven ? index ~/ 2 : null;
1295
         },
1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
1307
         semanticChildCount: itemCount,
1308
         dragStartBehavior: dragStartBehavior,
1309
         keyboardDismissBehavior: keyboardDismissBehavior,
1310
         restorationId: restorationId,
1311
         clipBehavior: clipBehavior,
1312
       );
1313

1314 1315 1316 1317
  /// Creates a scrollable, linear array of widgets with a custom child model.
  ///
  /// For example, a custom child model can control the algorithm used to
  /// estimate the size of children that are not actually visible.
1318
  ///
1319
  /// {@tool snippet}
1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352
  ///
  /// This [ListView] uses a custom [SliverChildBuilderDelegate] to support child
  /// reordering.
  ///
  /// ```dart
  /// class MyListView extends StatefulWidget {
  ///   @override
  ///   _MyListViewState createState() => _MyListViewState();
  /// }
  ///
  /// class _MyListViewState extends State<MyListView> {
  ///   List<String> items = <String>['1', '2', '3', '4', '5'];
  ///
  ///   void _reverse() {
  ///     setState(() {
  ///       items = items.reversed.toList();
  ///     });
  ///   }
  ///
  ///   @override
  ///   Widget build(BuildContext context) {
  ///     return Scaffold(
  ///       body: SafeArea(
  ///         child: ListView.custom(
  ///           childrenDelegate: SliverChildBuilderDelegate(
  ///             (BuildContext context, int index) {
  ///               return KeepAlive(
  ///                 data: items[index],
  ///                 key: ValueKey<String>(items[index]),
  ///               );
  ///             },
  ///             childCount: items.length,
  ///             findChildIndexCallback: (Key key) {
1353
  ///               final ValueKey valueKey = key as ValueKey;
1354 1355 1356 1357 1358 1359 1360 1361 1362 1363
  ///               final String data = valueKey.value;
  ///               return items.indexOf(data);
  ///             }
  ///           ),
  ///         ),
  ///       ),
  ///       bottomNavigationBar: BottomAppBar(
  ///         child: Row(
  ///           mainAxisAlignment: MainAxisAlignment.center,
  ///           children: <Widget>[
1364
  ///             TextButton(
1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375
  ///               onPressed: () => _reverse(),
  ///               child: Text('Reverse items'),
  ///             ),
  ///           ],
  ///         ),
  ///       ),
  ///     );
  ///   }
  /// }
  ///
  /// class KeepAlive extends StatefulWidget {
1376 1377 1378 1379
  ///   const KeepAlive({
  ///     required Key key,
  ///     required this.data,
  ///   }) : super(key: key);
1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398
  ///
  ///   final String data;
  ///
  ///   @override
  ///   _KeepAliveState createState() => _KeepAliveState();
  /// }
  ///
  /// class _KeepAliveState extends State<KeepAlive> with AutomaticKeepAliveClientMixin{
  ///   @override
  ///   bool get wantKeepAlive => true;
  ///
  ///   @override
  ///   Widget build(BuildContext context) {
  ///     super.build(context);
  ///     return Text(widget.data);
  ///   }
  /// }
  /// ```
  /// {@end-tool}
1399
  const ListView.custom({
1400
    Key? key,
1401 1402
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1403 1404 1405
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1406
    bool shrinkWrap = false,
1407
    EdgeInsetsGeometry? padding,
1408
    this.itemExtent,
1409 1410 1411
    required this.childrenDelegate,
    double? cacheExtent,
    int? semanticChildCount,
1412 1413
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1414
    String? restorationId,
1415
    Clip clipBehavior = Clip.hardEdge,
1416 1417 1418 1419 1420 1421 1422 1423 1424 1425
  }) : assert(childrenDelegate != null),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
Ian Hickson's avatar
Ian Hickson committed
1426
         cacheExtent: cacheExtent,
1427
         semanticChildCount: semanticChildCount,
1428 1429
         dragStartBehavior: dragStartBehavior,
         keyboardDismissBehavior: keyboardDismissBehavior,
1430
         restorationId: restorationId,
1431
         clipBehavior: clipBehavior,
1432
       );
1433

1434 1435 1436 1437 1438 1439 1440
  /// If non-null, forces the children to have the given extent in the scroll
  /// direction.
  ///
  /// Specifying an [itemExtent] is more efficient than letting the children
  /// determine their own extent because the scrolling machinery can make use of
  /// the foreknowledge of the children's extent to save work, for example when
  /// the scroll position changes drastically.
1441
  final double? itemExtent;
1442

1443 1444 1445 1446 1447 1448
  /// A delegate that provides the children for the [ListView].
  ///
  /// The [ListView.custom] constructor lets you specify this delegate
  /// explicitly. The [ListView] and [ListView.builder] constructors create a
  /// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder],
  /// respectively.
1449
  final SliverChildDelegate childrenDelegate;
1450 1451 1452 1453

  @override
  Widget buildChildLayout(BuildContext context) {
    if (itemExtent != null) {
1454
      return SliverFixedExtentList(
1455
        delegate: childrenDelegate,
1456
        itemExtent: itemExtent!,
1457 1458
      );
    }
1459
    return SliverList(delegate: childrenDelegate);
1460 1461 1462
  }

  @override
1463 1464
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1465
    properties.add(DoubleProperty('itemExtent', itemExtent, defaultValue: null));
1466
  }
1467

1468 1469
  // Helper method to compute the actual child count for the separated constructor.
  static int _computeActualChildCount(int itemCount) {
1470 1471
    return math.max(0, itemCount * 2 - 1);
  }
1472 1473
}

1474 1475
/// A scrollable, 2D array of widgets.
///
1476 1477
/// {@youtube 560 315 https://www.youtube.com/watch?v=bLOtZDTm4H8}
///
1478 1479 1480
/// The main axis direction of a grid is the direction in which it scrolls (the
/// [scrollDirection]).
///
1481 1482 1483
/// The most commonly used grid layouts are [GridView.count], which creates a
/// layout with a fixed number of tiles in the cross axis, and
/// [GridView.extent], which creates a layout with tiles that have a maximum
1484
/// cross-axis extent. A custom [SliverGridDelegate] can produce an arbitrary 2D
1485 1486 1487 1488
/// arrangement of children, including arrangements that are unaligned or
/// overlapping.
///
/// To create a grid with a large (or infinite) number of children, use the
1489 1490 1491 1492 1493
/// [GridView.builder] constructor with either a
/// [SliverGridDelegateWithFixedCrossAxisCount] or a
/// [SliverGridDelegateWithMaxCrossAxisExtent] for the [gridDelegate].
///
/// To use a custom [SliverChildDelegate], use [GridView.custom].
1494 1495
///
/// To create a linear array of children, use a [ListView].
1496
///
1497 1498 1499
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
1500 1501 1502
/// ## Transitioning to [CustomScrollView]
///
/// A [GridView] is basically a [CustomScrollView] with a single [SliverGrid] in
1503
/// its [CustomScrollView.slivers] property.
1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520
///
/// If [GridView] is no longer sufficient, for example because the scroll view
/// is to have both a grid and a list, or because the grid is to be combined
/// with a [SliverAppBar], etc, it is straight-forward to port code from using
/// [GridView] to using [CustomScrollView] directly.
///
/// The [key], [scrollDirection], [reverse], [controller], [primary], [physics],
/// and [shrinkWrap] properties on [GridView] map directly to the identically
/// named properties on [CustomScrollView].
///
/// The [CustomScrollView.slivers] property should be a list containing just a
/// [SliverGrid].
///
/// The [childrenDelegate] property on [GridView] corresponds to the
/// [SliverGrid.delegate] property, and the [gridDelegate] property on the
/// [GridView] corresponds to the [SliverGrid.gridDelegate] property.
///
1521
/// The [GridView], [GridView.count], and [GridView.extent]
1522
/// constructors' `children` arguments correspond to the [childrenDelegate]
1523 1524
/// being a [SliverChildListDelegate] with that same argument. The
/// [GridView.builder] constructor's `itemBuilder` and `childCount` arguments
1525 1526 1527
/// correspond to the [childrenDelegate] being a [SliverChildBuilderDelegate]
/// with the matching arguments.
///
1528
/// The [GridView.count] and [GridView.extent] constructors create
1529
/// custom grid delegates, and have equivalently named constructors on
1530 1531
/// [SliverGrid] to ease the transition: [SliverGrid.count] and
/// [SliverGrid.extent] respectively.
1532 1533 1534 1535 1536 1537
///
/// The [padding] property corresponds to having a [SliverPadding] in the
/// [CustomScrollView.slivers] property instead of the grid itself, and having
/// the [SliverGrid] instead be a child of the [SliverPadding].
///
/// Once code has been ported to use [CustomScrollView], other slivers, such as
1538
/// [SliverList] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
1539 1540
/// list.
///
1541
/// {@tool snippet}
1542
/// This example demonstrates how to create a [GridView] with two columns. The
1543
/// children are spaced apart using the `crossAxisSpacing` and `mainAxisSpacing`
1544
/// properties.
1545
///
1546
/// ![The GridView displays six children with different background colors arranged in two columns](https://flutter.github.io/assets-for-api-docs/assets/widgets/grid_view.png)
1547 1548
///
/// ```dart
1549
/// GridView.count(
1550
///   primary: false,
1551 1552 1553
///   padding: const EdgeInsets.all(20),
///   crossAxisSpacing: 10,
///   mainAxisSpacing: 10,
1554 1555
///   crossAxisCount: 2,
///   children: <Widget>[
1556 1557
///     Container(
///       padding: const EdgeInsets.all(8),
1558
///       child: const Text("He'd have you all unravel at the"),
1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585
///       color: Colors.teal[100],
///     ),
///     Container(
///       padding: const EdgeInsets.all(8),
///       child: const Text('Heed not the rabble'),
///       color: Colors.teal[200],
///     ),
///     Container(
///       padding: const EdgeInsets.all(8),
///       child: const Text('Sound of screams but the'),
///       color: Colors.teal[300],
///     ),
///     Container(
///       padding: const EdgeInsets.all(8),
///       child: const Text('Who scream'),
///       color: Colors.teal[400],
///     ),
///     Container(
///       padding: const EdgeInsets.all(8),
///       child: const Text('Revolution is coming...'),
///       color: Colors.teal[500],
///     ),
///     Container(
///       padding: const EdgeInsets.all(8),
///       child: const Text('Revolution, they...'),
///       color: Colors.teal[600],
///     ),
1586 1587 1588
///   ],
/// )
/// ```
1589
/// {@end-tool}
1590
///
1591
/// {@tool snippet}
1592 1593 1594
/// This example shows how to create the same grid as the previous example
/// using a [CustomScrollView] and a [SliverGrid].
///
1595
/// ![The CustomScrollView contains a SliverGrid that displays six children with different background colors arranged in two columns](https://flutter.github.io/assets-for-api-docs/assets/widgets/grid_view_custom_scroll.png)
1596 1597
///
/// ```dart
1598
/// CustomScrollView(
1599 1600
///   primary: false,
///   slivers: <Widget>[
1601
///     SliverPadding(
1602
///       padding: const EdgeInsets.all(20),
1603
///       sliver: SliverGrid.count(
1604 1605
///         crossAxisSpacing: 10,
///         mainAxisSpacing: 10,
1606 1607
///         crossAxisCount: 2,
///         children: <Widget>[
1608 1609
///           Container(
///             padding: const EdgeInsets.all(8),
1610
///             child: const Text("He'd have you all unravel at the"),
1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637
///             color: Colors.green[100],
///           ),
///           Container(
///             padding: const EdgeInsets.all(8),
///             child: const Text('Heed not the rabble'),
///             color: Colors.green[200],
///           ),
///           Container(
///             padding: const EdgeInsets.all(8),
///             child: const Text('Sound of screams but the'),
///             color: Colors.green[300],
///           ),
///           Container(
///             padding: const EdgeInsets.all(8),
///             child: const Text('Who scream'),
///             color: Colors.green[400],
///           ),
///           Container(
///             padding: const EdgeInsets.all(8),
///             child: const Text('Revolution is coming...'),
///             color: Colors.green[500],
///           ),
///           Container(
///             padding: const EdgeInsets.all(8),
///             child: const Text('Revolution, they...'),
///             color: Colors.green[600],
///           ),
1638 1639 1640 1641 1642 1643
///         ],
///       ),
///     ),
///   ],
/// )
/// ```
1644
/// {@end-tool}
1645
///
1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676
/// By default, [GridView] will automatically pad the limits of the
/// grids's scrollable to avoid partial obstructions indicated by
/// [MediaQuery]'s padding. To avoid this behavior, override with a
/// zero [padding] property.
///
/// {@tool snippet}
/// The following example demonstrates how to override the default top padding
/// using [MediaQuery.removePadding].
///
/// ```dart
/// Widget myWidget(BuildContext context) {
///   return MediaQuery.removePadding(
///     context: context,
///     removeTop: true,
///     child: GridView.builder(
///       gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
///         crossAxisCount: 3,
///       ),
///       itemCount: 300,
///       itemBuilder: (BuildContext context, int index) {
///         return Card(
///           color: Colors.amber,
///           child: Center(child: Text('$index')),
///         );
///       }
///     ),
///   );
/// }
/// ```
/// {@end-tool}
///
1677 1678
/// See also:
///
1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689
///  * [SingleChildScrollView], which is a scrollable widget that has a single
///    child.
///  * [ListView], which is scrollable, linear list of widgets.
///  * [PageView], which is a scrolling list of child widgets that are each the
///    size of the viewport.
///  * [CustomScrollView], which is a scrollable widget that creates custom
///    scroll effects using slivers.
///  * [SliverGridDelegateWithFixedCrossAxisCount], which creates a layout with
///    a fixed number of tiles in the cross axis.
///  * [SliverGridDelegateWithMaxCrossAxisExtent], which creates a layout with
///    tiles that have a maximum cross-axis extent.
1690
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
1691
///    the scroll position without using a [ScrollController].
1692
///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
1693
class GridView extends BoxScrollView {
1694 1695 1696 1697
  /// Creates a scrollable, 2D array of widgets with a custom
  /// [SliverGridDelegate].
  ///
  /// The [gridDelegate] argument must not be null.
1698
  ///
1699 1700 1701 1702
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1703
  /// null.
1704
  GridView({
1705
    Key? key,
1706 1707
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1708 1709 1710
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1711
    bool shrinkWrap = false,
1712 1713
    EdgeInsetsGeometry? padding,
    required this.gridDelegate,
1714 1715
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1716
    bool addSemanticIndexes = true,
1717
    double? cacheExtent,
1718
    List<Widget> children = const <Widget>[],
1719
    int? semanticChildCount,
1720
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1721
    Clip clipBehavior = Clip.hardEdge,
1722
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1723
    String? restorationId,
1724
  }) : assert(gridDelegate != null),
1725
       childrenDelegate = SliverChildListDelegate(
1726
         children,
1727
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1728
         addRepaintBoundaries: addRepaintBoundaries,
1729
         addSemanticIndexes: addSemanticIndexes,
1730
       ),
1731 1732 1733 1734 1735 1736 1737 1738 1739
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1740 1741
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? children.length,
1742 1743
         dragStartBehavior: dragStartBehavior,
         keyboardDismissBehavior: keyboardDismissBehavior,
1744
         restorationId: restorationId,
1745
         clipBehavior: clipBehavior,
1746
       );
1747

1748 1749 1750 1751 1752 1753
  /// Creates a scrollable, 2D array of widgets that are created on demand.
  ///
  /// This constructor is appropriate for grid views with a large (or infinite)
  /// number of children because the builder is called only for those children
  /// that are actually visible.
  ///
1754
  /// Providing a non-null `itemCount` improves the ability of the [GridView] to
1755 1756
  /// estimate the maximum scroll extent.
  ///
1757 1758
  /// `itemBuilder` will be called only with indices greater than or equal to
  /// zero and less than `itemCount`.
1759 1760
  ///
  /// The [gridDelegate] argument must not be null.
1761
  ///
1762 1763 1764 1765 1766
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. Both must not
  /// be null.
1767
  GridView.builder({
1768
    Key? key,
1769 1770
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1771 1772 1773
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1774
    bool shrinkWrap = false,
1775 1776 1777 1778
    EdgeInsetsGeometry? padding,
    required this.gridDelegate,
    required IndexedWidgetBuilder itemBuilder,
    int? itemCount,
1779 1780
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1781
    bool addSemanticIndexes = true,
1782 1783
    double? cacheExtent,
    int? semanticChildCount,
1784 1785
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1786
    String? restorationId,
1787
    Clip clipBehavior = Clip.hardEdge,
1788
  }) : assert(gridDelegate != null),
1789
       childrenDelegate = SliverChildBuilderDelegate(
1790 1791
         itemBuilder,
         childCount: itemCount,
1792
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1793
         addRepaintBoundaries: addRepaintBoundaries,
1794
         addSemanticIndexes: addSemanticIndexes,
1795
       ),
1796 1797 1798 1799 1800 1801 1802 1803 1804
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1805 1806
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? itemCount,
1807 1808
         dragStartBehavior: dragStartBehavior,
         keyboardDismissBehavior: keyboardDismissBehavior,
1809
         restorationId: restorationId,
1810
         clipBehavior: clipBehavior,
1811
       );
1812

1813 1814 1815
  /// Creates a scrollable, 2D array of widgets with both a custom
  /// [SliverGridDelegate] and a custom [SliverChildDelegate].
  ///
1816 1817
  /// To use an [IndexedWidgetBuilder] callback to build children, either use
  /// a [SliverChildBuilderDelegate] or use the [GridView.builder] constructor.
1818 1819
  ///
  /// The [gridDelegate] and [childrenDelegate] arguments must not be null.
1820
  const GridView.custom({
1821
    Key? key,
1822 1823
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1824 1825 1826
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1827
    bool shrinkWrap = false,
1828 1829 1830 1831 1832
    EdgeInsetsGeometry? padding,
    required this.gridDelegate,
    required this.childrenDelegate,
    double? cacheExtent,
    int? semanticChildCount,
1833
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1834
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1835
    String? restorationId,
1836
    Clip clipBehavior = Clip.hardEdge,
1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847
  }) : assert(gridDelegate != null),
       assert(childrenDelegate != null),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1848 1849
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount,
1850
         dragStartBehavior: dragStartBehavior,
1851
         keyboardDismissBehavior: keyboardDismissBehavior,
1852
         restorationId: restorationId,
1853
         clipBehavior: clipBehavior,
1854
       );
1855

1856 1857 1858 1859
  /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
  /// the cross axis.
  ///
  /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate].
1860
  ///
1861 1862 1863 1864
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1865 1866
  /// null.
  ///
1867 1868
  /// See also:
  ///
1869
  ///  * [SliverGrid.count], the equivalent constructor for [SliverGrid].
1870
  GridView.count({
1871
    Key? key,
1872 1873
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1874 1875 1876
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1877
    bool shrinkWrap = false,
1878 1879
    EdgeInsetsGeometry? padding,
    required int crossAxisCount,
1880 1881 1882 1883 1884
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1885
    bool addSemanticIndexes = true,
1886
    double? cacheExtent,
1887
    List<Widget> children = const <Widget>[],
1888
    int? semanticChildCount,
1889
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1890
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1891
    String? restorationId,
1892
    Clip clipBehavior = Clip.hardEdge,
1893
  }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
1894 1895 1896 1897 1898
         crossAxisCount: crossAxisCount,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
1899
       childrenDelegate = SliverChildListDelegate(
1900
         children,
1901
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1902
         addRepaintBoundaries: addRepaintBoundaries,
1903
         addSemanticIndexes: addSemanticIndexes,
1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? children.length,
         dragStartBehavior: dragStartBehavior,
1917
         keyboardDismissBehavior: keyboardDismissBehavior,
1918
         restorationId: restorationId,
1919
         clipBehavior: clipBehavior,
1920
       );
1921

1922 1923
  /// Creates a scrollable, 2D array of widgets with tiles that each have a
  /// maximum cross-axis extent.
1924 1925
  ///
  /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate].
1926
  ///
1927 1928 1929 1930
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1931 1932
  /// null.
  ///
1933 1934
  /// See also:
  ///
1935
  ///  * [SliverGrid.extent], the equivalent constructor for [SliverGrid].
1936
  GridView.extent({
1937
    Key? key,
1938 1939
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1940 1941 1942
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1943
    bool shrinkWrap = false,
1944 1945
    EdgeInsetsGeometry? padding,
    required double maxCrossAxisExtent,
1946 1947 1948 1949 1950
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1951
    bool addSemanticIndexes = true,
1952
    double? cacheExtent,
1953
    List<Widget> children = const <Widget>[],
1954
    int? semanticChildCount,
1955
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1956
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1957
    String? restorationId,
1958
    Clip clipBehavior = Clip.hardEdge,
1959
  }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
1960 1961 1962 1963 1964
         maxCrossAxisExtent: maxCrossAxisExtent,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
1965
       childrenDelegate = SliverChildListDelegate(
1966
         children,
1967
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1968
         addRepaintBoundaries: addRepaintBoundaries,
1969
         addSemanticIndexes: addSemanticIndexes,
1970 1971 1972 1973 1974 1975 1976 1977 1978 1979
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1980
         cacheExtent: cacheExtent,
1981 1982
         semanticChildCount: semanticChildCount ?? children.length,
         dragStartBehavior: dragStartBehavior,
1983
         keyboardDismissBehavior: keyboardDismissBehavior,
1984
         restorationId: restorationId,
1985
         clipBehavior: clipBehavior,
1986
       );
1987

1988 1989
  /// A delegate that controls the layout of the children within the [GridView].
  ///
1990
  /// The [GridView], [GridView.builder], and [GridView.custom] constructors let you specify this
1991 1992
  /// delegate explicitly. The other constructors create a [gridDelegate]
  /// implicitly.
1993 1994
  final SliverGridDelegate gridDelegate;

1995 1996 1997 1998 1999
  /// A delegate that provides the children for the [GridView].
  ///
  /// The [GridView.custom] constructor lets you specify this delegate
  /// explicitly. The other constructors create a [childrenDelegate] that wraps
  /// the given child list.
2000
  final SliverChildDelegate childrenDelegate;
2001

2002
  @override
2003
  Widget buildChildLayout(BuildContext context) {
2004
    return SliverGrid(
2005
      delegate: childrenDelegate,
2006 2007
      gridDelegate: gridDelegate,
    );
Adam Barth's avatar
Adam Barth committed
2008 2009
  }
}