scroll_view.dart 39.8 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 181 182
  @protected
  AxisDirection getDirection(BuildContext context) {
    switch (scrollDirection) {
      case Axis.horizontal:
183 184
        final TextDirection textDirection = Directionality.of(context);
        assert(textDirection != null);
185 186
        final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
        return reverse ? flipAxisDirection(axisDirection) : axisDirection;
187 188 189 190 191 192
      case Axis.vertical:
        return reverse ? AxisDirection.up : AxisDirection.down;
    }
    return null;
  }

193 194
  /// Subclasses should override this method to build the slivers for the inside
  /// of the viewport.
195
  @protected
196
  List<Widget> buildSlivers(BuildContext context);
Adam Barth's avatar
Adam Barth committed
197

198 199
  @override
  Widget build(BuildContext context) {
200 201
    final List<Widget> slivers = buildSlivers(context);
    final AxisDirection axisDirection = getDirection(context);
202

203
    final ScrollController scrollController = primary
204 205
        ? PrimaryScrollController.of(context)
        : controller;
206
    final Scrollable scrollable = new Scrollable(
207
      axisDirection: axisDirection,
208
      controller: scrollController,
Adam Barth's avatar
Adam Barth committed
209
      physics: physics,
210 211 212 213 214
      viewportBuilder: (BuildContext context, ViewportOffset offset) {
        if (shrinkWrap) {
          return new ShrinkWrappingViewport(
            axisDirection: axisDirection,
            offset: offset,
215
            slivers: slivers,
216 217
          );
        } else {
Adam Barth's avatar
Adam Barth committed
218
          return new Viewport(
219 220
            axisDirection: axisDirection,
            offset: offset,
221
            slivers: slivers,
222 223 224
          );
        }
      }
225
    );
226 227 228
    return primary && scrollController != null
      ? new PrimaryScrollController.none(child: scrollable)
      : scrollable;
229
  }
230 231

  @override
232
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
233 234 235 236 237 238 239
    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));
240 241 242
  }
}

243 244 245 246 247 248 249 250
/// 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].
///
251 252 253
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
/// ## 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(
///             alignment: FractionalOffset.center,
///             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(
///             alignment: FractionalOffset.center,
///             color: Colors.lightBlue[100 * (index % 9)],
///             child: new Text('list item $index'),
///           );
///         },
///       ),
///     ),
///   ],
/// )
/// ```
///
303 304 305 306 307 308 309 310 311 312
/// 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.
313
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
314
///    the scroll position without using a [ScrollController].
Adam Barth's avatar
Adam Barth committed
315
class CustomScrollView extends ScrollView {
316 317 318
  /// 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
319 320 321 322
  CustomScrollView({
    Key key,
    Axis scrollDirection: Axis.vertical,
    bool reverse: false,
323
    ScrollController controller,
324
    bool primary,
Adam Barth's avatar
Adam Barth committed
325 326 327 328 329 330 331
    ScrollPhysics physics,
    bool shrinkWrap: false,
    this.slivers: const <Widget>[],
  }) : super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
332
    controller: controller,
333
    primary: primary,
Adam Barth's avatar
Adam Barth committed
334 335 336 337
    physics: physics,
    shrinkWrap: shrinkWrap,
  );

338
  /// The slivers to place inside the viewport.
Adam Barth's avatar
Adam Barth committed
339 340 341 342 343 344
  final List<Widget> slivers;

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

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

376
  /// The amount of space by which to inset the children.
377
  final EdgeInsetsGeometry padding;
378 379 380 381 382

  @override
  List<Widget> buildSlivers(BuildContext context) {
    Widget sliver = buildChildLayout(context);
    if (padding != null)
383
      sliver = new SliverPadding(padding: padding, sliver: sliver);
384 385 386
    return <Widget>[ sliver ];
  }

387
  /// Subclasses should override this method to build the layout model.
388 389 390 391
  @protected
  Widget buildChildLayout(BuildContext context);

  @override
392
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
393
    super.debugFillProperties(description);
394
    description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
395 396 397
  }
}

398
/// A scrollable list of widgets arranged linearly.
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
///
/// [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.
427
///
428 429 430
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
431 432 433 434 435 436 437 438 439 440 441 442 443 444
/// ## 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');
///   },
/// )
/// ```
///
445 446 447
/// ## Transitioning to [CustomScrollView]
///
/// A [ListView] is basically a [CustomScrollView] with a single [SliverList] in
448
/// its [CustomScrollView.slivers] property.
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
///
/// 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.
///
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
/// ### 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'),
///           ],
///         ),
///       ),
///     ),
///   ],
/// )
/// ```
///
518 519
/// See also:
///
520 521 522 523 524 525 526
///  * [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.
527 528
///  * [ListBody], which arranges its children in a similar manner, but without
///    scrolling.
529
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
530
///    the scroll position without using a [ScrollController].
531
class ListView extends BoxScrollView {
532 533 534 535 536 537
  /// 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.
538 539 540
  ///
  /// It is usually more efficient to create children on demand using [new
  /// ListView.builder].
541
  ///
542 543 544 545
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
546
  /// null.
547 548 549 550
  ListView({
    Key key,
    Axis scrollDirection: Axis.vertical,
    bool reverse: false,
551
    ScrollController controller,
552
    bool primary,
553 554
    ScrollPhysics physics,
    bool shrinkWrap: false,
555
    EdgeInsetsGeometry padding,
556
    this.itemExtent,
557
    bool addAutomaticKeepAlives: true,
558
    bool addRepaintBoundaries: true,
559
    List<Widget> children: const <Widget>[],
560 561
  }) : childrenDelegate = new SliverChildListDelegate(
         children,
562
         addAutomaticKeepAlives: addAutomaticKeepAlives,
563 564
         addRepaintBoundaries: addRepaintBoundaries,
       ), super(
565 566 567
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
568
    controller: controller,
569
    primary: primary,
570 571 572 573 574
    physics: physics,
    shrinkWrap: shrinkWrap,
    padding: padding,
  );

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

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

656 657 658 659 660 661 662
  /// 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.
663 664
  final double itemExtent;

665 666 667 668 669 670
  /// 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.
671
  final SliverChildDelegate childrenDelegate;
672 673 674 675

  @override
  Widget buildChildLayout(BuildContext context) {
    if (itemExtent != null) {
676
      return new SliverFixedExtentList(
677 678 679 680
        delegate: childrenDelegate,
        itemExtent: itemExtent,
      );
    }
681
    return new SliverList(delegate: childrenDelegate);
682 683 684
  }

  @override
685
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
686 687
    super.debugFillProperties(description);
    description.add(new DoubleProperty('itemExtent', itemExtent, defaultValue: null));
688
  }
689 690
}

691 692
/// A scrollable, 2D array of widgets.
///
693 694 695
/// The main axis direction of a grid is the direction in which it scrolls (the
/// [scrollDirection]).
///
696 697 698
/// 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
699
/// cross-axis extent. A custom [SliverGridDelegate] can produce an arbitrary 2D
700 701 702 703
/// arrangement of children, including arrangements that are unaligned or
/// overlapping.
///
/// To create a grid with a large (or infinite) number of children, use the
704 705 706 707 708
/// [GridView.builder] constructor with either a
/// [SliverGridDelegateWithFixedCrossAxisCount] or a
/// [SliverGridDelegateWithMaxCrossAxisExtent] for the [gridDelegate].
///
/// To use a custom [SliverChildDelegate], use [GridView.custom].
709 710
///
/// To create a linear array of children, use a [ListView].
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
/// ## Transitioning to [CustomScrollView]
///
/// A [GridView] is basically a [CustomScrollView] with a single [SliverGrid] in
718
/// its [CustomScrollView.slivers] property.
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
///
/// 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
753
/// [SliverList] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
754 755
/// list.
///
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 794 795 796 797 798 799 800
/// ### 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...'),
///         ],
///       ),
///     ),
///   ],
/// )
/// ```
///
801 802
/// See also:
///
803 804 805 806 807 808 809 810 811 812 813
///  * [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.
814
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
815
///    the scroll position without using a [ScrollController].
816
class GridView extends BoxScrollView {
817 818 819 820
  /// Creates a scrollable, 2D array of widgets with a custom
  /// [SliverGridDelegate].
  ///
  /// The [gridDelegate] argument must not be null.
821
  ///
822 823 824 825
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
826
  /// null.
827
  GridView({
828
    Key key,
829
    Axis scrollDirection: Axis.vertical,
830
    bool reverse: false,
831
    ScrollController controller,
832
    bool primary,
833
    ScrollPhysics physics,
834
    bool shrinkWrap: false,
835
    EdgeInsetsGeometry padding,
836
    @required this.gridDelegate,
837
    bool addAutomaticKeepAlives: true,
838
    bool addRepaintBoundaries: true,
839
    List<Widget> children: const <Widget>[],
840
  }) : assert(gridDelegate != null),
841 842
       childrenDelegate = new SliverChildListDelegate(
         children,
843
         addAutomaticKeepAlives: addAutomaticKeepAlives,
844 845
         addRepaintBoundaries: addRepaintBoundaries,
       ),
846 847 848 849 850 851 852 853 854 855
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
       );
856

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

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

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

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

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

1048 1049 1050 1051 1052
  /// 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.
1053
  final SliverChildDelegate childrenDelegate;
1054

1055
  @override
1056 1057 1058
  Widget buildChildLayout(BuildContext context) {
    return new SliverGrid(
      delegate: childrenDelegate,
1059 1060
      gridDelegate: gridDelegate,
    );
Adam Barth's avatar
Adam Barth committed
1061 1062
  }
}