scroll_view.dart 39.5 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.

xster's avatar
xster committed
5
import 'package:flutter/foundation.dart';
Adam Barth's avatar
Adam Barth committed
6 7 8
import 'package:flutter/rendering.dart';

import 'basic.dart';
9 10
import 'framework.dart';
import 'primary_scroll_controller.dart';
11
import 'scroll_controller.dart';
12
import 'scroll_physics.dart';
Adam Barth's avatar
Adam Barth committed
13 14
import 'scrollable.dart';
import 'sliver.dart';
15
import 'viewport.dart';
Adam Barth's avatar
Adam Barth committed
16

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

69 70 71
  /// The axis along which the scroll view scrolls.
  ///
  /// Defaults to [Axis.vertical].
Adam Barth's avatar
Adam Barth committed
72 73
  final Axis scrollDirection;

74
  /// Whether the scroll view scrolls in the reading direction.
75 76 77 78 79 80
  ///
  /// 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
81
  /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
82 83 84 85
  /// scrolls from top to bottom when [reverse] is false and from bottom to top
  /// when [reverse] is true.
  ///
  /// Defaults to false.
86 87
  final bool reverse;

88 89 90 91
  /// An object that can be used to control the position to which this scroll
  /// view is scrolled.
  ///
  /// Must be null if [primary] is true.
92 93 94 95 96 97 98 99
  ///
  /// 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]).
100 101
  final ScrollController controller;

102 103 104
  /// Whether this is the primary scroll view associated with the parent
  /// [PrimaryScrollController].
  ///
105 106 107 108 109
  /// 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
110 111
  /// response to a tap in the status bar.
  ///
112 113
  /// Defaults to true when [scrollDirection] is [Axis.vertical] and
  /// [controller] is null.
114 115
  final bool primary;

116 117 118 119 120
  /// 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.
  ///
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
  /// 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(),
  /// ```
141 142 143 144 145 146 147 148 149
  ///
  /// 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
150 151
  final ScrollPhysics physics;

152 153 154 155 156 157 158 159 160 161 162 163 164 165
  /// 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.
166 167
  final bool shrinkWrap;

168 169 170 171 172
  /// Returns the [AxisDirection] in which the scroll view scrolls.
  ///
  /// Combines the [scrollDirection] with the [reverse] boolean to obtain the
  /// concrete [AxisDirection].
  ///
173 174 175 176 177 178
  /// If the [scrollDirection] is [Axis.horizontal], the ambient
  /// [Directionality] is also consided when selecting the concrete
  /// [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].
179 180
  @protected
  AxisDirection getDirection(BuildContext context) {
181
    return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
182 183
  }

184 185
  /// Subclasses should override this method to build the slivers for the inside
  /// of the viewport.
186
  @protected
187
  List<Widget> buildSlivers(BuildContext context);
Adam Barth's avatar
Adam Barth committed
188

189 190
  @override
  Widget build(BuildContext context) {
191 192
    final List<Widget> slivers = buildSlivers(context);
    final AxisDirection axisDirection = getDirection(context);
193

194
    final ScrollController scrollController = primary
195 196
        ? PrimaryScrollController.of(context)
        : controller;
197
    final Scrollable scrollable = new Scrollable(
198
      axisDirection: axisDirection,
199
      controller: scrollController,
Adam Barth's avatar
Adam Barth committed
200
      physics: physics,
201 202 203 204 205
      viewportBuilder: (BuildContext context, ViewportOffset offset) {
        if (shrinkWrap) {
          return new ShrinkWrappingViewport(
            axisDirection: axisDirection,
            offset: offset,
206
            slivers: slivers,
207 208
          );
        } else {
Adam Barth's avatar
Adam Barth committed
209
          return new Viewport(
210 211
            axisDirection: axisDirection,
            offset: offset,
212
            slivers: slivers,
213 214 215
          );
        }
      }
216
    );
217 218 219
    return primary && scrollController != null
      ? new PrimaryScrollController.none(child: scrollable)
      : scrollable;
220
  }
221 222

  @override
223
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
224 225 226 227 228 229 230
    super.debugFillProperties(description);
    description.add(new EnumProperty<Axis>('scrollDirection', scrollDirection));
    description.add(new FlagProperty('reverse', value: reverse, ifTrue: 'reversed', showName: true));
    description.add(new DiagnosticsProperty<ScrollController>('controller', controller, showName: false, defaultValue: null));
    description.add(new FlagProperty('primary', value: primary, ifTrue: 'using primary controller', showName: true));
    description.add(new DiagnosticsProperty<ScrollPhysics>('physics', physics, showName: false, defaultValue: null));
    description.add(new FlagProperty('shrinkWrap', value: shrinkWrap, ifTrue: 'shrink-wrapping', showName: true));
231 232 233
  }
}

234 235 236 237 238 239 240 241
/// 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].
///
242 243
/// [Widget]s in these [slivers] must produce [RenderSliver] objects.
///
244 245 246
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
/// ## Sample code
///
/// This sample code shows a scroll view that contains a flexible pinned app
/// bar, a grid, and an infinite list.
///
/// ```dart
/// new CustomScrollView(
///   slivers: <Widget>[
///     const SliverAppBar(
///       pinned: true,
///       expandedHeight: 250.0,
///       flexibleSpace: const FlexibleSpaceBar(
///         title: const Text('Demo'),
///       ),
///     ),
///     new SliverGrid(
///       gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent(
///         maxCrossAxisExtent: 200.0,
///         mainAxisSpacing: 10.0,
///         crossAxisSpacing: 10.0,
///         childAspectRatio: 4.0,
///       ),
///       delegate: new SliverChildBuilderDelegate(
///         (BuildContext context, int index) {
///           return new Container(
272
///             alignment: Alignment.center,
273 274 275 276 277 278 279 280 281 282 283 284
///             color: Colors.teal[100 * (index % 9)],
///             child: new Text('grid item $index'),
///           );
///         },
///         childCount: 20,
///       ),
///     ),
///     new SliverFixedExtentList(
///       itemExtent: 50.0,
///       delegate: new SliverChildBuilderDelegate(
///         (BuildContext context, int index) {
///           return new Container(
285
///             alignment: Alignment.center,
286 287 288 289 290 291 292 293 294 295
///             color: Colors.lightBlue[100 * (index % 9)],
///             child: new Text('list item $index'),
///           );
///         },
///       ),
///     ),
///   ],
/// )
/// ```
///
296 297 298 299 300 301 302 303 304 305
/// 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.
306
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
307
///    the scroll position without using a [ScrollController].
Adam Barth's avatar
Adam Barth committed
308
class CustomScrollView extends ScrollView {
309 310 311
  /// Creates a [ScrollView] that creates custom scroll effects using slivers.
  ///
  /// If the [primary] argument is true, the [controller] must be null.
Adam Barth's avatar
Adam Barth committed
312 313 314 315
  CustomScrollView({
    Key key,
    Axis scrollDirection: Axis.vertical,
    bool reverse: false,
316
    ScrollController controller,
317
    bool primary,
Adam Barth's avatar
Adam Barth committed
318 319 320 321 322 323 324
    ScrollPhysics physics,
    bool shrinkWrap: false,
    this.slivers: const <Widget>[],
  }) : super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
325
    controller: controller,
326
    primary: primary,
Adam Barth's avatar
Adam Barth committed
327 328 329 330
    physics: physics,
    shrinkWrap: shrinkWrap,
  );

331
  /// The slivers to place inside the viewport.
Adam Barth's avatar
Adam Barth committed
332 333 334 335 336 337
  final List<Widget> slivers;

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

338 339 340 341 342 343 344 345
/// A [ScrollView] uses a single child layout model.
///
/// 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.
346
abstract class BoxScrollView extends ScrollView {
347 348 349
  /// Creates a [ScrollView] uses a single child layout model.
  ///
  /// If the [primary] argument is true, the [controller] must be null.
350 351 352 353
  BoxScrollView({
    Key key,
    Axis scrollDirection: Axis.vertical,
    bool reverse: false,
354
    ScrollController controller,
355
    bool primary,
356 357 358 359 360 361 362
    ScrollPhysics physics,
    bool shrinkWrap: false,
    this.padding,
  }) : super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
363
    controller: controller,
364
    primary: primary,
365 366 367 368
    physics: physics,
    shrinkWrap: shrinkWrap,
  );

369
  /// The amount of space by which to inset the children.
370
  final EdgeInsetsGeometry padding;
371 372 373 374 375

  @override
  List<Widget> buildSlivers(BuildContext context) {
    Widget sliver = buildChildLayout(context);
    if (padding != null)
376
      sliver = new SliverPadding(padding: padding, sliver: sliver);
377 378 379
    return <Widget>[ sliver ];
  }

380
  /// Subclasses should override this method to build the layout model.
381 382 383 384
  @protected
  Widget buildChildLayout(BuildContext context);

  @override
385
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
386
    super.debugFillProperties(description);
387
    description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
388 389 390
  }
}

391
/// A scrollable list of widgets arranged linearly.
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
///
/// [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.
///
/// There are three options for constructing a [ListView]:
///
///  1. The default constuctor takes an explict [List<Widget>] of children. 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.
///
///  2. The [ListView.builder] 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
///     only for those children that are actually visible.
///
///  3. The [ListView.custom] takes a [SliverChildDelegate], which provides the
///     ability to customize additional aspects of the child model. For example,
///     a [SliverChildDelegate] can control the algorithm used to estimate the
///     size of children that are not actually visible.
420
///
421 422 423
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
424 425 426 427 428 429 430 431 432 433 434 435 436 437
/// ## Sample code
///
/// An infinite list of children:
///
/// ```dart
/// new ListView.builder(
///   padding: new EdgeInsets.all(8.0),
///   itemExtent: 20.0,
///   itemBuilder: (BuildContext context, int index) {
///     return new Text('entry $index');
///   },
/// )
/// ```
///
438 439 440
/// ## Transitioning to [CustomScrollView]
///
/// A [ListView] is basically a [CustomScrollView] with a single [SliverList] in
441
/// its [CustomScrollView.slivers] property.
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
///
/// 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
/// `childCount` arguments correspond to the [childrenDelegate] being a
/// [SliverChildBuilderDelegate] with the matching arguments.
///
/// 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].
///
/// Once code has been ported to use [CustomScrollView], other slivers, such as
/// [SliverGrid] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
/// list.
///
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
/// ### Sample code
///
/// Here are two brief snippets showing a [ListView] and its equivalent using
/// [CustomScrollView]:
///
/// ```dart
/// new ListView(
///   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'),
///   ],
/// )
/// ```
///
/// ```dart
/// new CustomScrollView(
///   shrinkWrap: true,
///   slivers: <Widget>[
///     new SliverPadding(
///       padding: const EdgeInsets.all(20.0),
///       sliver: new SliverList(
///         delegate: new SliverChildListDelegate(
///           <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'),
///           ],
///         ),
///       ),
///     ),
///   ],
/// )
/// ```
///
511 512
/// See also:
///
513 514 515 516 517 518 519
///  * [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.
520 521
///  * [ListBody], which arranges its children in a similar manner, but without
///    scrolling.
522
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
523
///    the scroll position without using a [ScrollController].
524
class ListView extends BoxScrollView {
525 526 527 528 529 530
  /// 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.
531 532 533
  ///
  /// It is usually more efficient to create children on demand using [new
  /// ListView.builder].
534
  ///
535 536 537 538
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
539
  /// null.
540 541 542 543
  ListView({
    Key key,
    Axis scrollDirection: Axis.vertical,
    bool reverse: false,
544
    ScrollController controller,
545
    bool primary,
546 547
    ScrollPhysics physics,
    bool shrinkWrap: false,
548
    EdgeInsetsGeometry padding,
549
    this.itemExtent,
550
    bool addAutomaticKeepAlives: true,
551
    bool addRepaintBoundaries: true,
552
    List<Widget> children: const <Widget>[],
553 554
  }) : childrenDelegate = new SliverChildListDelegate(
         children,
555
         addAutomaticKeepAlives: addAutomaticKeepAlives,
556 557
         addRepaintBoundaries: addRepaintBoundaries,
       ), super(
558 559 560
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
561
    controller: controller,
562
    primary: primary,
563 564 565 566 567
    physics: physics,
    shrinkWrap: shrinkWrap,
    padding: padding,
  );

568 569 570 571 572 573
  /// 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.
  ///
574
  /// Providing a non-null `itemCount` improves the ability of the [ListView] to
575 576
  /// estimate the maximum scroll extent.
  ///
577 578 579 580 581 582 583 584 585
  /// 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.
586
  ///
587 588 589 590 591
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. Both must not
  /// be null.
592 593 594 595
  ListView.builder({
    Key key,
    Axis scrollDirection: Axis.vertical,
    bool reverse: false,
596
    ScrollController controller,
597
    bool primary,
598 599
    ScrollPhysics physics,
    bool shrinkWrap: false,
600
    EdgeInsetsGeometry padding,
601
    this.itemExtent,
602
    @required IndexedWidgetBuilder itemBuilder,
603
    int itemCount,
604
    bool addAutomaticKeepAlives: true,
605 606 607 608
    bool addRepaintBoundaries: true,
  }) : childrenDelegate = new SliverChildBuilderDelegate(
         itemBuilder,
         childCount: itemCount,
609
         addAutomaticKeepAlives: addAutomaticKeepAlives,
610 611
         addRepaintBoundaries: addRepaintBoundaries,
       ), super(
612 613 614
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
615
    controller: controller,
616
    primary: primary,
617 618 619 620 621
    physics: physics,
    shrinkWrap: shrinkWrap,
    padding: padding,
  );

622 623 624 625
  /// 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.
626 627 628 629
  ListView.custom({
    Key key,
    Axis scrollDirection: Axis.vertical,
    bool reverse: false,
630
    ScrollController controller,
631
    bool primary,
632 633
    ScrollPhysics physics,
    bool shrinkWrap: false,
634
    EdgeInsetsGeometry padding,
635 636
    this.itemExtent,
    @required this.childrenDelegate,
637 638 639 640 641 642 643 644 645 646 647
  }) : assert(childrenDelegate != null),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
       );
648

649 650 651 652 653 654 655
  /// 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.
656 657
  final double itemExtent;

658 659 660 661 662 663
  /// 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.
664
  final SliverChildDelegate childrenDelegate;
665 666 667 668

  @override
  Widget buildChildLayout(BuildContext context) {
    if (itemExtent != null) {
669
      return new SliverFixedExtentList(
670 671 672 673
        delegate: childrenDelegate,
        itemExtent: itemExtent,
      );
    }
674
    return new SliverList(delegate: childrenDelegate);
675 676 677
  }

  @override
678
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
679 680
    super.debugFillProperties(description);
    description.add(new DoubleProperty('itemExtent', itemExtent, defaultValue: null));
681
  }
682 683
}

684 685
/// A scrollable, 2D array of widgets.
///
686 687 688
/// The main axis direction of a grid is the direction in which it scrolls (the
/// [scrollDirection]).
///
689 690 691
/// 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
692
/// cross-axis extent. A custom [SliverGridDelegate] can produce an arbitrary 2D
693 694 695 696
/// arrangement of children, including arrangements that are unaligned or
/// overlapping.
///
/// To create a grid with a large (or infinite) number of children, use the
697 698 699 700 701
/// [GridView.builder] constructor with either a
/// [SliverGridDelegateWithFixedCrossAxisCount] or a
/// [SliverGridDelegateWithMaxCrossAxisExtent] for the [gridDelegate].
///
/// To use a custom [SliverChildDelegate], use [GridView.custom].
702 703
///
/// To create a linear array of children, use a [ListView].
704
///
705 706 707
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
708 709 710
/// ## Transitioning to [CustomScrollView]
///
/// A [GridView] is basically a [CustomScrollView] with a single [SliverGrid] in
711
/// its [CustomScrollView.slivers] property.
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
///
/// 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].
///
/// Once code has been ported to use [CustomScrollView], other slivers, such as
746
/// [SliverList] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
747 748
/// list.
///
749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793
/// ### Sample code
///
/// Here are two brief snippets showing a [GridView] and its equivalent using
/// [CustomScrollView]:
///
/// ```dart
/// new GridView.count(
///   primary: false,
///   padding: const EdgeInsets.all(20.0),
///   crossAxisSpacing: 10.0,
///   crossAxisCount: 2,
///   children: <Widget>[
///     const Text('He\'d have you all unravel at the'),
///     const Text('Heed not the rabble'),
///     const Text('Sound of screams but the'),
///     const Text('Who scream'),
///     const Text('Revolution is coming...'),
///     const Text('Revolution, they...'),
///   ],
/// )
/// ```
///
/// ```dart
/// new CustomScrollView(
///   primary: false,
///   slivers: <Widget>[
///     new SliverPadding(
///       padding: const EdgeInsets.all(20.0),
///       sliver: new SliverGrid.count(
///         crossAxisSpacing: 10.0,
///         crossAxisCount: 2,
///         children: <Widget>[
///           const Text('He\'d have you all unravel at the'),
///           const Text('Heed not the rabble'),
///           const Text('Sound of screams but the'),
///           const Text('Who scream'),
///           const Text('Revolution is coming...'),
///           const Text('Revolution, they...'),
///         ],
///       ),
///     ),
///   ],
/// )
/// ```
///
794 795
/// See also:
///
796 797 798 799 800 801 802 803 804 805 806
///  * [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.
807
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
808
///    the scroll position without using a [ScrollController].
809
class GridView extends BoxScrollView {
810 811 812 813
  /// Creates a scrollable, 2D array of widgets with a custom
  /// [SliverGridDelegate].
  ///
  /// The [gridDelegate] argument must not be null.
814
  ///
815 816 817 818
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
819
  /// null.
820
  GridView({
821
    Key key,
822
    Axis scrollDirection: Axis.vertical,
823
    bool reverse: false,
824
    ScrollController controller,
825
    bool primary,
826
    ScrollPhysics physics,
827
    bool shrinkWrap: false,
828
    EdgeInsetsGeometry padding,
829
    @required this.gridDelegate,
830
    bool addAutomaticKeepAlives: true,
831
    bool addRepaintBoundaries: true,
832
    List<Widget> children: const <Widget>[],
833
  }) : assert(gridDelegate != null),
834 835
       childrenDelegate = new SliverChildListDelegate(
         children,
836
         addAutomaticKeepAlives: addAutomaticKeepAlives,
837 838
         addRepaintBoundaries: addRepaintBoundaries,
       ),
839 840 841 842 843 844 845 846 847 848
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
       );
849

850 851 852 853 854 855
  /// 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.
  ///
856
  /// Providing a non-null `itemCount` improves the ability of the [GridView] to
857 858
  /// estimate the maximum scroll extent.
  ///
859 860
  /// `itemBuilder` will be called only with indices greater than or equal to
  /// zero and less than `itemCount`.
861 862
  ///
  /// The [gridDelegate] argument must not be null.
863
  ///
864 865 866 867 868
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. Both must not
  /// be null.
869 870 871 872 873 874 875 876
  GridView.builder({
    Key key,
    Axis scrollDirection: Axis.vertical,
    bool reverse: false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap: false,
877
    EdgeInsetsGeometry padding,
878 879 880
    @required this.gridDelegate,
    @required IndexedWidgetBuilder itemBuilder,
    int itemCount,
881
    bool addAutomaticKeepAlives: true,
882
    bool addRepaintBoundaries: true,
883
  }) : assert(gridDelegate != null),
884 885 886
       childrenDelegate = new SliverChildBuilderDelegate(
         itemBuilder,
         childCount: itemCount,
887
         addAutomaticKeepAlives: addAutomaticKeepAlives,
888 889
         addRepaintBoundaries: addRepaintBoundaries,
       ),
890 891 892 893 894 895 896 897 898 899
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
       );
900

901 902 903
  /// Creates a scrollable, 2D array of widgets with both a custom
  /// [SliverGridDelegate] and a custom [SliverChildDelegate].
  ///
904 905
  /// To use an [IndexedWidgetBuilder] callback to build children, either use
  /// a [SliverChildBuilderDelegate] or use the [GridView.builder] constructor.
906 907
  ///
  /// The [gridDelegate] and [childrenDelegate] arguments must not be null.
908
  GridView.custom({
909
    Key key,
910
    Axis scrollDirection: Axis.vertical,
911
    bool reverse: false,
912
    ScrollController controller,
913
    bool primary,
914 915
    ScrollPhysics physics,
    bool shrinkWrap: false,
916
    EdgeInsetsGeometry padding,
917 918
    @required this.gridDelegate,
    @required this.childrenDelegate,
919 920 921 922 923 924 925 926 927 928 929 930
  }) : assert(gridDelegate != null),
       assert(childrenDelegate != null),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
       );
931

932 933 934 935
  /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
  /// the cross axis.
  ///
  /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate].
936
  ///
937 938 939 940
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
941 942
  /// null.
  ///
943 944 945
  /// See also:
  ///
  ///  * [new SliverGrid.count], the equivalent constructor for [SliverGrid].
946 947 948 949
  GridView.count({
    Key key,
    Axis scrollDirection: Axis.vertical,
    bool reverse: false,
950
    ScrollController controller,
951
    bool primary,
952
    ScrollPhysics physics,
953
    bool shrinkWrap: false,
954
    EdgeInsetsGeometry padding,
955 956 957 958
    @required int crossAxisCount,
    double mainAxisSpacing: 0.0,
    double crossAxisSpacing: 0.0,
    double childAspectRatio: 1.0,
959
    bool addAutomaticKeepAlives: true,
960
    bool addRepaintBoundaries: true,
961
    List<Widget> children: const <Widget>[],
962
  }) : gridDelegate = new SliverGridDelegateWithFixedCrossAxisCount(
963 964 965 966 967
         crossAxisCount: crossAxisCount,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
968 969
       childrenDelegate = new SliverChildListDelegate(
         children,
970
         addAutomaticKeepAlives: addAutomaticKeepAlives,
971 972
         addRepaintBoundaries: addRepaintBoundaries,
       ), super(
973 974 975
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
976
    controller: controller,
977
    primary: primary,
978 979 980 981
    physics: physics,
    shrinkWrap: shrinkWrap,
    padding: padding,
  );
982

983 984
  /// Creates a scrollable, 2D array of widgets with tiles that each have a
  /// maximum cross-axis extent.
985 986
  ///
  /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate].
987
  ///
988 989 990 991
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
992 993
  /// null.
  ///
994 995 996
  /// See also:
  ///
  ///  * [new SliverGrid.extent], the equivalent constructor for [SliverGrid].
997
  GridView.extent({
998
    Key key,
999
    Axis scrollDirection: Axis.vertical,
1000
    bool reverse: false,
1001
    ScrollController controller,
1002
    bool primary,
1003
    ScrollPhysics physics,
1004
    bool shrinkWrap: false,
1005
    EdgeInsetsGeometry padding,
1006 1007 1008 1009
    @required double maxCrossAxisExtent,
    double mainAxisSpacing: 0.0,
    double crossAxisSpacing: 0.0,
    double childAspectRatio: 1.0,
1010
    bool addAutomaticKeepAlives: true,
1011
    bool addRepaintBoundaries: true,
1012
    List<Widget> children: const <Widget>[],
1013
  }) : gridDelegate = new SliverGridDelegateWithMaxCrossAxisExtent(
1014 1015 1016 1017 1018
         maxCrossAxisExtent: maxCrossAxisExtent,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
1019 1020
       childrenDelegate = new SliverChildListDelegate(
         children,
1021
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1022 1023
         addRepaintBoundaries: addRepaintBoundaries,
       ), super(
1024 1025 1026
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
1027
    controller: controller,
1028
    primary: primary,
1029 1030 1031 1032
    physics: physics,
    shrinkWrap: shrinkWrap,
    padding: padding,
  );
1033

1034 1035 1036 1037 1038
  /// A delegate that controls the layout of the children within the [GridView].
  ///
  /// The [GridView] and [GridView.custom] constructors let you specify this
  /// delegate explicitly. The other constructors create a [gridDelegate]
  /// implicitly.
1039 1040
  final SliverGridDelegate gridDelegate;

1041 1042 1043 1044 1045
  /// 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.
1046
  final SliverChildDelegate childrenDelegate;
1047

1048
  @override
1049 1050 1051
  Widget buildChildLayout(BuildContext context) {
    return new SliverGrid(
      delegate: childrenDelegate,
1052 1053
      gridDelegate: gridDelegate,
    );
Adam Barth's avatar
Adam Barth committed
1054 1055
  }
}