scroll_view.dart 76.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 26 27
// Examples can assume:
// int itemCount;

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 113 114
  /// The axis along which the scroll view scrolls.
  ///
  /// Defaults to [Axis.vertical].
Adam Barth's avatar
Adam Barth committed
115 116
  final Axis scrollDirection;

117
  /// Whether the scroll view scrolls in the reading direction.
118 119 120 121 122 123
  ///
  /// 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
124
  /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
125 126 127 128
  /// scrolls from top to bottom when [reverse] is false and from bottom to top
  /// when [reverse] is true.
  ///
  /// Defaults to false.
129 130
  final bool reverse;

131 132 133 134
  /// An object that can be used to control the position to which this scroll
  /// view is scrolled.
  ///
  /// Must be null if [primary] is true.
135 136 137 138 139 140 141 142
  ///
  /// 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]).
143
  final ScrollController? controller;
144

145 146 147
  /// Whether this is the primary scroll view associated with the parent
  /// [PrimaryScrollController].
  ///
148 149 150 151
  /// 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].
  ///
152 153 154 155 156
  /// 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.
  ///
157
  /// On iOS, this also identifies the scroll view that will scroll to top in
158 159
  /// response to a tap in the status bar.
  ///
160 161
  /// Defaults to true when [scrollDirection] is [Axis.vertical] and
  /// [controller] is null.
162 163
  final bool primary;

164 165 166 167 168
  /// 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.
  ///
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
  /// 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(),
  /// ```
189 190 191 192 193 194 195 196 197
  ///
  /// 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.)
198
  final ScrollPhysics? physics;
Adam Barth's avatar
Adam Barth committed
199

200 201 202 203 204 205 206 207 208 209 210 211 212 213
  /// 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.
214 215
  final bool shrinkWrap;

216 217
  /// The first child in the [GrowthDirection.forward] growth direction.
  ///
218 219 220 221 222
  /// 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.
223 224 225 226 227 228 229 230 231 232
  ///
  /// 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.
233
  final Key? center;
234 235 236

  /// The relative position of the zero scroll offset.
  ///
237 238 239 240 241 242
  /// 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.
243 244
  final double anchor;

245
  /// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
246
  final double? cacheExtent;
247

248 249 250 251
  /// 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,
252
  /// while the [ListView.separated] constructor will use half that amount.
253 254 255 256 257 258 259 260
  ///
  /// 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.
261
  final int? semanticChildCount;
262

263 264 265
  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
  final DragStartBehavior dragStartBehavior;

266 267 268 269
  /// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will
  /// dismiss the keyboard automatically.
  final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;

270
  /// {@macro flutter.widgets.scrollable.restorationId}
271
  final String? restorationId;
272

273
  /// {@macro flutter.material.Material.clipBehavior}
274 275 276 277
  ///
  /// Defaults to [Clip.hardEdge].
  final Clip clipBehavior;

278 279 280 281 282
  /// Returns the [AxisDirection] in which the scroll view scrolls.
  ///
  /// Combines the [scrollDirection] with the [reverse] boolean to obtain the
  /// concrete [AxisDirection].
  ///
283
  /// If the [scrollDirection] is [Axis.horizontal], the ambient
284
  /// [Directionality] is also considered when selecting the concrete
285 286 287 288
  /// [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].
289 290
  @protected
  AxisDirection getDirection(BuildContext context) {
291
    return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
292 293
  }

294 295
  /// Build the list of widgets to place inside the viewport.
  ///
296 297
  /// Subclasses should override this method to build the slivers for the inside
  /// of the viewport.
298
  @protected
299
  List<Widget> buildSlivers(BuildContext context);
Adam Barth's avatar
Adam Barth committed
300

301 302 303 304 305
  /// 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.
306 307 308 309 310 311 312 313
  ///
  /// 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].
314 315 316 317 318 319 320
  @protected
  Widget buildViewport(
    BuildContext context,
    ViewportOffset offset,
    AxisDirection axisDirection,
    List<Widget> slivers,
  ) {
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
    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;
      }
    }());
336
    if (shrinkWrap) {
337
      return ShrinkWrappingViewport(
338 339 340
        axisDirection: axisDirection,
        offset: offset,
        slivers: slivers,
341
        clipBehavior: clipBehavior,
342 343
      );
    }
344
    return Viewport(
345 346 347
      axisDirection: axisDirection,
      offset: offset,
      slivers: slivers,
348
      cacheExtent: cacheExtent,
349 350
      center: center,
      anchor: anchor,
351
      clipBehavior: clipBehavior,
352 353 354
    );
  }

355 356
  @override
  Widget build(BuildContext context) {
357 358
    final List<Widget> slivers = buildSlivers(context);
    final AxisDirection axisDirection = getDirection(context);
359

360
    final ScrollController? scrollController =
361
        primary ? PrimaryScrollController.of(context) : controller;
362
    final Scrollable scrollable = Scrollable(
363
      dragStartBehavior: dragStartBehavior,
364
      axisDirection: axisDirection,
365
      controller: scrollController,
Adam Barth's avatar
Adam Barth committed
366
      physics: physics,
367
      semanticChildCount: semanticChildCount,
368
      restorationId: restorationId,
369
      viewportBuilder: (BuildContext context, ViewportOffset offset) {
370 371
        return buildViewport(context, offset, axisDirection, slivers);
      },
372
    );
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
    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;
    }
391
  }
392 393

  @override
394 395
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
396 397 398 399 400 401
    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));
402 403 404
  }
}

405 406 407 408 409 410 411 412
/// 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].
///
413 414
/// [Widget]s in these [slivers] must produce [RenderSliver] objects.
///
415 416 417
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
418 419
/// {@animation 400 376 https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_scroll_view.mp4}
///
420
/// {@tool snippet}
421 422 423 424 425
///
/// This sample code shows a scroll view that contains a flexible pinned app
/// bar, a grid, and an infinite list.
///
/// ```dart
426
/// CustomScrollView(
427 428 429 430
///   slivers: <Widget>[
///     const SliverAppBar(
///       pinned: true,
///       expandedHeight: 250.0,
431 432
///       flexibleSpace: FlexibleSpaceBar(
///         title: Text('Demo'),
433 434
///       ),
///     ),
435 436
///     SliverGrid(
///       gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
437 438 439 440 441
///         maxCrossAxisExtent: 200.0,
///         mainAxisSpacing: 10.0,
///         crossAxisSpacing: 10.0,
///         childAspectRatio: 4.0,
///       ),
442
///       delegate: SliverChildBuilderDelegate(
443
///         (BuildContext context, int index) {
444
///           return Container(
445
///             alignment: Alignment.center,
446
///             color: Colors.teal[100 * (index % 9)],
447
///             child: Text('Grid Item $index'),
448 449 450 451 452
///           );
///         },
///         childCount: 20,
///       ),
///     ),
453
///     SliverFixedExtentList(
454
///       itemExtent: 50.0,
455
///       delegate: SliverChildBuilderDelegate(
456
///         (BuildContext context, int index) {
457
///           return Container(
458
///             alignment: Alignment.center,
459
///             color: Colors.lightBlue[100 * (index % 9)],
460
///             child: Text('List Item $index'),
461 462 463 464 465 466 467
///           );
///         },
///       ),
///     ),
///   ],
/// )
/// ```
468
/// {@end-tool}
469
///
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 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
/// {@tool dartpad --template=stateful_widget_material}
///
/// 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}
///
538 539 540 541 542
/// ## 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
543
/// this announcement, the scroll view needs three pieces of information:
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
///
///   * 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].
///
559
/// This semantic index is not necessarily the same as the index of the widget in
Ian Hickson's avatar
Ian Hickson committed
560
/// the scrollable, because some widgets may not contribute semantic
561
/// information. Consider a [ListView.separated]: every other widget is a
562 563 564
/// 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
565
/// widgets. (The [ListView.separated] constructor handles this
Ian Hickson's avatar
Ian Hickson committed
566
/// automatically; this is only used here as an example.)
567 568 569 570 571
///
/// 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].
///
572 573 574 575 576 577 578 579 580 581
/// 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.
582
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
583
///    the scroll position without using a [ScrollController].
584 585
///  * [IndexedSemantics], which allows annotating child lists with an index
///    for scroll announcements.
Adam Barth's avatar
Adam Barth committed
586
class CustomScrollView extends ScrollView {
587 588
  /// Creates a [ScrollView] that creates custom scroll effects using slivers.
  ///
589
  /// See the [ScrollView] constructor for more details on these arguments.
590
  const CustomScrollView({
591
    Key? key,
592 593
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
594 595 596
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
597
    bool shrinkWrap = false,
598
    Key? center,
599
    double anchor = 0.0,
600
    double? cacheExtent,
601
    this.slivers = const <Widget>[],
602
    int? semanticChildCount,
603
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
604
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
605
    String? restorationId,
606
    Clip clipBehavior = Clip.hardEdge,
Adam Barth's avatar
Adam Barth committed
607 608 609 610
  }) : super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
611
    controller: controller,
612
    primary: primary,
Adam Barth's avatar
Adam Barth committed
613 614
    physics: physics,
    shrinkWrap: shrinkWrap,
615 616
    center: center,
    anchor: anchor,
617
    cacheExtent: cacheExtent,
618
    semanticChildCount: semanticChildCount,
619
    dragStartBehavior: dragStartBehavior,
620
    keyboardDismissBehavior: keyboardDismissBehavior,
621
    restorationId: restorationId,
622
    clipBehavior: clipBehavior,
Adam Barth's avatar
Adam Barth committed
623 624
  );

625
  /// The slivers to place inside the viewport.
Adam Barth's avatar
Adam Barth committed
626 627 628 629 630 631
  final List<Widget> slivers;

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

632
/// A [ScrollView] that uses a single child layout model.
633 634 635 636 637 638 639
///
/// 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.
640
abstract class BoxScrollView extends ScrollView {
641 642 643
  /// Creates a [ScrollView] uses a single child layout model.
  ///
  /// If the [primary] argument is true, the [controller] must be null.
644
  const BoxScrollView({
645
    Key? key,
646 647
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
648 649 650
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
651
    bool shrinkWrap = false,
652
    this.padding,
653 654
    double? cacheExtent,
    int? semanticChildCount,
655
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
656
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
657
    String? restorationId,
658
    Clip clipBehavior = Clip.hardEdge,
659 660 661 662
  }) : super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
663
    controller: controller,
664
    primary: primary,
665 666
    physics: physics,
    shrinkWrap: shrinkWrap,
667
    cacheExtent: cacheExtent,
668
    semanticChildCount: semanticChildCount,
669
    dragStartBehavior: dragStartBehavior,
670
    keyboardDismissBehavior: keyboardDismissBehavior,
671
    restorationId: restorationId,
672
    clipBehavior: clipBehavior,
673 674
  );

675
  /// The amount of space by which to inset the children.
676
  final EdgeInsetsGeometry? padding;
677 678 679 680

  @override
  List<Widget> buildSlivers(BuildContext context) {
    Widget sliver = buildChildLayout(context);
681
    EdgeInsetsGeometry? effectivePadding = padding;
682
    if (padding == null) {
683
      final MediaQueryData? mediaQuery = MediaQuery.maybeOf(context);
684 685 686 687 688 689 690 691 692 693 694
      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.
695
        sliver = MediaQuery(
696 697 698 699 700 701 702 703 704 705 706
          data: mediaQuery.copyWith(
            padding: scrollDirection == Axis.vertical
                ? mediaQueryHorizontalPadding
                : mediaQueryVerticalPadding,
          ),
          child: sliver,
        );
      }
    }

    if (effectivePadding != null)
707
      sliver = SliverPadding(padding: effectivePadding, sliver: sliver);
708 709 710
    return <Widget>[ sliver ];
  }

711
  /// Subclasses should override this method to build the layout model.
712 713 714 715
  @protected
  Widget buildChildLayout(BuildContext context);

  @override
716 717
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
718
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
719 720 721
  }
}

722
/// A scrollable list of widgets arranged linearly.
723
///
724 725
/// {@youtube 560 315 https://www.youtube.com/watch?v=KJpkjHGiI5A}
///
726 727 728 729 730 731 732 733 734 735
/// [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.
///
736
/// There are four options for constructing a [ListView]:
737
///
738
///  1. The default constructor takes an explicit [List<Widget>] of children. This
739 740 741 742 743
///     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.
///
744 745 746
///  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
747 748
///     only for those children that are actually visible.
///
749 750 751 752 753 754 755
///  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,
756 757
///     a [SliverChildDelegate] can control the algorithm used to estimate the
///     size of children that are not actually visible.
758
///
759 760 761
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
762 763 764 765
/// 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.
///
766
/// {@tool snippet}
767 768 769
/// 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].
770
///
771 772
/// ![A ListView of 3 amber colored containers with sample text.](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view.png)
///
773 774
/// ```dart
/// ListView(
775
///   padding: const EdgeInsets.all(8),
776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
///   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}
796
///
797
/// {@tool snippet}
798 799 800
/// 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.
801
///
802 803
/// ![A ListView of 3 amber colored containers with sample text.](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view_builder.png)
///
804
/// ```dart
805 806 807
/// final List<String> entries = <String>['A', 'B', 'C'];
/// final List<int> colorCodes = <int>[600, 500, 100];
///
808
/// ListView.builder(
809
///   padding: const EdgeInsets.all(8),
810
///   itemCount: entries.length,
811
///   itemBuilder: (BuildContext context, int index) {
812 813 814 815 816 817 818 819 820
///     return Container(
///       height: 50,
///       color: Colors.amber[colorCodes[index]],
///       child: Center(child: Text('Entry ${entries[index]}')),
///     );
///   }
/// );
/// ```
/// {@end-tool}
821
///
822
/// {@tool snippet}
823 824 825 826
/// 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.
///
827 828 829
/// ![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)
///
830 831 832 833 834
/// ```dart
/// final List<String> entries = <String>['A', 'B', 'C'];
/// final List<int> colorCodes = <int>[600, 500, 100];
///
/// ListView.separated(
835
///   padding: const EdgeInsets.all(8),
836 837 838 839 840 841 842
///   itemCount: entries.length,
///   itemBuilder: (BuildContext context, int index) {
///     return Container(
///       height: 50,
///       color: Colors.amber[colorCodes[index]],
///       child: Center(child: Text('Entry ${entries[index]}')),
///     );
843
///   },
844 845
///   separatorBuilder: (BuildContext context, int index) => const Divider(),
/// );
846
/// ```
847
/// {@end-tool}
848
///
849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
/// ## 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
880
///    subtree's top render object child for keepalive. When the associated top
881 882 883 884 885
///    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).
///
886
///    This only works if `addAutomaticKeepAlives` and `addRepaintBoundaries`
887 888 889 890
///    are false since those parameters cause the [ListView] to wrap each child
///    widget subtree with other widgets.
///
///  * Using [AutomaticKeepAlive] widgets (inserted by default when
891
///    `addAutomaticKeepAlives` is true). [AutomaticKeepAlive] allows descendant
892 893 894
///    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.
895 896 897
///
///    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
898
///    have focus and no other descendants signaled for keepalive via a
899 900 901 902 903
///    [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
904 905
///    [AutomaticKeepAliveClientMixin.wantKeepAlive] getter and calling
///    [AutomaticKeepAliveClientMixin.updateKeepAlive].
906
///
907 908 909
/// ## Transitioning to [CustomScrollView]
///
/// A [ListView] is basically a [CustomScrollView] with a single [SliverList] in
910
/// its [CustomScrollView.slivers] property.
911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926
///
/// 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
927
/// [ListView] constructor's `children` argument corresponds to the
928
/// [childrenDelegate] being a [SliverChildListDelegate] with that same
929
/// argument. The [ListView.builder] constructor's `itemBuilder` and
Ian Hickson's avatar
Ian Hickson committed
930 931
/// `itemCount` arguments correspond to the [childrenDelegate] being a
/// [SliverChildBuilderDelegate] with the equivalent arguments.
932 933 934 935 936
///
/// 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].
///
937 938 939
/// [CustomScrollView]s don't automatically avoid obstructions from [MediaQuery]
/// like [ListView]s do. To reproduce the behavior, wrap the slivers in
/// [SliverSafeArea]s.
940
///
941 942 943 944
/// Once code has been ported to use [CustomScrollView], other slivers, such as
/// [SliverGrid] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
/// list.
///
945
/// {@tool snippet}
946 947 948 949 950
///
/// Here are two brief snippets showing a [ListView] and its equivalent using
/// [CustomScrollView]:
///
/// ```dart
951
/// ListView(
952 953 954
///   shrinkWrap: true,
///   padding: const EdgeInsets.all(20.0),
///   children: <Widget>[
955
///     const Text("I'm dedicating every day to you"),
956 957 958 959 960 961
///     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'),
///   ],
/// )
/// ```
962
/// {@end-tool}
963
/// {@tool snippet}
964 965
///
/// ```dart
966
/// CustomScrollView(
967 968
///   shrinkWrap: true,
///   slivers: <Widget>[
969
///     SliverPadding(
970
///       padding: const EdgeInsets.all(20.0),
971 972
///       sliver: SliverList(
///         delegate: SliverChildListDelegate(
973
///           <Widget>[
974
///             const Text("I'm dedicating every day to you"),
975 976 977 978 979 980 981 982 983 984
///             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'),
///           ],
///         ),
///       ),
///     ),
///   ],
/// )
/// ```
985
/// {@end-tool}
986
///
987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
/// ## 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}
///
1017 1018 1019 1020 1021 1022
/// ## 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].
///
1023 1024
/// See also:
///
1025 1026 1027 1028
///  * [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.
1029
///  * [GridView], which is a scrollable, 2D array of widgets.
1030 1031
///  * [CustomScrollView], which is a scrollable widget that creates custom
///    scroll effects using slivers.
1032 1033
///  * [ListBody], which arranges its children in a similar manner, but without
///    scrolling.
1034
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
1035
///    the scroll position without using a [ScrollController].
1036
///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
1037 1038 1039 1040 1041
///  * 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)
1042
class ListView extends BoxScrollView {
1043 1044 1045 1046 1047 1048
  /// 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.
1049
  ///
1050 1051 1052 1053
  /// 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.
  ///
1054
  /// It is usually more efficient to create children on demand using
1055
  /// [ListView.builder] because it will create the widget children lazily as necessary.
1056
  ///
1057 1058 1059
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1060 1061 1062 1063
  /// [SliverChildListDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildListDelegate.addSemanticIndexes] property. None
  /// may be null.
1064
  ListView({
1065
    Key? key,
1066 1067
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1068 1069 1070
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1071
    bool shrinkWrap = false,
1072
    EdgeInsetsGeometry? padding,
1073
    this.itemExtent,
1074 1075
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1076
    bool addSemanticIndexes = true,
1077
    double? cacheExtent,
1078
    List<Widget> children = const <Widget>[],
1079
    int? semanticChildCount,
1080
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1081
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1082
    String? restorationId,
1083
    Clip clipBehavior = Clip.hardEdge,
1084
  }) : childrenDelegate = SliverChildListDelegate(
1085
         children,
1086
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1087
         addRepaintBoundaries: addRepaintBoundaries,
1088
         addSemanticIndexes: addSemanticIndexes,
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101
       ),
       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,
1102
         keyboardDismissBehavior: keyboardDismissBehavior,
1103
         restorationId: restorationId,
1104
         clipBehavior: clipBehavior,
1105
       );
1106

1107 1108 1109 1110 1111 1112
  /// 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.
  ///
1113
  /// Providing a non-null `itemCount` improves the ability of the [ListView] to
1114 1115
  /// estimate the maximum scroll extent.
  ///
1116 1117 1118
  /// The `itemBuilder` callback will be called only with indices greater than
  /// or equal to zero and less than `itemCount`.
  ///
1119 1120 1121 1122 1123 1124 1125
  /// 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.
1126
  ///
1127 1128 1129
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1130 1131 1132 1133
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None may be
  /// null.
1134 1135 1136 1137
  ///
  /// [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].
1138
  ListView.builder({
1139
    Key? key,
1140 1141
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1142 1143 1144
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1145
    bool shrinkWrap = false,
1146
    EdgeInsetsGeometry? padding,
1147
    this.itemExtent,
1148 1149
    required IndexedWidgetBuilder itemBuilder,
    int? itemCount,
1150 1151
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1152
    bool addSemanticIndexes = true,
1153 1154
    double? cacheExtent,
    int? semanticChildCount,
1155
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1156
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1157
    String? restorationId,
1158
    Clip clipBehavior = Clip.hardEdge,
1159
  }) : assert(itemCount == null || itemCount >= 0),
1160
       assert(semanticChildCount == null || semanticChildCount <= itemCount!),
1161
       childrenDelegate = SliverChildBuilderDelegate(
1162 1163
         itemBuilder,
         childCount: itemCount,
1164
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1165
         addRepaintBoundaries: addRepaintBoundaries,
1166
         addSemanticIndexes: addSemanticIndexes,
1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? itemCount,
         dragStartBehavior: dragStartBehavior,
1180
         keyboardDismissBehavior: keyboardDismissBehavior,
1181
         restorationId: restorationId,
1182
         clipBehavior: clipBehavior,
1183
       );
1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200

  /// 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`.
  ///
1201 1202 1203 1204 1205
  /// 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.
1206
  ///
1207
  /// {@tool snippet}
1208 1209 1210 1211 1212
  ///
  /// This example shows how to create [ListView] whose [ListTile] list items
  /// are separated by [Divider]s.
  ///
  /// ```dart
1213
  /// ListView.separated(
1214
  ///   itemCount: 25,
1215
  ///   separatorBuilder: (BuildContext context, int index) => Divider(),
1216
  ///   itemBuilder: (BuildContext context, int index) {
1217 1218
  ///     return ListTile(
  ///       title: Text('item $index'),
1219 1220 1221 1222
  ///     );
  ///   },
  /// )
  /// ```
1223
  /// {@end-tool}
1224 1225 1226 1227
  ///
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1228 1229 1230 1231
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None may be
  /// null.
1232
  ListView.separated({
1233
    Key? key,
1234 1235
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1236 1237 1238
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1239
    bool shrinkWrap = false,
1240 1241 1242 1243
    EdgeInsetsGeometry? padding,
    required IndexedWidgetBuilder itemBuilder,
    required IndexedWidgetBuilder separatorBuilder,
    required int itemCount,
1244 1245
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1246
    bool addSemanticIndexes = true,
1247
    double? cacheExtent,
1248
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1249
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1250
    String? restorationId,
1251
    Clip clipBehavior = Clip.hardEdge,
1252 1253 1254 1255
  }) : assert(itemBuilder != null),
       assert(separatorBuilder != null),
       assert(itemCount != null && itemCount >= 0),
       itemExtent = null,
1256
       childrenDelegate = SliverChildBuilderDelegate(
1257 1258
         (BuildContext context, int index) {
           final int itemIndex = index ~/ 2;
1259
           final Widget widget;
1260 1261 1262 1263 1264
           if (index.isEven) {
             widget = itemBuilder(context, itemIndex);
           } else {
             widget = separatorBuilder(context, itemIndex);
             assert(() {
1265
               if (widget == null) { // ignore: dead_code
1266 1267 1268 1269 1270 1271
                 throw FlutterError('separatorBuilder cannot return null.');
               }
               return true;
             }());
           }
           return widget;
1272
         },
1273
         childCount: _computeActualChildCount(itemCount),
1274 1275
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
1276 1277 1278
         addSemanticIndexes: addSemanticIndexes,
         semanticIndexCallback: (Widget _, int index) {
           return index.isEven ? index ~/ 2 : null;
1279
         },
1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
1291
         semanticChildCount: itemCount,
1292
         dragStartBehavior: dragStartBehavior,
1293
         keyboardDismissBehavior: keyboardDismissBehavior,
1294
         restorationId: restorationId,
1295
         clipBehavior: clipBehavior,
1296
       );
1297

1298 1299 1300 1301
  /// 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.
1302
  ///
1303
  /// {@tool snippet}
1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 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
  ///
  /// 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) {
  ///               final ValueKey valueKey = key;
  ///               final String data = valueKey.value;
  ///               return items.indexOf(data);
  ///             }
  ///           ),
  ///         ),
  ///       ),
  ///       bottomNavigationBar: BottomAppBar(
  ///         child: Row(
  ///           mainAxisAlignment: MainAxisAlignment.center,
  ///           children: <Widget>[
1348
  ///             TextButton(
1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379
  ///               onPressed: () => _reverse(),
  ///               child: Text('Reverse items'),
  ///             ),
  ///           ],
  ///         ),
  ///       ),
  ///     );
  ///   }
  /// }
  ///
  /// class KeepAlive extends StatefulWidget {
  ///   const KeepAlive({Key key, this.data}) : super(key: key);
  ///
  ///   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}
1380
  const ListView.custom({
1381
    Key? key,
1382 1383
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1384 1385 1386
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1387
    bool shrinkWrap = false,
1388
    EdgeInsetsGeometry? padding,
1389
    this.itemExtent,
1390 1391 1392
    required this.childrenDelegate,
    double? cacheExtent,
    int? semanticChildCount,
1393 1394
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1395
    String? restorationId,
1396
    Clip clipBehavior = Clip.hardEdge,
1397 1398 1399 1400 1401 1402 1403 1404 1405 1406
  }) : 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
1407
         cacheExtent: cacheExtent,
1408
         semanticChildCount: semanticChildCount,
1409 1410
         dragStartBehavior: dragStartBehavior,
         keyboardDismissBehavior: keyboardDismissBehavior,
1411
         restorationId: restorationId,
1412
         clipBehavior: clipBehavior,
1413
       );
1414

1415 1416 1417 1418 1419 1420 1421
  /// 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.
1422
  final double? itemExtent;
1423

1424 1425 1426 1427 1428 1429
  /// 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.
1430
  final SliverChildDelegate childrenDelegate;
1431 1432 1433 1434

  @override
  Widget buildChildLayout(BuildContext context) {
    if (itemExtent != null) {
1435
      return SliverFixedExtentList(
1436
        delegate: childrenDelegate,
1437
        itemExtent: itemExtent!,
1438 1439
      );
    }
1440
    return SliverList(delegate: childrenDelegate);
1441 1442 1443
  }

  @override
1444 1445
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1446
    properties.add(DoubleProperty('itemExtent', itemExtent, defaultValue: null));
1447
  }
1448

1449 1450
  // Helper method to compute the actual child count for the separated constructor.
  static int _computeActualChildCount(int itemCount) {
1451 1452
    return math.max(0, itemCount * 2 - 1);
  }
1453 1454
}

1455 1456
/// A scrollable, 2D array of widgets.
///
1457 1458 1459
/// The main axis direction of a grid is the direction in which it scrolls (the
/// [scrollDirection]).
///
1460 1461 1462
/// 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
1463
/// cross-axis extent. A custom [SliverGridDelegate] can produce an arbitrary 2D
1464 1465 1466 1467
/// arrangement of children, including arrangements that are unaligned or
/// overlapping.
///
/// To create a grid with a large (or infinite) number of children, use the
1468 1469 1470 1471 1472
/// [GridView.builder] constructor with either a
/// [SliverGridDelegateWithFixedCrossAxisCount] or a
/// [SliverGridDelegateWithMaxCrossAxisExtent] for the [gridDelegate].
///
/// To use a custom [SliverChildDelegate], use [GridView.custom].
1473 1474
///
/// To create a linear array of children, use a [ListView].
1475
///
1476 1477 1478
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
1479 1480 1481
/// ## Transitioning to [CustomScrollView]
///
/// A [GridView] is basically a [CustomScrollView] with a single [SliverGrid] in
1482
/// its [CustomScrollView.slivers] property.
1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499
///
/// 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.
///
1500
/// The [GridView], [GridView.count], and [GridView.extent]
1501
/// constructors' `children` arguments correspond to the [childrenDelegate]
1502 1503
/// being a [SliverChildListDelegate] with that same argument. The
/// [GridView.builder] constructor's `itemBuilder` and `childCount` arguments
1504 1505 1506
/// correspond to the [childrenDelegate] being a [SliverChildBuilderDelegate]
/// with the matching arguments.
///
1507
/// The [GridView.count] and [GridView.extent] constructors create
1508
/// custom grid delegates, and have equivalently named constructors on
1509 1510
/// [SliverGrid] to ease the transition: [SliverGrid.count] and
/// [SliverGrid.extent] respectively.
1511 1512 1513 1514 1515
///
/// 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].
///
1516 1517 1518 1519
/// 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.
///
1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537
/// {@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: ListView.builder(
///       itemCount: 25,
///       itemBuilder: (BuildContext context, int index) => ListTile(title: Text('item $index')),
///     )
///   );
/// }
/// ```
/// {@end-tool}
///
1538
/// Once code has been ported to use [CustomScrollView], other slivers, such as
1539
/// [SliverList] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
1540 1541
/// list.
///
1542
/// {@tool snippet}
1543
/// This example demonstrates how to create a [GridView] with two columns. The
1544
/// children are spaced apart using the `crossAxisSpacing` and `mainAxisSpacing`
1545
/// properties.
1546
///
1547
/// ![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)
1548 1549
///
/// ```dart
1550
/// GridView.count(
1551
///   primary: false,
1552 1553 1554
///   padding: const EdgeInsets.all(20),
///   crossAxisSpacing: 10,
///   mainAxisSpacing: 10,
1555 1556
///   crossAxisCount: 2,
///   children: <Widget>[
1557 1558
///     Container(
///       padding: const EdgeInsets.all(8),
1559
///       child: const Text("He'd have you all unravel at the"),
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 1586
///       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],
///     ),
1587 1588 1589
///   ],
/// )
/// ```
1590
/// {@end-tool}
1591
///
1592
/// {@tool snippet}
1593 1594 1595
/// This example shows how to create the same grid as the previous example
/// using a [CustomScrollView] and a [SliverGrid].
///
1596
/// ![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)
1597 1598
///
/// ```dart
1599
/// CustomScrollView(
1600 1601
///   primary: false,
///   slivers: <Widget>[
1602
///     SliverPadding(
1603
///       padding: const EdgeInsets.all(20),
1604
///       sliver: SliverGrid.count(
1605 1606
///         crossAxisSpacing: 10,
///         mainAxisSpacing: 10,
1607 1608
///         crossAxisCount: 2,
///         children: <Widget>[
1609 1610
///           Container(
///             padding: const EdgeInsets.all(8),
1611
///             child: const Text("He'd have you all unravel at the"),
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 1638
///             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],
///           ),
1639 1640 1641 1642 1643 1644
///         ],
///       ),
///     ),
///   ],
/// )
/// ```
1645
/// {@end-tool}
1646
///
1647 1648
/// See also:
///
1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659
///  * [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.
1660
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
1661
///    the scroll position without using a [ScrollController].
1662
///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
1663
class GridView extends BoxScrollView {
1664 1665 1666 1667
  /// Creates a scrollable, 2D array of widgets with a custom
  /// [SliverGridDelegate].
  ///
  /// The [gridDelegate] argument must not be null.
1668
  ///
1669 1670 1671 1672
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1673
  /// null.
1674
  GridView({
1675
    Key? key,
1676 1677
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1678 1679 1680
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1681
    bool shrinkWrap = false,
1682 1683
    EdgeInsetsGeometry? padding,
    required this.gridDelegate,
1684 1685
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1686
    bool addSemanticIndexes = true,
1687
    double? cacheExtent,
1688
    List<Widget> children = const <Widget>[],
1689
    int? semanticChildCount,
1690
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1691
    Clip clipBehavior = Clip.hardEdge,
1692
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1693
    String? restorationId,
1694
  }) : assert(gridDelegate != null),
1695
       childrenDelegate = SliverChildListDelegate(
1696
         children,
1697
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1698
         addRepaintBoundaries: addRepaintBoundaries,
1699
         addSemanticIndexes: addSemanticIndexes,
1700
       ),
1701 1702 1703 1704 1705 1706 1707 1708 1709
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1710 1711
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? children.length,
1712 1713
         dragStartBehavior: dragStartBehavior,
         keyboardDismissBehavior: keyboardDismissBehavior,
1714
         restorationId: restorationId,
1715
         clipBehavior: clipBehavior,
1716
       );
1717

1718 1719 1720 1721 1722 1723
  /// 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.
  ///
1724
  /// Providing a non-null `itemCount` improves the ability of the [GridView] to
1725 1726
  /// estimate the maximum scroll extent.
  ///
1727 1728
  /// `itemBuilder` will be called only with indices greater than or equal to
  /// zero and less than `itemCount`.
1729 1730
  ///
  /// The [gridDelegate] argument must not be null.
1731
  ///
1732 1733 1734 1735 1736
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. Both must not
  /// be null.
1737
  GridView.builder({
1738
    Key? key,
1739 1740
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1741 1742 1743
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1744
    bool shrinkWrap = false,
1745 1746 1747 1748
    EdgeInsetsGeometry? padding,
    required this.gridDelegate,
    required IndexedWidgetBuilder itemBuilder,
    int? itemCount,
1749 1750
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1751
    bool addSemanticIndexes = true,
1752 1753
    double? cacheExtent,
    int? semanticChildCount,
1754 1755
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1756
    String? restorationId,
1757
    Clip clipBehavior = Clip.hardEdge,
1758
  }) : assert(gridDelegate != null),
1759
       childrenDelegate = SliverChildBuilderDelegate(
1760 1761
         itemBuilder,
         childCount: itemCount,
1762
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1763
         addRepaintBoundaries: addRepaintBoundaries,
1764
         addSemanticIndexes: addSemanticIndexes,
1765
       ),
1766 1767 1768 1769 1770 1771 1772 1773 1774
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1775 1776
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? itemCount,
1777 1778
         dragStartBehavior: dragStartBehavior,
         keyboardDismissBehavior: keyboardDismissBehavior,
1779
         restorationId: restorationId,
1780
         clipBehavior: clipBehavior,
1781
       );
1782

1783 1784 1785
  /// Creates a scrollable, 2D array of widgets with both a custom
  /// [SliverGridDelegate] and a custom [SliverChildDelegate].
  ///
1786 1787
  /// To use an [IndexedWidgetBuilder] callback to build children, either use
  /// a [SliverChildBuilderDelegate] or use the [GridView.builder] constructor.
1788 1789
  ///
  /// The [gridDelegate] and [childrenDelegate] arguments must not be null.
1790
  const GridView.custom({
1791
    Key? key,
1792 1793
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1794 1795 1796
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1797
    bool shrinkWrap = false,
1798 1799 1800 1801 1802
    EdgeInsetsGeometry? padding,
    required this.gridDelegate,
    required this.childrenDelegate,
    double? cacheExtent,
    int? semanticChildCount,
1803
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1804
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1805
    String? restorationId,
1806
    Clip clipBehavior = Clip.hardEdge,
1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817
  }) : assert(gridDelegate != null),
       assert(childrenDelegate != null),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1818 1819
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount,
1820
         dragStartBehavior: dragStartBehavior,
1821
         keyboardDismissBehavior: keyboardDismissBehavior,
1822
         restorationId: restorationId,
1823
         clipBehavior: clipBehavior,
1824
       );
1825

1826 1827 1828 1829
  /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
  /// the cross axis.
  ///
  /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate].
1830
  ///
1831 1832 1833 1834
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1835 1836
  /// null.
  ///
1837 1838
  /// See also:
  ///
1839
  ///  * [SliverGrid.count], the equivalent constructor for [SliverGrid].
1840
  GridView.count({
1841
    Key? key,
1842 1843
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1844 1845 1846
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1847
    bool shrinkWrap = false,
1848 1849
    EdgeInsetsGeometry? padding,
    required int crossAxisCount,
1850 1851 1852 1853 1854
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1855
    bool addSemanticIndexes = true,
1856
    double? cacheExtent,
1857
    List<Widget> children = const <Widget>[],
1858
    int? semanticChildCount,
1859
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1860
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1861
    String? restorationId,
1862
    Clip clipBehavior = Clip.hardEdge,
1863
  }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
1864 1865 1866 1867 1868
         crossAxisCount: crossAxisCount,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
1869
       childrenDelegate = SliverChildListDelegate(
1870
         children,
1871
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1872
         addRepaintBoundaries: addRepaintBoundaries,
1873
         addSemanticIndexes: addSemanticIndexes,
1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886
       ),
       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,
1887
         keyboardDismissBehavior: keyboardDismissBehavior,
1888
         restorationId: restorationId,
1889
         clipBehavior: clipBehavior,
1890
       );
1891

1892 1893
  /// Creates a scrollable, 2D array of widgets with tiles that each have a
  /// maximum cross-axis extent.
1894 1895
  ///
  /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate].
1896
  ///
1897 1898 1899 1900
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1901 1902
  /// null.
  ///
1903 1904
  /// See also:
  ///
1905
  ///  * [SliverGrid.extent], the equivalent constructor for [SliverGrid].
1906
  GridView.extent({
1907
    Key? key,
1908 1909
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1910 1911 1912
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1913
    bool shrinkWrap = false,
1914 1915
    EdgeInsetsGeometry? padding,
    required double maxCrossAxisExtent,
1916 1917 1918 1919 1920
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1921
    bool addSemanticIndexes = true,
1922
    double? cacheExtent,
1923
    List<Widget> children = const <Widget>[],
1924
    int? semanticChildCount,
1925
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1926
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1927
    String? restorationId,
1928
    Clip clipBehavior = Clip.hardEdge,
1929
  }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
1930 1931 1932 1933 1934
         maxCrossAxisExtent: maxCrossAxisExtent,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
1935
       childrenDelegate = SliverChildListDelegate(
1936
         children,
1937
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1938
         addRepaintBoundaries: addRepaintBoundaries,
1939
         addSemanticIndexes: addSemanticIndexes,
1940 1941 1942 1943 1944 1945 1946 1947 1948 1949
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1950
         cacheExtent: cacheExtent,
1951 1952
         semanticChildCount: semanticChildCount ?? children.length,
         dragStartBehavior: dragStartBehavior,
1953
         keyboardDismissBehavior: keyboardDismissBehavior,
1954
         restorationId: restorationId,
1955
         clipBehavior: clipBehavior,
1956
       );
1957

1958 1959
  /// A delegate that controls the layout of the children within the [GridView].
  ///
1960
  /// The [GridView], [GridView.builder], and [GridView.custom] constructors let you specify this
1961 1962
  /// delegate explicitly. The other constructors create a [gridDelegate]
  /// implicitly.
1963 1964
  final SliverGridDelegate gridDelegate;

1965 1966 1967 1968 1969
  /// 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.
1970
  final SliverChildDelegate childrenDelegate;
1971

1972
  @override
1973
  Widget buildChildLayout(BuildContext context) {
1974
    return SliverGrid(
1975
      delegate: childrenDelegate,
1976 1977
      gridDelegate: gridDelegate,
    );
Adam Barth's avatar
Adam Barth committed
1978 1979
  }
}