scroll_view.dart 69.7 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 12
import 'focus_manager.dart';
import 'focus_scope.dart';
13
import 'framework.dart';
14
import 'media_query.dart';
15
import 'notification_listener.dart';
16
import 'primary_scroll_controller.dart';
17
import 'scroll_controller.dart';
18
import 'scroll_notification.dart';
19
import 'scroll_physics.dart';
Adam Barth's avatar
Adam Barth committed
20 21
import 'scrollable.dart';
import 'sliver.dart';
22
import 'viewport.dart';
Adam Barth's avatar
Adam Barth committed
23

24 25 26
// Examples can assume:
// int itemCount;

27 28 29 30 31 32 33 34 35 36 37
/// A representation of how a [ScrollView] should dismiss the on-screen
/// keyboard.
enum ScrollViewKeyboardDismissBehavior {
  /// `manual` means there is no automatic dimissal of the on-screen keyboard.
  /// 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,
}

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

108 109 110
  /// The axis along which the scroll view scrolls.
  ///
  /// Defaults to [Axis.vertical].
Adam Barth's avatar
Adam Barth committed
111 112
  final Axis scrollDirection;

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

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

141 142 143
  /// Whether this is the primary scroll view associated with the parent
  /// [PrimaryScrollController].
  ///
144 145 146 147 148
  /// 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
149 150
  /// response to a tap in the status bar.
  ///
151 152
  /// Defaults to true when [scrollDirection] is [Axis.vertical] and
  /// [controller] is null.
153 154
  final bool primary;

155 156 157 158 159
  /// 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.
  ///
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
  /// 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(),
  /// ```
180 181 182 183 184 185 186 187 188
  ///
  /// 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
189 190
  final ScrollPhysics physics;

191 192 193 194 195 196 197 198 199 200 201 202 203 204
  /// 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.
205 206
  final bool shrinkWrap;

207 208 209 210
  /// 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
211 212
  /// the [axisDirection] relative to the [center]. This makes the [center] the
  /// inflection point of the growth direction.
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
  ///
  /// 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;

234 235 236
  /// {@macro flutter.rendering.viewport.cacheExtent}
  final double cacheExtent;

237 238 239 240
  /// 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,
241
  /// while the [ListView.separated] constructor will use half that amount.
242 243 244 245 246 247 248 249 250 251
  ///
  /// 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;

252 253 254
  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
  final DragStartBehavior dragStartBehavior;

255 256 257 258
  /// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will
  /// dismiss the keyboard automatically.
  final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;

259 260 261 262 263
  /// Returns the [AxisDirection] in which the scroll view scrolls.
  ///
  /// Combines the [scrollDirection] with the [reverse] boolean to obtain the
  /// concrete [AxisDirection].
  ///
264
  /// If the [scrollDirection] is [Axis.horizontal], the ambient
265
  /// [Directionality] is also considered when selecting the concrete
266 267 268 269
  /// [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].
270 271
  @protected
  AxisDirection getDirection(BuildContext context) {
272
    return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
273 274
  }

275 276
  /// Build the list of widgets to place inside the viewport.
  ///
277 278
  /// Subclasses should override this method to build the slivers for the inside
  /// of the viewport.
279
  @protected
280
  List<Widget> buildSlivers(BuildContext context);
Adam Barth's avatar
Adam Barth committed
281

282 283 284 285 286
  /// 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.
287 288 289 290 291 292 293 294
  ///
  /// 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].
295 296 297 298 299 300 301 302
  @protected
  Widget buildViewport(
    BuildContext context,
    ViewportOffset offset,
    AxisDirection axisDirection,
    List<Widget> slivers,
  ) {
    if (shrinkWrap) {
303
      return ShrinkWrappingViewport(
304 305 306 307 308
        axisDirection: axisDirection,
        offset: offset,
        slivers: slivers,
      );
    }
309
    return Viewport(
310 311 312
      axisDirection: axisDirection,
      offset: offset,
      slivers: slivers,
313
      cacheExtent: cacheExtent,
314 315
      center: center,
      anchor: anchor,
316 317 318
    );
  }

319 320
  @override
  Widget build(BuildContext context) {
321 322
    final List<Widget> slivers = buildSlivers(context);
    final AxisDirection axisDirection = getDirection(context);
323

324 325
    final ScrollController scrollController =
        primary ? PrimaryScrollController.of(context) : controller;
326
    final Scrollable scrollable = Scrollable(
327
      dragStartBehavior: dragStartBehavior,
328
      axisDirection: axisDirection,
329
      controller: scrollController,
Adam Barth's avatar
Adam Barth committed
330
      physics: physics,
331
      semanticChildCount: semanticChildCount,
332
      viewportBuilder: (BuildContext context, ViewportOffset offset) {
333 334
        return buildViewport(context, offset, axisDirection, slivers);
      },
335
    );
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
    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;
    }
354
  }
355 356

  @override
357 358
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
359 360 361 362 363 364
    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));
365 366 367
  }
}

368 369 370 371 372 373 374 375
/// 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].
///
376 377
/// [Widget]s in these [slivers] must produce [RenderSliver] objects.
///
378 379 380
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
381 382
/// {@animation 400 376 https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_scroll_view.mp4}
///
383
/// {@tool snippet}
384 385 386 387 388
///
/// This sample code shows a scroll view that contains a flexible pinned app
/// bar, a grid, and an infinite list.
///
/// ```dart
389
/// CustomScrollView(
390 391 392 393
///   slivers: <Widget>[
///     const SliverAppBar(
///       pinned: true,
///       expandedHeight: 250.0,
394 395
///       flexibleSpace: FlexibleSpaceBar(
///         title: Text('Demo'),
396 397
///       ),
///     ),
398 399
///     SliverGrid(
///       gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
400 401 402 403 404
///         maxCrossAxisExtent: 200.0,
///         mainAxisSpacing: 10.0,
///         crossAxisSpacing: 10.0,
///         childAspectRatio: 4.0,
///       ),
405
///       delegate: SliverChildBuilderDelegate(
406
///         (BuildContext context, int index) {
407
///           return Container(
408
///             alignment: Alignment.center,
409
///             color: Colors.teal[100 * (index % 9)],
410
///             child: Text('Grid Item $index'),
411 412 413 414 415
///           );
///         },
///         childCount: 20,
///       ),
///     ),
416
///     SliverFixedExtentList(
417
///       itemExtent: 50.0,
418
///       delegate: SliverChildBuilderDelegate(
419
///         (BuildContext context, int index) {
420
///           return Container(
421
///             alignment: Alignment.center,
422
///             color: Colors.lightBlue[100 * (index % 9)],
423
///             child: Text('List Item $index'),
424 425 426 427 428 429 430
///           );
///         },
///       ),
///     ),
///   ],
/// )
/// ```
431
/// {@end-tool}
432
///
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 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
/// {@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}
///
501 502 503 504 505
/// ## 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
506
/// this announcement, the scroll view needs three pieces of information:
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
///
///   * 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].
///
522
/// This semantic index is not necessarily the same as the index of the widget in
Ian Hickson's avatar
Ian Hickson committed
523
/// the scrollable, because some widgets may not contribute semantic
524
/// information. Consider a [ListView.separated]: every other widget is a
525 526 527
/// 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
528
/// widgets. (The [ListView.separated] constructor handles this
Ian Hickson's avatar
Ian Hickson committed
529
/// automatically; this is only used here as an example.)
530 531 532 533 534
///
/// 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].
///
535 536 537 538 539 540 541 542 543 544
/// 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.
545
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
546
///    the scroll position without using a [ScrollController].
547 548
///  * [IndexedSemantics], which allows annotating child lists with an index
///    for scroll announcements.
Adam Barth's avatar
Adam Barth committed
549
class CustomScrollView extends ScrollView {
550 551
  /// Creates a [ScrollView] that creates custom scroll effects using slivers.
  ///
552
  /// See the [ScrollView] constructor for more details on these arguments.
553
  const CustomScrollView({
Adam Barth's avatar
Adam Barth committed
554
    Key key,
555 556
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
557
    ScrollController controller,
558
    bool primary,
Adam Barth's avatar
Adam Barth committed
559
    ScrollPhysics physics,
560
    bool shrinkWrap = false,
561 562
    Key center,
    double anchor = 0.0,
563
    double cacheExtent,
564
    this.slivers = const <Widget>[],
565
    int semanticChildCount,
566
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
Adam Barth's avatar
Adam Barth committed
567 568 569 570
  }) : super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
571
    controller: controller,
572
    primary: primary,
Adam Barth's avatar
Adam Barth committed
573 574
    physics: physics,
    shrinkWrap: shrinkWrap,
575 576
    center: center,
    anchor: anchor,
577
    cacheExtent: cacheExtent,
578
    semanticChildCount: semanticChildCount,
579
    dragStartBehavior: dragStartBehavior,
Adam Barth's avatar
Adam Barth committed
580 581
  );

582
  /// The slivers to place inside the viewport.
Adam Barth's avatar
Adam Barth committed
583 584 585 586 587 588
  final List<Widget> slivers;

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

589
/// A [ScrollView] that uses a single child layout model.
590 591 592 593 594 595 596
///
/// 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.
597
abstract class BoxScrollView extends ScrollView {
598 599 600
  /// Creates a [ScrollView] uses a single child layout model.
  ///
  /// If the [primary] argument is true, the [controller] must be null.
601
  const BoxScrollView({
602
    Key key,
603 604
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
605
    ScrollController controller,
606
    bool primary,
607
    ScrollPhysics physics,
608
    bool shrinkWrap = false,
609
    this.padding,
610
    double cacheExtent,
611
    int semanticChildCount,
612
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
613
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
614 615 616 617
  }) : super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
618
    controller: controller,
619
    primary: primary,
620 621
    physics: physics,
    shrinkWrap: shrinkWrap,
622
    cacheExtent: cacheExtent,
623
    semanticChildCount: semanticChildCount,
624
    dragStartBehavior: dragStartBehavior,
625
    keyboardDismissBehavior: keyboardDismissBehavior,
626 627
  );

628
  /// The amount of space by which to inset the children.
629
  final EdgeInsetsGeometry padding;
630 631 632 633

  @override
  List<Widget> buildSlivers(BuildContext context) {
    Widget sliver = buildChildLayout(context);
634 635 636 637 638 639 640 641 642 643 644 645 646 647
    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.
648
        sliver = MediaQuery(
649 650 651 652 653 654 655 656 657 658 659
          data: mediaQuery.copyWith(
            padding: scrollDirection == Axis.vertical
                ? mediaQueryHorizontalPadding
                : mediaQueryVerticalPadding,
          ),
          child: sliver,
        );
      }
    }

    if (effectivePadding != null)
660
      sliver = SliverPadding(padding: effectivePadding, sliver: sliver);
661 662 663
    return <Widget>[ sliver ];
  }

664
  /// Subclasses should override this method to build the layout model.
665 666 667 668
  @protected
  Widget buildChildLayout(BuildContext context);

  @override
669 670
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
671
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
672 673 674
  }
}

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

1040 1041 1042 1043 1044 1045
  /// 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.
  ///
1046
  /// Providing a non-null `itemCount` improves the ability of the [ListView] to
1047 1048
  /// estimate the maximum scroll extent.
  ///
1049 1050 1051 1052 1053 1054
  /// 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
1055 1056 1057
  /// [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.
1058
  ///
1059 1060 1061
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1062 1063 1064 1065
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None may be
  /// null.
1066 1067 1068 1069
  ///
  /// [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].
1070 1071
  ListView.builder({
    Key key,
1072 1073
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1074
    ScrollController controller,
1075
    bool primary,
1076
    ScrollPhysics physics,
1077
    bool shrinkWrap = false,
1078
    EdgeInsetsGeometry padding,
1079
    this.itemExtent,
1080
    @required IndexedWidgetBuilder itemBuilder,
1081
    int itemCount,
1082 1083
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1084
    bool addSemanticIndexes = true,
1085
    double cacheExtent,
1086
    int semanticChildCount,
1087
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1088 1089 1090
  }) : assert(itemCount == null || itemCount >= 0),
       assert(semanticChildCount == null || semanticChildCount <= itemCount),
       childrenDelegate = SliverChildBuilderDelegate(
1091 1092
         itemBuilder,
         childCount: itemCount,
1093
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1094
         addRepaintBoundaries: addRepaintBoundaries,
1095
         addSemanticIndexes: addSemanticIndexes,
1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? itemCount,
         dragStartBehavior: dragStartBehavior,
       );
1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130

  /// 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
1131
  /// efficient to use the [ListView] constructor.
1132
  ///
1133
  /// {@tool snippet}
1134 1135 1136 1137 1138
  ///
  /// This example shows how to create [ListView] whose [ListTile] list items
  /// are separated by [Divider]s.
  ///
  /// ```dart
1139
  /// ListView.separated(
1140
  ///   itemCount: 25,
1141
  ///   separatorBuilder: (BuildContext context, int index) => Divider(),
1142
  ///   itemBuilder: (BuildContext context, int index) {
1143 1144
  ///     return ListTile(
  ///       title: Text('item $index'),
1145 1146 1147 1148
  ///     );
  ///   },
  /// )
  /// ```
1149
  /// {@end-tool}
1150 1151 1152 1153
  ///
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1154 1155 1156 1157
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None may be
  /// null.
1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171
  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,
1172
    bool addSemanticIndexes = true,
1173
    double cacheExtent,
1174
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1175 1176 1177 1178
  }) : assert(itemBuilder != null),
       assert(separatorBuilder != null),
       assert(itemCount != null && itemCount >= 0),
       itemExtent = null,
1179
       childrenDelegate = SliverChildBuilderDelegate(
1180 1181
         (BuildContext context, int index) {
           final int itemIndex = index ~/ 2;
1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194
           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;
1195
         },
1196
         childCount: _computeActualChildCount(itemCount),
1197 1198
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
1199 1200 1201
         addSemanticIndexes: addSemanticIndexes,
         semanticIndexCallback: (Widget _, int index) {
           return index.isEven ? index ~/ 2 : null;
1202
         },
1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
1214
         semanticChildCount: itemCount,
1215
         keyboardDismissBehavior: keyboardDismissBehavior,
1216
       );
1217

1218 1219 1220 1221
  /// 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.
1222
  ///
1223
  /// {@tool snippet}
1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 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 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299
  ///
  /// 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}
1300
  const ListView.custom({
1301
    Key key,
1302 1303
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1304
    ScrollController controller,
1305
    bool primary,
1306
    ScrollPhysics physics,
1307
    bool shrinkWrap = false,
1308
    EdgeInsetsGeometry padding,
1309 1310
    this.itemExtent,
    @required this.childrenDelegate,
1311
    double cacheExtent,
1312
    int semanticChildCount,
1313 1314 1315 1316 1317 1318 1319 1320 1321 1322
  }) : 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
1323
         cacheExtent: cacheExtent,
1324
         semanticChildCount: semanticChildCount,
1325
       );
1326

1327 1328 1329 1330 1331 1332 1333
  /// 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.
1334 1335
  final double itemExtent;

1336 1337 1338 1339 1340 1341
  /// 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.
1342
  final SliverChildDelegate childrenDelegate;
1343 1344 1345 1346

  @override
  Widget buildChildLayout(BuildContext context) {
    if (itemExtent != null) {
1347
      return SliverFixedExtentList(
1348 1349 1350 1351
        delegate: childrenDelegate,
        itemExtent: itemExtent,
      );
    }
1352
    return SliverList(delegate: childrenDelegate);
1353 1354 1355
  }

  @override
1356 1357
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1358
    properties.add(DoubleProperty('itemExtent', itemExtent, defaultValue: null));
1359
  }
1360

1361 1362
  // Helper method to compute the actual child count for the separated constructor.
  static int _computeActualChildCount(int itemCount) {
1363 1364
    return math.max(0, itemCount * 2 - 1);
  }
1365 1366
}

1367 1368
/// A scrollable, 2D array of widgets.
///
1369 1370 1371
/// The main axis direction of a grid is the direction in which it scrolls (the
/// [scrollDirection]).
///
1372 1373 1374
/// 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
1375
/// cross-axis extent. A custom [SliverGridDelegate] can produce an arbitrary 2D
1376 1377 1378 1379
/// arrangement of children, including arrangements that are unaligned or
/// overlapping.
///
/// To create a grid with a large (or infinite) number of children, use the
1380 1381 1382 1383 1384
/// [GridView.builder] constructor with either a
/// [SliverGridDelegateWithFixedCrossAxisCount] or a
/// [SliverGridDelegateWithMaxCrossAxisExtent] for the [gridDelegate].
///
/// To use a custom [SliverChildDelegate], use [GridView.custom].
1385 1386
///
/// To create a linear array of children, use a [ListView].
1387
///
1388 1389 1390
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
1391 1392 1393
/// ## Transitioning to [CustomScrollView]
///
/// A [GridView] is basically a [CustomScrollView] with a single [SliverGrid] in
1394
/// its [CustomScrollView.slivers] property.
1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411
///
/// 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.
///
1412
/// The [GridView], [GridView.count], and [GridView.extent]
1413
/// constructors' `children` arguments correspond to the [childrenDelegate]
1414 1415
/// being a [SliverChildListDelegate] with that same argument. The
/// [GridView.builder] constructor's `itemBuilder` and `childCount` arguments
1416 1417 1418
/// correspond to the [childrenDelegate] being a [SliverChildBuilderDelegate]
/// with the matching arguments.
///
1419
/// The [GridView.count] and [GridView.extent] constructors create
1420
/// custom grid delegates, and have equivalently named constructors on
1421 1422
/// [SliverGrid] to ease the transition: [SliverGrid.count] and
/// [SliverGrid.extent] respectively.
1423 1424 1425 1426 1427
///
/// 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].
///
1428 1429 1430 1431
/// 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.
///
1432
/// Once code has been ported to use [CustomScrollView], other slivers, such as
1433
/// [SliverList] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
1434 1435
/// list.
///
1436
/// {@tool snippet}
1437 1438 1439
/// This example demonstrates how to create a [GridView] with two columns. The
/// children are spaced apart using the [crossAxisSpacing] and [mainAxisSpacing]
/// properties.
1440
///
1441
/// ![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)
1442 1443
///
/// ```dart
1444
/// GridView.count(
1445
///   primary: false,
1446 1447 1448
///   padding: const EdgeInsets.all(20),
///   crossAxisSpacing: 10,
///   mainAxisSpacing: 10,
1449 1450
///   crossAxisCount: 2,
///   children: <Widget>[
1451 1452
///     Container(
///       padding: const EdgeInsets.all(8),
1453
///       child: const Text("He'd have you all unravel at the"),
1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480
///       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],
///     ),
1481 1482 1483
///   ],
/// )
/// ```
1484
/// {@end-tool}
1485
///
1486
/// {@tool snippet}
1487 1488 1489
/// This example shows how to create the same grid as the previous example
/// using a [CustomScrollView] and a [SliverGrid].
///
1490
/// ![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)
1491 1492
///
/// ```dart
1493
/// CustomScrollView(
1494 1495
///   primary: false,
///   slivers: <Widget>[
1496
///     SliverPadding(
1497
///       padding: const EdgeInsets.all(20),
1498
///       sliver: SliverGrid.count(
1499 1500
///         crossAxisSpacing: 10,
///         mainAxisSpacing: 10,
1501 1502
///         crossAxisCount: 2,
///         children: <Widget>[
1503 1504
///           Container(
///             padding: const EdgeInsets.all(8),
1505
///             child: const Text("He'd have you all unravel at the"),
1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532
///             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],
///           ),
1533 1534 1535 1536 1537 1538
///         ],
///       ),
///     ),
///   ],
/// )
/// ```
1539
/// {@end-tool}
1540
///
1541 1542
/// See also:
///
1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553
///  * [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.
1554
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
1555
///    the scroll position without using a [ScrollController].
1556
///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
1557
class GridView extends BoxScrollView {
1558 1559 1560 1561
  /// Creates a scrollable, 2D array of widgets with a custom
  /// [SliverGridDelegate].
  ///
  /// The [gridDelegate] argument must not be null.
1562
  ///
1563 1564 1565 1566
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1567
  /// null.
1568
  GridView({
1569
    Key key,
1570 1571
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1572
    ScrollController controller,
1573
    bool primary,
1574
    ScrollPhysics physics,
1575
    bool shrinkWrap = false,
1576
    EdgeInsetsGeometry padding,
1577
    @required this.gridDelegate,
1578 1579
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1580
    bool addSemanticIndexes = true,
1581
    double cacheExtent,
1582
    List<Widget> children = const <Widget>[],
1583
    int semanticChildCount,
1584
  }) : assert(gridDelegate != null),
1585
       childrenDelegate = SliverChildListDelegate(
1586
         children,
1587
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1588
         addRepaintBoundaries: addRepaintBoundaries,
1589
         addSemanticIndexes: addSemanticIndexes,
1590
       ),
1591 1592 1593 1594 1595 1596 1597 1598 1599
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1600 1601
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? children.length,
1602
       );
1603

1604 1605 1606 1607 1608 1609
  /// 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.
  ///
1610
  /// Providing a non-null `itemCount` improves the ability of the [GridView] to
1611 1612
  /// estimate the maximum scroll extent.
  ///
1613 1614
  /// `itemBuilder` will be called only with indices greater than or equal to
  /// zero and less than `itemCount`.
1615 1616
  ///
  /// The [gridDelegate] argument must not be null.
1617
  ///
1618 1619 1620 1621 1622
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. Both must not
  /// be null.
1623 1624
  GridView.builder({
    Key key,
1625 1626
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1627 1628 1629
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
1630
    bool shrinkWrap = false,
1631
    EdgeInsetsGeometry padding,
1632 1633 1634
    @required this.gridDelegate,
    @required IndexedWidgetBuilder itemBuilder,
    int itemCount,
1635 1636
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1637
    bool addSemanticIndexes = true,
1638
    double cacheExtent,
1639
    int semanticChildCount,
1640
  }) : assert(gridDelegate != null),
1641
       childrenDelegate = SliverChildBuilderDelegate(
1642 1643
         itemBuilder,
         childCount: itemCount,
1644
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1645
         addRepaintBoundaries: addRepaintBoundaries,
1646
         addSemanticIndexes: addSemanticIndexes,
1647
       ),
1648 1649 1650 1651 1652 1653 1654 1655 1656
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1657 1658
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? itemCount,
1659
       );
1660

1661 1662 1663
  /// Creates a scrollable, 2D array of widgets with both a custom
  /// [SliverGridDelegate] and a custom [SliverChildDelegate].
  ///
1664 1665
  /// To use an [IndexedWidgetBuilder] callback to build children, either use
  /// a [SliverChildBuilderDelegate] or use the [GridView.builder] constructor.
1666 1667
  ///
  /// The [gridDelegate] and [childrenDelegate] arguments must not be null.
1668
  const GridView.custom({
1669
    Key key,
1670 1671
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1672
    ScrollController controller,
1673
    bool primary,
1674
    ScrollPhysics physics,
1675
    bool shrinkWrap = false,
1676
    EdgeInsetsGeometry padding,
1677 1678
    @required this.gridDelegate,
    @required this.childrenDelegate,
1679
    double cacheExtent,
1680
    int semanticChildCount,
1681
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692
  }) : assert(gridDelegate != null),
       assert(childrenDelegate != null),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1693 1694
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount,
1695
         dragStartBehavior: dragStartBehavior,
1696
       );
1697

1698 1699 1700 1701
  /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
  /// the cross axis.
  ///
  /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate].
1702
  ///
1703 1704 1705 1706
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1707 1708
  /// null.
  ///
1709 1710
  /// See also:
  ///
1711
  ///  * [SliverGrid.count], the equivalent constructor for [SliverGrid].
1712 1713
  GridView.count({
    Key key,
1714 1715
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1716
    ScrollController controller,
1717
    bool primary,
1718
    ScrollPhysics physics,
1719
    bool shrinkWrap = false,
1720
    EdgeInsetsGeometry padding,
1721
    @required int crossAxisCount,
1722 1723 1724 1725 1726
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1727
    bool addSemanticIndexes = true,
1728
    double cacheExtent,
1729
    List<Widget> children = const <Widget>[],
1730
    int semanticChildCount,
1731
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1732
  }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
1733 1734 1735 1736 1737
         crossAxisCount: crossAxisCount,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
1738
       childrenDelegate = SliverChildListDelegate(
1739
         children,
1740
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1741
         addRepaintBoundaries: addRepaintBoundaries,
1742
         addSemanticIndexes: addSemanticIndexes,
1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756
       ),
       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,
       );
1757

1758 1759
  /// Creates a scrollable, 2D array of widgets with tiles that each have a
  /// maximum cross-axis extent.
1760 1761
  ///
  /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate].
1762
  ///
1763 1764 1765 1766
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1767 1768
  /// null.
  ///
1769 1770
  /// See also:
  ///
1771
  ///  * [SliverGrid.extent], the equivalent constructor for [SliverGrid].
1772
  GridView.extent({
1773
    Key key,
1774 1775
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1776
    ScrollController controller,
1777
    bool primary,
1778
    ScrollPhysics physics,
1779
    bool shrinkWrap = false,
1780
    EdgeInsetsGeometry padding,
1781
    @required double maxCrossAxisExtent,
1782 1783 1784 1785 1786
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1787
    bool addSemanticIndexes = true,
1788
    List<Widget> children = const <Widget>[],
1789
    int semanticChildCount,
1790
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1791
  }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
1792 1793 1794 1795 1796
         maxCrossAxisExtent: maxCrossAxisExtent,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
1797
       childrenDelegate = SliverChildListDelegate(
1798
         children,
1799
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1800
         addRepaintBoundaries: addRepaintBoundaries,
1801
         addSemanticIndexes: addSemanticIndexes,
1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         semanticChildCount: semanticChildCount ?? children.length,
         dragStartBehavior: dragStartBehavior,
       );
1815

1816 1817
  /// A delegate that controls the layout of the children within the [GridView].
  ///
1818
  /// The [GridView], [GridView.builder], and [GridView.custom] constructors let you specify this
1819 1820
  /// delegate explicitly. The other constructors create a [gridDelegate]
  /// implicitly.
1821 1822
  final SliverGridDelegate gridDelegate;

1823 1824 1825 1826 1827
  /// 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.
1828
  final SliverChildDelegate childrenDelegate;
1829

1830
  @override
1831
  Widget buildChildLayout(BuildContext context) {
1832
    return SliverGrid(
1833
      delegate: childrenDelegate,
1834 1835
      gridDelegate: gridDelegate,
    );
Adam Barth's avatar
Adam Barth committed
1836 1837
  }
}