scroll_view.dart 64.3 KB
Newer Older
Adam Barth's avatar
Adam Barth committed
1 2 3 4
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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 'framework.dart';
12
import 'media_query.dart';
13
import 'primary_scroll_controller.dart';
14
import 'scroll_controller.dart';
15
import 'scroll_physics.dart';
Adam Barth's avatar
Adam Barth committed
16 17
import 'scrollable.dart';
import 'sliver.dart';
18
import 'viewport.dart';
Adam Barth's avatar
Adam Barth committed
19

20 21 22 23 24 25 26 27 28 29 30 31 32
/// 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
33
/// the viewport and deferring to its subclass to create the slivers.
34
///
35 36 37
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
38 39 40 41 42 43 44 45 46 47
/// 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.
48
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
49
///    the scroll position without using a [ScrollController].
50
abstract class ScrollView extends StatelessWidget {
51 52 53
  /// Creates a widget that scrolls.
  ///
  /// If the [primary] argument is true, the [controller] must be null.
54 55 56 57 58 59
  ///
  /// 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.
60
  const ScrollView({
Adam Barth's avatar
Adam Barth committed
61
    Key key,
62 63
    this.scrollDirection = Axis.vertical,
    this.reverse = false,
64
    this.controller,
65
    bool primary,
66
    ScrollPhysics physics,
67
    this.shrinkWrap = false,
68 69
    this.center,
    this.anchor = 0.0,
70
    this.cacheExtent,
71
    this.semanticChildCount,
72
    this.dragStartBehavior = DragStartBehavior.start,
73 74
  }) : assert(scrollDirection != null),
       assert(reverse != null),
75
       assert(shrinkWrap != null),
76
       assert(dragStartBehavior != null),
77
       assert(!(controller != null && primary == true),
78
           'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. '
79
           'You cannot both set primary to true and pass an explicit controller.'
80
       ),
81 82 83
       assert(!shrinkWrap || center == null),
       assert(anchor != null),
       assert(anchor >= 0.0 && anchor <= 1.0),
84 85
       primary = primary ?? controller == null && identical(scrollDirection, Axis.vertical),
       physics = physics ?? (primary == true || (primary == null && controller == null && identical(scrollDirection, Axis.vertical)) ? const AlwaysScrollableScrollPhysics() : null),
86
       super(key: key);
Adam Barth's avatar
Adam Barth committed
87

88 89 90
  /// The axis along which the scroll view scrolls.
  ///
  /// Defaults to [Axis.vertical].
Adam Barth's avatar
Adam Barth committed
91 92
  final Axis scrollDirection;

93
  /// Whether the scroll view scrolls in the reading direction.
94 95 96 97 98 99
  ///
  /// 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
100
  /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
101 102 103 104
  /// scrolls from top to bottom when [reverse] is false and from bottom to top
  /// when [reverse] is true.
  ///
  /// Defaults to false.
105 106
  final bool reverse;

107 108 109 110
  /// An object that can be used to control the position to which this scroll
  /// view is scrolled.
  ///
  /// Must be null if [primary] is true.
111 112 113 114 115 116 117 118
  ///
  /// 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]).
119 120
  final ScrollController controller;

121 122 123
  /// Whether this is the primary scroll view associated with the parent
  /// [PrimaryScrollController].
  ///
124 125 126 127 128
  /// 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].
  ///
  /// On iOS, this also identifies the scroll view that will scroll to top in
129 130
  /// response to a tap in the status bar.
  ///
131 132
  /// Defaults to true when [scrollDirection] is [Axis.vertical] and
  /// [controller] is null.
133 134
  final bool primary;

135 136 137 138 139
  /// 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.
  ///
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
  /// 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(),
  /// ```
160 161 162 163 164 165 166 167 168
  ///
  /// 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.)
Adam Barth's avatar
Adam Barth committed
169 170
  final ScrollPhysics physics;

171 172 173 174 175 176 177 178 179 180 181 182 183 184
  /// 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.
185 186
  final bool shrinkWrap;

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
  /// The first child in the [GrowthDirection.forward] growth direction.
  ///
  /// Children after [center] will be placed in the [axisDirection] relative to
  /// the [center]. Children before [center] will be placed in the opposite of
  /// the [axisDirection] relative to the [center].
  ///
  /// 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.
  final Key center;

  /// The relative position of the zero scroll offset.
  ///
  /// For example, if [anchor] is 0.5 and the [axisDirection] 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
  /// [axisDirection] is [AxisDirection.right], then the zero scroll offset is
  /// on the left edge of the viewport.
  final double anchor;

213 214 215
  /// {@macro flutter.rendering.viewport.cacheExtent}
  final double cacheExtent;

216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
  /// 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,
  /// while the [new ListView.separated] constructor will use half that amount.
  ///
  /// 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.
  final int semanticChildCount;

231 232 233
  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
  final DragStartBehavior dragStartBehavior;

234 235 236 237 238
  /// Returns the [AxisDirection] in which the scroll view scrolls.
  ///
  /// Combines the [scrollDirection] with the [reverse] boolean to obtain the
  /// concrete [AxisDirection].
  ///
239
  /// If the [scrollDirection] is [Axis.horizontal], the ambient
240
  /// [Directionality] is also considered when selecting the concrete
241 242 243 244
  /// [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].
245 246
  @protected
  AxisDirection getDirection(BuildContext context) {
247
    return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
248 249
  }

250 251
  /// Build the list of widgets to place inside the viewport.
  ///
252 253
  /// Subclasses should override this method to build the slivers for the inside
  /// of the viewport.
254
  @protected
255
  List<Widget> buildSlivers(BuildContext context);
Adam Barth's avatar
Adam Barth committed
256

257 258 259 260 261
  /// 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.
262 263 264 265 266 267 268 269
  ///
  /// 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].
270 271 272 273 274 275 276 277
  @protected
  Widget buildViewport(
    BuildContext context,
    ViewportOffset offset,
    AxisDirection axisDirection,
    List<Widget> slivers,
  ) {
    if (shrinkWrap) {
278
      return ShrinkWrappingViewport(
279 280 281 282 283
        axisDirection: axisDirection,
        offset: offset,
        slivers: slivers,
      );
    }
284
    return Viewport(
285 286 287
      axisDirection: axisDirection,
      offset: offset,
      slivers: slivers,
288
      cacheExtent: cacheExtent,
289 290
      center: center,
      anchor: anchor,
291 292 293
    );
  }

294 295
  @override
  Widget build(BuildContext context) {
296 297
    final List<Widget> slivers = buildSlivers(context);
    final AxisDirection axisDirection = getDirection(context);
298

299
    final ScrollController scrollController = primary
Ian Hickson's avatar
Ian Hickson committed
300 301
      ? PrimaryScrollController.of(context)
      : controller;
302
    final Scrollable scrollable = Scrollable(
303
      dragStartBehavior: dragStartBehavior,
304
      axisDirection: axisDirection,
305
      controller: scrollController,
Adam Barth's avatar
Adam Barth committed
306
      physics: physics,
307
      semanticChildCount: semanticChildCount,
308
      viewportBuilder: (BuildContext context, ViewportOffset offset) {
309 310
        return buildViewport(context, offset, axisDirection, slivers);
      },
311
    );
312
    return primary && scrollController != null
313
      ? PrimaryScrollController.none(child: scrollable)
314
      : scrollable;
315
  }
316 317

  @override
318 319
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
320 321 322 323 324 325
    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));
326 327 328
  }
}

329 330 331 332 333 334 335 336
/// 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].
///
337 338
/// [Widget]s in these [slivers] must produce [RenderSliver] objects.
///
339 340 341
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
342 343
/// {@animation 400 376 https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_scroll_view.mp4}
///
344
/// {@tool sample}
345 346 347 348 349
///
/// This sample code shows a scroll view that contains a flexible pinned app
/// bar, a grid, and an infinite list.
///
/// ```dart
350
/// CustomScrollView(
351 352 353 354
///   slivers: <Widget>[
///     const SliverAppBar(
///       pinned: true,
///       expandedHeight: 250.0,
355 356
///       flexibleSpace: FlexibleSpaceBar(
///         title: Text('Demo'),
357 358
///       ),
///     ),
359 360
///     SliverGrid(
///       gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
361 362 363 364 365
///         maxCrossAxisExtent: 200.0,
///         mainAxisSpacing: 10.0,
///         crossAxisSpacing: 10.0,
///         childAspectRatio: 4.0,
///       ),
366
///       delegate: SliverChildBuilderDelegate(
367
///         (BuildContext context, int index) {
368
///           return Container(
369
///             alignment: Alignment.center,
370
///             color: Colors.teal[100 * (index % 9)],
371
///             child: Text('Grid Item $index'),
372 373 374 375 376
///           );
///         },
///         childCount: 20,
///       ),
///     ),
377
///     SliverFixedExtentList(
378
///       itemExtent: 50.0,
379
///       delegate: SliverChildBuilderDelegate(
380
///         (BuildContext context, int index) {
381
///           return Container(
382
///             alignment: Alignment.center,
383
///             color: Colors.lightBlue[100 * (index % 9)],
384
///             child: Text('List Item $index'),
385 386 387 388 389 390 391
///           );
///         },
///       ),
///     ),
///   ],
/// )
/// ```
392
/// {@end-tool}
393
///
394 395 396 397 398
/// ## 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
399
/// this announcement, the scroll view needs three pieces of information:
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
///
///   * 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].
///
415
/// This semantic index is not necessarily the same as the index of the widget in
Ian Hickson's avatar
Ian Hickson committed
416 417
/// the scrollable, because some widgets may not contribute semantic
/// information. Consider a [new ListView.separated()]: every other widget is a
418 419 420
/// 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
Ian Hickson's avatar
Ian Hickson committed
421 422
/// widgets. (The [new ListView.separated()] constructor handles this
/// automatically; this is only used here as an example.)
423 424 425 426 427
///
/// 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].
///
428 429 430 431 432 433 434 435 436 437
/// 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.
438
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
439
///    the scroll position without using a [ScrollController].
440 441
///  * [IndexedSemantics], which allows annotating child lists with an index
///    for scroll announcements.
Adam Barth's avatar
Adam Barth committed
442
class CustomScrollView extends ScrollView {
443 444
  /// Creates a [ScrollView] that creates custom scroll effects using slivers.
  ///
445
  /// See the [new ScrollView] constructor for more details on these arguments.
446
  const CustomScrollView({
Adam Barth's avatar
Adam Barth committed
447
    Key key,
448 449
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
450
    ScrollController controller,
451
    bool primary,
Adam Barth's avatar
Adam Barth committed
452
    ScrollPhysics physics,
453
    bool shrinkWrap = false,
454 455
    Key center,
    double anchor = 0.0,
456
    double cacheExtent,
457
    this.slivers = const <Widget>[],
458
    int semanticChildCount,
459
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
Adam Barth's avatar
Adam Barth committed
460 461 462 463
  }) : super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
464
    controller: controller,
465
    primary: primary,
Adam Barth's avatar
Adam Barth committed
466 467
    physics: physics,
    shrinkWrap: shrinkWrap,
468 469
    center: center,
    anchor: anchor,
470
    cacheExtent: cacheExtent,
471
    semanticChildCount: semanticChildCount,
472
    dragStartBehavior: dragStartBehavior,
Adam Barth's avatar
Adam Barth committed
473 474
  );

475
  /// The slivers to place inside the viewport.
Adam Barth's avatar
Adam Barth committed
476 477 478 479 480 481
  final List<Widget> slivers;

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

482
/// A [ScrollView] that uses a single child layout model.
483 484 485 486 487 488 489
///
/// 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.
490
abstract class BoxScrollView extends ScrollView {
491 492 493
  /// Creates a [ScrollView] uses a single child layout model.
  ///
  /// If the [primary] argument is true, the [controller] must be null.
494
  const BoxScrollView({
495
    Key key,
496 497
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
498
    ScrollController controller,
499
    bool primary,
500
    ScrollPhysics physics,
501
    bool shrinkWrap = false,
502
    this.padding,
503
    double cacheExtent,
504
    int semanticChildCount,
505
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
506 507 508 509
  }) : super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
510
    controller: controller,
511
    primary: primary,
512 513
    physics: physics,
    shrinkWrap: shrinkWrap,
514
    cacheExtent: cacheExtent,
515
    semanticChildCount: semanticChildCount,
516
    dragStartBehavior: dragStartBehavior,
517 518
  );

519
  /// The amount of space by which to inset the children.
520
  final EdgeInsetsGeometry padding;
521 522 523 524

  @override
  List<Widget> buildSlivers(BuildContext context) {
    Widget sliver = buildChildLayout(context);
525 526 527 528 529 530 531 532 533 534 535 536 537 538
    EdgeInsetsGeometry effectivePadding = padding;
    if (padding == null) {
      final MediaQueryData mediaQuery = MediaQuery.of(context, nullOk: true);
      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.
539
        sliver = MediaQuery(
540 541 542 543 544 545 546 547 548 549 550
          data: mediaQuery.copyWith(
            padding: scrollDirection == Axis.vertical
                ? mediaQueryHorizontalPadding
                : mediaQueryVerticalPadding,
          ),
          child: sliver,
        );
      }
    }

    if (effectivePadding != null)
551
      sliver = SliverPadding(padding: effectivePadding, sliver: sliver);
552 553 554
    return <Widget>[ sliver ];
  }

555
  /// Subclasses should override this method to build the layout model.
556 557 558 559
  @protected
  Widget buildChildLayout(BuildContext context);

  @override
560 561
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
562
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
563 564 565
  }
}

566
/// A scrollable list of widgets arranged linearly.
567 568 569 570 571 572 573 574 575 576 577
///
/// [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.
///
578
/// There are four options for constructing a [ListView]:
579
///
580
///  1. The default constructor takes an explicit [List<Widget>] of children. This
581 582 583 584 585
///     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.
///
586 587 588
///  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
589 590
///     only for those children that are actually visible.
///
591 592 593 594 595 596 597
///  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,
598 599
///     a [SliverChildDelegate] can control the algorithm used to estimate the
///     size of children that are not actually visible.
600
///
601 602 603
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
604 605 606 607
/// 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.
///
608
/// {@tool sample}
609 610 611
/// 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].
612
///
613 614
/// ![A ListView of 3 amber colored containers with sample text.](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view.png)
///
615 616
/// ```dart
/// ListView(
617
///   padding: const EdgeInsets.all(8),
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
///   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}
638
///
639 640 641 642
/// {@tool sample}
/// 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.
643
///
644 645
/// ![A ListView of 3 amber colored containers with sample text.](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view_builder.png)
///
646
/// ```dart
647 648 649
/// final List<String> entries = <String>['A', 'B', 'C'];
/// final List<int> colorCodes = <int>[600, 500, 100];
///
650
/// ListView.builder(
651
///   padding: const EdgeInsets.all(8),
652
///   itemCount: entries.length,
653
///   itemBuilder: (BuildContext context, int index) {
654 655 656 657 658 659 660 661 662
///     return Container(
///       height: 50,
///       color: Colors.amber[colorCodes[index]],
///       child: Center(child: Text('Entry ${entries[index]}')),
///     );
///   }
/// );
/// ```
/// {@end-tool}
663
///
664 665 666 667 668
/// {@tool sample}
/// 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.
///
669 670 671
/// ![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)
///
672 673 674 675 676
/// ```dart
/// final List<String> entries = <String>['A', 'B', 'C'];
/// final List<int> colorCodes = <int>[600, 500, 100];
///
/// ListView.separated(
677
///   padding: const EdgeInsets.all(8),
678 679 680 681 682 683 684
///   itemCount: entries.length,
///   itemBuilder: (BuildContext context, int index) {
///     return Container(
///       height: 50,
///       color: Colors.amber[colorCodes[index]],
///       child: Center(child: Text('Entry ${entries[index]}')),
///     );
685
///   },
686 687
///   separatorBuilder: (BuildContext context, int index) => const Divider(),
/// );
688
/// ```
689
/// {@end-tool}
690
///
691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747
/// ## 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
///    subtree's top render object child for keep-alive. When the associated top
///    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).
///
///    This only works if [addAutomaticKeepAlives] and [addRepaintBoundaries]
///    are false since those parameters cause the [ListView] to wrap each child
///    widget subtree with other widgets.
///
///  * Using [AutomaticKeepAlive] widgets (inserted by default when
///    [addAutomaticKeepAlives] is true). Instead of unconditionally caching the
///    child element subtree when scrolling off-screen like [KeepAlive],
///    [AutomaticKeepAlive] can let whether to cache the subtree be determined
///    by descendant logic in the subtree.
///
///    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
///    have focus and no other descendants signaled for keep-alive via a
///    [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
///    [wantKeepAlive] getter and calling [updateKeepAlive].
///
748 749 750
/// ## Transitioning to [CustomScrollView]
///
/// A [ListView] is basically a [CustomScrollView] with a single [SliverList] in
751
/// its [CustomScrollView.slivers] property.
752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
///
/// 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
/// [new ListView] constructor's `children` argument corresponds to the
/// [childrenDelegate] being a [SliverChildListDelegate] with that same
/// argument. The [new ListView.builder] constructor's `itemBuilder` and
Ian Hickson's avatar
Ian Hickson committed
771 772
/// `itemCount` arguments correspond to the [childrenDelegate] being a
/// [SliverChildBuilderDelegate] with the equivalent arguments.
773 774 775 776 777
///
/// 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].
///
778 779 780
/// [CustomScrollView]s don't automatically avoid obstructions from [MediaQuery]
/// like [ListView]s do. To reproduce the behavior, wrap the slivers in
/// [SliverSafeArea]s.
781
///
782 783 784 785
/// Once code has been ported to use [CustomScrollView], other slivers, such as
/// [SliverGrid] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
/// list.
///
786
/// {@tool sample}
787 788 789 790 791
///
/// Here are two brief snippets showing a [ListView] and its equivalent using
/// [CustomScrollView]:
///
/// ```dart
792
/// ListView(
793 794 795 796 797 798 799 800 801 802
///   shrinkWrap: true,
///   padding: const EdgeInsets.all(20.0),
///   children: <Widget>[
///     const Text('I\'m dedicating every day to you'),
///     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'),
///   ],
/// )
/// ```
803 804
/// {@end-tool}
/// {@tool sample}
805 806
///
/// ```dart
807
/// CustomScrollView(
808 809
///   shrinkWrap: true,
///   slivers: <Widget>[
810
///     SliverPadding(
811
///       padding: const EdgeInsets.all(20.0),
812 813
///       sliver: SliverList(
///         delegate: SliverChildListDelegate(
814 815 816 817 818 819 820 821 822 823 824 825
///           <Widget>[
///             const Text('I\'m dedicating every day to you'),
///             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'),
///           ],
///         ),
///       ),
///     ),
///   ],
/// )
/// ```
826
/// {@end-tool}
827
///
828 829
/// See also:
///
830 831 832 833 834 835 836
///  * [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.
///  * [GridView], which is scrollable, 2D array of widgets.
///  * [CustomScrollView], which is a scrollable widget that creates custom
///    scroll effects using slivers.
837 838
///  * [ListBody], which arranges its children in a similar manner, but without
///    scrolling.
839
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
840
///    the scroll position without using a [ScrollController].
841
class ListView extends BoxScrollView {
842 843 844 845 846 847
  /// 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.
848 849 850
  ///
  /// It is usually more efficient to create children on demand using [new
  /// ListView.builder].
851
  ///
852 853 854
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
855 856 857 858
  /// [SliverChildListDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildListDelegate.addSemanticIndexes] property. None
  /// may be null.
859 860
  ListView({
    Key key,
861 862
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
863
    ScrollController controller,
864
    bool primary,
865
    ScrollPhysics physics,
866
    bool shrinkWrap = false,
867
    EdgeInsetsGeometry padding,
868
    this.itemExtent,
869 870
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
871
    bool addSemanticIndexes = true,
872
    double cacheExtent,
873
    List<Widget> children = const <Widget>[],
874
    int semanticChildCount,
875
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
876
  }) : childrenDelegate = SliverChildListDelegate(
877
         children,
878
         addAutomaticKeepAlives: addAutomaticKeepAlives,
879
         addRepaintBoundaries: addRepaintBoundaries,
880
         addSemanticIndexes: addSemanticIndexes,
881 882 883 884 885 886 887 888 889 890 891 892 893 894
       ),
       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,
       );
895

896 897 898 899 900 901
  /// 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.
  ///
902
  /// Providing a non-null `itemCount` improves the ability of the [ListView] to
903 904
  /// estimate the maximum scroll extent.
  ///
905 906 907 908 909 910 911 912 913
  /// The `itemBuilder` callback will be called only with indices greater than
  /// or equal to zero and less than `itemCount`.
  ///
  /// The `itemBuilder` should 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 [new ListView].
  /// Even more efficient, however, is to create the instances on demand using
  /// this constructor's `itemBuilder` callback.
914
  ///
915 916 917
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
918 919 920 921
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None may be
  /// null.
922 923 924 925
  ///
  /// [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].
926 927
  ListView.builder({
    Key key,
928 929
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
930
    ScrollController controller,
931
    bool primary,
932
    ScrollPhysics physics,
933
    bool shrinkWrap = false,
934
    EdgeInsetsGeometry padding,
935
    this.itemExtent,
936
    @required IndexedWidgetBuilder itemBuilder,
937
    int itemCount,
938 939
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
940
    bool addSemanticIndexes = true,
941
    double cacheExtent,
942
    int semanticChildCount,
943
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
944
  }) : childrenDelegate = SliverChildBuilderDelegate(
945 946
         itemBuilder,
         childCount: itemCount,
947
         addAutomaticKeepAlives: addAutomaticKeepAlives,
948
         addRepaintBoundaries: addRepaintBoundaries,
949
         addSemanticIndexes: addSemanticIndexes,
950 951 952 953 954 955 956 957 958 959 960 961 962 963
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? itemCount,
         dragStartBehavior: dragStartBehavior,
       );
964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986

  /// 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`.
  ///
  /// The `itemBuilder` and `separatorBuilder` callbacks should 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 [new ListView].
  ///
987
  /// {@tool sample}
988 989 990 991 992
  ///
  /// This example shows how to create [ListView] whose [ListTile] list items
  /// are separated by [Divider]s.
  ///
  /// ```dart
993
  /// ListView.separated(
994
  ///   itemCount: 25,
995
  ///   separatorBuilder: (BuildContext context, int index) => Divider(),
996
  ///   itemBuilder: (BuildContext context, int index) {
997 998
  ///     return ListTile(
  ///       title: Text('item $index'),
999 1000 1001 1002
  ///     );
  ///   },
  /// )
  /// ```
1003
  /// {@end-tool}
1004 1005 1006 1007
  ///
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1008 1009 1010 1011
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None may be
  /// null.
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025
  ListView.separated({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required IndexedWidgetBuilder itemBuilder,
    @required IndexedWidgetBuilder separatorBuilder,
    @required int itemCount,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1026
    bool addSemanticIndexes = true,
1027 1028 1029 1030 1031
    double cacheExtent,
  }) : assert(itemBuilder != null),
       assert(separatorBuilder != null),
       assert(itemCount != null && itemCount >= 0),
       itemExtent = null,
1032
       childrenDelegate = SliverChildBuilderDelegate(
1033 1034
         (BuildContext context, int index) {
           final int itemIndex = index ~/ 2;
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047
           Widget widget;
           if (index.isEven) {
             widget = itemBuilder(context, itemIndex);
           } else {
             widget = separatorBuilder(context, itemIndex);
             assert(() {
               if (widget == null) {
                 throw FlutterError('separatorBuilder cannot return null.');
               }
               return true;
             }());
           }
           return widget;
1048
         },
1049
         childCount: _computeSemanticChildCount(itemCount),
1050 1051
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
1052 1053 1054
         addSemanticIndexes: addSemanticIndexes,
         semanticIndexCallback: (Widget _, int index) {
           return index.isEven ? index ~/ 2 : null;
1055
         },
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
         semanticChildCount: _computeSemanticChildCount(itemCount),
       );
1069

1070 1071 1072 1073
  /// 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.
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151
  ///
  /// {@tool sample}
  ///
  /// 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>[
  ///             FlatButton(
  ///               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}
1152
  const ListView.custom({
1153
    Key key,
1154 1155
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1156
    ScrollController controller,
1157
    bool primary,
1158
    ScrollPhysics physics,
1159
    bool shrinkWrap = false,
1160
    EdgeInsetsGeometry padding,
1161 1162
    this.itemExtent,
    @required this.childrenDelegate,
1163
    double cacheExtent,
1164
    int semanticChildCount,
1165 1166 1167 1168 1169 1170 1171 1172 1173 1174
  }) : 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
1175
         cacheExtent: cacheExtent,
1176
         semanticChildCount: semanticChildCount,
1177
       );
1178

1179 1180 1181 1182 1183 1184 1185
  /// 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.
1186 1187
  final double itemExtent;

1188 1189 1190 1191 1192 1193
  /// 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.
1194
  final SliverChildDelegate childrenDelegate;
1195 1196 1197 1198

  @override
  Widget buildChildLayout(BuildContext context) {
    if (itemExtent != null) {
1199
      return SliverFixedExtentList(
1200 1201 1202 1203
        delegate: childrenDelegate,
        itemExtent: itemExtent,
      );
    }
1204
    return SliverList(delegate: childrenDelegate);
1205 1206 1207
  }

  @override
1208 1209
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1210
    properties.add(DoubleProperty('itemExtent', itemExtent, defaultValue: null));
1211
  }
1212 1213 1214 1215 1216

  // Helper method to compute the semantic child count for the separated constructor.
  static int _computeSemanticChildCount(int itemCount) {
    return math.max(0, itemCount * 2 - 1);
  }
1217 1218
}

1219 1220
/// A scrollable, 2D array of widgets.
///
1221 1222 1223
/// The main axis direction of a grid is the direction in which it scrolls (the
/// [scrollDirection]).
///
1224 1225 1226
/// 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
1227
/// cross-axis extent. A custom [SliverGridDelegate] can produce an arbitrary 2D
1228 1229 1230 1231
/// arrangement of children, including arrangements that are unaligned or
/// overlapping.
///
/// To create a grid with a large (or infinite) number of children, use the
1232 1233 1234 1235 1236
/// [GridView.builder] constructor with either a
/// [SliverGridDelegateWithFixedCrossAxisCount] or a
/// [SliverGridDelegateWithMaxCrossAxisExtent] for the [gridDelegate].
///
/// To use a custom [SliverChildDelegate], use [GridView.custom].
1237 1238
///
/// To create a linear array of children, use a [ListView].
1239
///
1240 1241 1242
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
1243 1244 1245
/// ## Transitioning to [CustomScrollView]
///
/// A [GridView] is basically a [CustomScrollView] with a single [SliverGrid] in
1246
/// its [CustomScrollView.slivers] property.
1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279
///
/// 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.
///
/// The [new GridView], [new GridView.count], and [new GridView.extent]
/// constructors' `children` arguments correspond to the [childrenDelegate]
/// being a [SliverChildListDelegate] with that same argument. The [new
/// GridView.builder] constructor's `itemBuilder` and `childCount` arguments
/// correspond to the [childrenDelegate] being a [SliverChildBuilderDelegate]
/// with the matching arguments.
///
/// The [new GridView.count] and [new GridView.extent] constructors create
/// custom grid delegates, and have equivalently named constructors on
/// [SliverGrid] to ease the transition: [new SliverGrid.count] and [new
/// SliverGrid.extent] respectively.
///
/// 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].
///
1280 1281 1282 1283
/// 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.
///
1284
/// Once code has been ported to use [CustomScrollView], other slivers, such as
1285
/// [SliverList] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
1286 1287
/// list.
///
1288
/// {@tool sample}
1289 1290 1291
/// This example demonstrates how to create a [GridView] with two columns. The
/// children are spaced apart using the [crossAxisSpacing] and [mainAxisSpacing]
/// properties.
1292
///
1293
/// ![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)
1294 1295
///
/// ```dart
1296
/// GridView.count(
1297
///   primary: false,
1298 1299 1300
///   padding: const EdgeInsets.all(20),
///   crossAxisSpacing: 10,
///   mainAxisSpacing: 10,
1301 1302
///   crossAxisCount: 2,
///   children: <Widget>[
1303 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
///     Container(
///       padding: const EdgeInsets.all(8),
///       child: const Text('He\'d have you all unravel at the'),
///       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],
///     ),
1333 1334 1335
///   ],
/// )
/// ```
1336
/// {@end-tool}
1337
///
1338
/// {@tool sample}
1339 1340 1341
/// This example shows how to create the same grid as the previous example
/// using a [CustomScrollView] and a [SliverGrid].
///
1342
/// ![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)
1343 1344
///
/// ```dart
1345
/// CustomScrollView(
1346 1347
///   primary: false,
///   slivers: <Widget>[
1348
///     SliverPadding(
1349
///       padding: const EdgeInsets.all(20),
1350
///       sliver: SliverGrid.count(
1351 1352
///         crossAxisSpacing: 10,
///         mainAxisSpacing: 10,
1353 1354
///         crossAxisCount: 2,
///         children: <Widget>[
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 1380 1381 1382 1383 1384
///           Container(
///             padding: const EdgeInsets.all(8),
///             child: const Text('He\'d have you all unravel at the'),
///             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],
///           ),
1385 1386 1387 1388 1389 1390
///         ],
///       ),
///     ),
///   ],
/// )
/// ```
1391
/// {@end-tool}
1392
///
1393 1394
/// See also:
///
1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405
///  * [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.
1406
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
1407
///    the scroll position without using a [ScrollController].
1408
class GridView extends BoxScrollView {
1409 1410 1411 1412
  /// Creates a scrollable, 2D array of widgets with a custom
  /// [SliverGridDelegate].
  ///
  /// The [gridDelegate] argument must not be null.
1413
  ///
1414 1415 1416 1417
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1418
  /// null.
1419
  GridView({
1420
    Key key,
1421 1422
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1423
    ScrollController controller,
1424
    bool primary,
1425
    ScrollPhysics physics,
1426
    bool shrinkWrap = false,
1427
    EdgeInsetsGeometry padding,
1428
    @required this.gridDelegate,
1429 1430
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1431
    bool addSemanticIndexes = true,
1432
    double cacheExtent,
1433
    List<Widget> children = const <Widget>[],
1434
    int semanticChildCount,
1435
  }) : assert(gridDelegate != null),
1436
       childrenDelegate = SliverChildListDelegate(
1437
         children,
1438
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1439
         addRepaintBoundaries: addRepaintBoundaries,
1440
         addSemanticIndexes: addSemanticIndexes,
1441
       ),
1442 1443 1444 1445 1446 1447 1448 1449 1450
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1451 1452
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? children.length,
1453
       );
1454

1455 1456 1457 1458 1459 1460
  /// 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.
  ///
1461
  /// Providing a non-null `itemCount` improves the ability of the [GridView] to
1462 1463
  /// estimate the maximum scroll extent.
  ///
1464 1465
  /// `itemBuilder` will be called only with indices greater than or equal to
  /// zero and less than `itemCount`.
1466 1467
  ///
  /// The [gridDelegate] argument must not be null.
1468
  ///
1469 1470 1471 1472 1473
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. Both must not
  /// be null.
1474 1475
  GridView.builder({
    Key key,
1476 1477
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1478 1479 1480
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
1481
    bool shrinkWrap = false,
1482
    EdgeInsetsGeometry padding,
1483 1484 1485
    @required this.gridDelegate,
    @required IndexedWidgetBuilder itemBuilder,
    int itemCount,
1486 1487
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1488
    bool addSemanticIndexes = true,
1489
    double cacheExtent,
1490
    int semanticChildCount,
1491
  }) : assert(gridDelegate != null),
1492
       childrenDelegate = SliverChildBuilderDelegate(
1493 1494
         itemBuilder,
         childCount: itemCount,
1495
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1496
         addRepaintBoundaries: addRepaintBoundaries,
1497
         addSemanticIndexes: addSemanticIndexes,
1498
       ),
1499 1500 1501 1502 1503 1504 1505 1506 1507
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1508 1509
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? itemCount,
1510
       );
1511

1512 1513 1514
  /// Creates a scrollable, 2D array of widgets with both a custom
  /// [SliverGridDelegate] and a custom [SliverChildDelegate].
  ///
1515 1516
  /// To use an [IndexedWidgetBuilder] callback to build children, either use
  /// a [SliverChildBuilderDelegate] or use the [GridView.builder] constructor.
1517 1518
  ///
  /// The [gridDelegate] and [childrenDelegate] arguments must not be null.
1519
  const GridView.custom({
1520
    Key key,
1521 1522
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1523
    ScrollController controller,
1524
    bool primary,
1525
    ScrollPhysics physics,
1526
    bool shrinkWrap = false,
1527
    EdgeInsetsGeometry padding,
1528 1529
    @required this.gridDelegate,
    @required this.childrenDelegate,
1530
    double cacheExtent,
1531
    int semanticChildCount,
1532
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543
  }) : assert(gridDelegate != null),
       assert(childrenDelegate != null),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1544 1545
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount,
1546
         dragStartBehavior: dragStartBehavior,
1547
       );
1548

1549 1550 1551 1552
  /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
  /// the cross axis.
  ///
  /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate].
1553
  ///
1554 1555 1556 1557
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1558 1559
  /// null.
  ///
1560 1561 1562
  /// See also:
  ///
  ///  * [new SliverGrid.count], the equivalent constructor for [SliverGrid].
1563 1564
  GridView.count({
    Key key,
1565 1566
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1567
    ScrollController controller,
1568
    bool primary,
1569
    ScrollPhysics physics,
1570
    bool shrinkWrap = false,
1571
    EdgeInsetsGeometry padding,
1572
    @required int crossAxisCount,
1573 1574 1575 1576 1577
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1578
    bool addSemanticIndexes = true,
1579
    double cacheExtent,
1580
    List<Widget> children = const <Widget>[],
1581
    int semanticChildCount,
1582
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1583
  }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
1584 1585 1586 1587 1588
         crossAxisCount: crossAxisCount,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
1589
       childrenDelegate = SliverChildListDelegate(
1590
         children,
1591
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1592
         addRepaintBoundaries: addRepaintBoundaries,
1593
         addSemanticIndexes: addSemanticIndexes,
1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607
       ),
       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,
       );
1608

1609 1610
  /// Creates a scrollable, 2D array of widgets with tiles that each have a
  /// maximum cross-axis extent.
1611 1612
  ///
  /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate].
1613
  ///
1614 1615 1616 1617
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1618 1619
  /// null.
  ///
1620 1621 1622
  /// See also:
  ///
  ///  * [new SliverGrid.extent], the equivalent constructor for [SliverGrid].
1623
  GridView.extent({
1624
    Key key,
1625 1626
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1627
    ScrollController controller,
1628
    bool primary,
1629
    ScrollPhysics physics,
1630
    bool shrinkWrap = false,
1631
    EdgeInsetsGeometry padding,
1632
    @required double maxCrossAxisExtent,
1633 1634 1635 1636 1637
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1638
    bool addSemanticIndexes = true,
1639
    List<Widget> children = const <Widget>[],
1640
    int semanticChildCount,
1641
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1642
  }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
1643 1644 1645 1646 1647
         maxCrossAxisExtent: maxCrossAxisExtent,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
1648
       childrenDelegate = SliverChildListDelegate(
1649
         children,
1650
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1651
         addRepaintBoundaries: addRepaintBoundaries,
1652
         addSemanticIndexes: addSemanticIndexes,
1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         semanticChildCount: semanticChildCount ?? children.length,
         dragStartBehavior: dragStartBehavior,
       );
1666

1667 1668
  /// A delegate that controls the layout of the children within the [GridView].
  ///
1669
  /// The [GridView], [GridView.builder], and [GridView.custom] constructors let you specify this
1670 1671
  /// delegate explicitly. The other constructors create a [gridDelegate]
  /// implicitly.
1672 1673
  final SliverGridDelegate gridDelegate;

1674 1675 1676 1677 1678
  /// 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.
1679
  final SliverChildDelegate childrenDelegate;
1680

1681
  @override
1682
  Widget buildChildLayout(BuildContext context) {
1683
    return SliverGrid(
1684
      delegate: childrenDelegate,
1685 1686
      gridDelegate: gridDelegate,
    );
Adam Barth's avatar
Adam Barth committed
1687 1688
  }
}