scroll_view.dart 78.5 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;

7
import 'package:flutter/gestures.dart';
8
import 'package:flutter/rendering.dart';
Adam Barth's avatar
Adam Barth committed
9 10

import 'basic.dart';
11
import 'debug.dart';
12 13
import 'focus_manager.dart';
import 'focus_scope.dart';
14
import 'framework.dart';
15
import 'media_query.dart';
16
import 'notification_listener.dart';
17
import 'primary_scroll_controller.dart';
18
import 'scroll_configuration.dart';
19
import 'scroll_controller.dart';
20
import 'scroll_notification.dart';
21
import 'scroll_physics.dart';
Adam Barth's avatar
Adam Barth committed
22 23
import 'scrollable.dart';
import 'sliver.dart';
24
import 'sliver_prototype_extent_list.dart';
25
import 'viewport.dart';
Adam Barth's avatar
Adam Barth committed
26

27
// Examples can assume:
28
// late int itemCount;
29

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

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

115
  /// {@template flutter.widgets.scroll_view.scrollDirection}
116 117 118
  /// The axis along which the scroll view scrolls.
  ///
  /// Defaults to [Axis.vertical].
119
  /// {@endtemplate}
Adam Barth's avatar
Adam Barth committed
120 121
  final Axis scrollDirection;

122
  /// {@template flutter.widgets.scroll_view.reverse}
123
  /// Whether the scroll view scrolls in the reading direction.
124 125 126 127 128 129
  ///
  /// 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
130
  /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
131 132 133 134
  /// scrolls from top to bottom when [reverse] is false and from bottom to top
  /// when [reverse] is true.
  ///
  /// Defaults to false.
135
  /// {@endtemplate}
136 137
  final bool reverse;

138
  /// {@template flutter.widgets.scroll_view.controller}
139 140 141 142
  /// An object that can be used to control the position to which this scroll
  /// view is scrolled.
  ///
  /// Must be null if [primary] is true.
143 144 145 146 147 148 149 150
  ///
  /// 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]).
151
  /// {@endtemplate}
152
  final ScrollController? controller;
153

154
  /// {@template flutter.widgets.scroll_view.primary}
155 156 157
  /// Whether this is the primary scroll view associated with the parent
  /// [PrimaryScrollController].
  ///
158 159 160 161
  /// 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].
  ///
162 163 164 165 166
  /// Also when true, the scroll view is used for default [ScrollAction]s. If a
  /// ScrollAction is not handled by an otherwise focused part of the application,
  /// the ScrollAction will be evaluated using this scroll view, for example,
  /// when executing [Shortcuts] key events like page up and down.
  ///
167
  /// On iOS, this also identifies the scroll view that will scroll to top in
168
  /// response to a tap in the status bar.
169
  /// {@endtemplate}
170
  ///
171 172
  /// Defaults to true when [scrollDirection] is [Axis.vertical] and
  /// [controller] is null.
173 174
  final bool primary;

175
  /// {@template flutter.widgets.scroll_view.physics}
176 177 178 179 180
  /// 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.
  ///
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
  /// 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(),
  /// ```
201 202 203 204 205 206 207 208 209
  ///
  /// 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.)
210
  /// {@endtemplate}
211 212 213 214
  ///
  /// If an explicit [ScrollBehavior] is provided to [scrollBehavior], the
  /// [ScrollPhysics] provided by that behavior will take precedence after
  /// [physics].
215
  final ScrollPhysics? physics;
Adam Barth's avatar
Adam Barth committed
216

217 218 219 220 221 222 223 224
  /// {@macro flutter.widgets.shadow.scrollBehavior}
  ///
  /// [ScrollBehavior]s also provide [ScrollPhysics]. If an explicit
  /// [ScrollPhysics] is provided in [physics], it will take precedence,
  /// followed by [scrollBehavior], and then the inherited ancestor
  /// [ScrollBehavior].
  final ScrollBehavior? scrollBehavior;

225
  /// {@template flutter.widgets.scroll_view.shrinkWrap}
226 227 228 229 230 231 232 233 234 235 236 237 238 239
  /// 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.
240
  /// {@endtemplate}
241 242
  final bool shrinkWrap;

243 244
  /// The first child in the [GrowthDirection.forward] growth direction.
  ///
245 246 247 248 249
  /// Children after [center] will be placed in the [AxisDirection] determined
  /// by [scrollDirection] and [reverse] relative to the [center]. Children
  /// before [center] will be placed in the opposite of the axis direction
  /// relative to the [center]. This makes the [center] the inflection point of
  /// the growth direction.
250 251 252 253 254 255 256 257 258 259
  ///
  /// 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.
260
  final Key? center;
261

262
  /// {@template flutter.widgets.scroll_view.anchor}
263 264
  /// The relative position of the zero scroll offset.
  ///
265 266 267 268 269 270
  /// For example, if [anchor] is 0.5 and the [AxisDirection] determined by
  /// [scrollDirection] and [reverse] is [AxisDirection.down] or
  /// [AxisDirection.up], then the zero scroll offset is vertically centered
  /// within the viewport. If the [anchor] is 1.0, and the axis direction is
  /// [AxisDirection.right], then the zero scroll offset is on the left edge of
  /// the viewport.
271
  /// {@endtemplate}
272 273
  final double anchor;

274
  /// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
275
  final double? cacheExtent;
276

277 278 279 280
  /// 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,
281
  /// while the [ListView.separated] constructor will use half that amount.
282 283 284 285 286 287 288 289
  ///
  /// 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.
290
  final int? semanticChildCount;
291

292 293 294
  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
  final DragStartBehavior dragStartBehavior;

295
  /// {@template flutter.widgets.scroll_view.keyboardDismissBehavior}
296 297
  /// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will
  /// dismiss the keyboard automatically.
298
  /// {@endtemplate}
299 300
  final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;

301
  /// {@macro flutter.widgets.scrollable.restorationId}
302
  final String? restorationId;
303

304
  /// {@macro flutter.material.Material.clipBehavior}
305 306 307 308
  ///
  /// Defaults to [Clip.hardEdge].
  final Clip clipBehavior;

309 310 311 312 313
  /// Returns the [AxisDirection] in which the scroll view scrolls.
  ///
  /// Combines the [scrollDirection] with the [reverse] boolean to obtain the
  /// concrete [AxisDirection].
  ///
314
  /// If the [scrollDirection] is [Axis.horizontal], the ambient
315
  /// [Directionality] is also considered when selecting the concrete
316 317 318 319
  /// [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].
320 321
  @protected
  AxisDirection getDirection(BuildContext context) {
322
    return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
323 324
  }

325 326
  /// Build the list of widgets to place inside the viewport.
  ///
327 328
  /// Subclasses should override this method to build the slivers for the inside
  /// of the viewport.
329
  @protected
330
  List<Widget> buildSlivers(BuildContext context);
Adam Barth's avatar
Adam Barth committed
331

332 333 334 335 336
  /// 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.
337 338 339 340 341 342 343 344
  ///
  /// 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].
345 346 347 348 349 350 351
  @protected
  Widget buildViewport(
    BuildContext context,
    ViewportOffset offset,
    AxisDirection axisDirection,
    List<Widget> slivers,
  ) {
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
    assert(() {
      switch (axisDirection) {
        case AxisDirection.up:
        case AxisDirection.down:
          return debugCheckHasDirectionality(
            context,
            why: 'to determine the cross-axis direction of the scroll view',
            hint: 'Vertical scroll views create Viewport widgets that try to determine their cross axis direction '
                  'from the ambient Directionality.',
          );
        case AxisDirection.left:
        case AxisDirection.right:
          return true;
      }
    }());
367
    if (shrinkWrap) {
368
      return ShrinkWrappingViewport(
369 370 371
        axisDirection: axisDirection,
        offset: offset,
        slivers: slivers,
372
        clipBehavior: clipBehavior,
373 374
      );
    }
375
    return Viewport(
376 377 378
      axisDirection: axisDirection,
      offset: offset,
      slivers: slivers,
379
      cacheExtent: cacheExtent,
380 381
      center: center,
      anchor: anchor,
382
      clipBehavior: clipBehavior,
383 384 385
    );
  }

386 387
  @override
  Widget build(BuildContext context) {
388 389
    final List<Widget> slivers = buildSlivers(context);
    final AxisDirection axisDirection = getDirection(context);
390

391
    final ScrollController? scrollController =
392
        primary ? PrimaryScrollController.of(context) : controller;
393
    final Scrollable scrollable = Scrollable(
394
      dragStartBehavior: dragStartBehavior,
395
      axisDirection: axisDirection,
396
      controller: scrollController,
Adam Barth's avatar
Adam Barth committed
397
      physics: physics,
398
      scrollBehavior: scrollBehavior,
399
      semanticChildCount: semanticChildCount,
400
      restorationId: restorationId,
401
      viewportBuilder: (BuildContext context, ViewportOffset offset) {
402 403
        return buildViewport(context, offset, axisDirection, slivers);
      },
404
    );
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
    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;
    }
423
  }
424 425

  @override
426 427
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
428 429 430 431 432 433
    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));
434 435 436
  }
}

437 438 439 440 441 442 443 444
/// 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].
///
445 446
/// [Widget]s in these [slivers] must produce [RenderSliver] objects.
///
447 448 449
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
450 451
/// {@animation 400 376 https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_scroll_view.mp4}
///
452
/// {@tool snippet}
453 454 455 456 457
///
/// This sample code shows a scroll view that contains a flexible pinned app
/// bar, a grid, and an infinite list.
///
/// ```dart
458
/// CustomScrollView(
459 460 461 462
///   slivers: <Widget>[
///     const SliverAppBar(
///       pinned: true,
///       expandedHeight: 250.0,
463 464
///       flexibleSpace: FlexibleSpaceBar(
///         title: Text('Demo'),
465 466
///       ),
///     ),
467
///     SliverGrid(
468
///       gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
469 470 471 472 473
///         maxCrossAxisExtent: 200.0,
///         mainAxisSpacing: 10.0,
///         crossAxisSpacing: 10.0,
///         childAspectRatio: 4.0,
///       ),
474
///       delegate: SliverChildBuilderDelegate(
475
///         (BuildContext context, int index) {
476
///           return Container(
477
///             alignment: Alignment.center,
478
///             color: Colors.teal[100 * (index % 9)],
479
///             child: Text('Grid Item $index'),
480 481 482 483 484
///           );
///         },
///         childCount: 20,
///       ),
///     ),
485
///     SliverFixedExtentList(
486
///       itemExtent: 50.0,
487
///       delegate: SliverChildBuilderDelegate(
488
///         (BuildContext context, int index) {
489
///           return Container(
490
///             alignment: Alignment.center,
491
///             color: Colors.lightBlue[100 * (index % 9)],
492
///             child: Text('List Item $index'),
493 494 495 496 497 498 499
///           );
///         },
///       ),
///     ),
///   ],
/// )
/// ```
500
/// {@end-tool}
501
///
502
/// {@tool dartpad --template=stateful_widget_material}
503 504 505 506 507 508 509 510 511
/// 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.
///
512
/// ** See code in examples/api/lib/widgets/scroll_view/custom_scroll_view.1.dart **
513 514
/// {@end-tool}
///
515 516 517 518 519
/// ## 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
520
/// this announcement, the scroll view needs three pieces of information:
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
///
///   * 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].
///
536
/// This semantic index is not necessarily the same as the index of the widget in
Ian Hickson's avatar
Ian Hickson committed
537
/// the scrollable, because some widgets may not contribute semantic
538
/// information. Consider a [ListView.separated]: every other widget is a
539 540 541
/// 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
542
/// widgets. (The [ListView.separated] constructor handles this
Ian Hickson's avatar
Ian Hickson committed
543
/// automatically; this is only used here as an example.)
544 545 546 547 548
///
/// 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].
///
549 550 551 552 553 554 555 556 557 558
/// 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.
559
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
560
///    the scroll position without using a [ScrollController].
561 562
///  * [IndexedSemantics], which allows annotating child lists with an index
///    for scroll announcements.
Adam Barth's avatar
Adam Barth committed
563
class CustomScrollView extends ScrollView {
564 565
  /// Creates a [ScrollView] that creates custom scroll effects using slivers.
  ///
566
  /// See the [ScrollView] constructor for more details on these arguments.
567
  const CustomScrollView({
568
    Key? key,
569 570
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
571 572 573
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
574
    ScrollBehavior? scrollBehavior,
575
    bool shrinkWrap = false,
576
    Key? center,
577
    double anchor = 0.0,
578
    double? cacheExtent,
579
    this.slivers = const <Widget>[],
580
    int? semanticChildCount,
581
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
582
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
583
    String? restorationId,
584
    Clip clipBehavior = Clip.hardEdge,
Adam Barth's avatar
Adam Barth committed
585 586 587 588
  }) : super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
589
    controller: controller,
590
    primary: primary,
Adam Barth's avatar
Adam Barth committed
591
    physics: physics,
592
    scrollBehavior: scrollBehavior,
Adam Barth's avatar
Adam Barth committed
593
    shrinkWrap: shrinkWrap,
594 595
    center: center,
    anchor: anchor,
596
    cacheExtent: cacheExtent,
597
    semanticChildCount: semanticChildCount,
598
    dragStartBehavior: dragStartBehavior,
599
    keyboardDismissBehavior: keyboardDismissBehavior,
600
    restorationId: restorationId,
601
    clipBehavior: clipBehavior,
Adam Barth's avatar
Adam Barth committed
602 603
  );

604
  /// The slivers to place inside the viewport.
Adam Barth's avatar
Adam Barth committed
605 606 607 608 609 610
  final List<Widget> slivers;

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

611
/// A [ScrollView] that uses a single child layout model.
612 613 614 615 616 617 618
///
/// 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.
619
abstract class BoxScrollView extends ScrollView {
620 621 622
  /// Creates a [ScrollView] uses a single child layout model.
  ///
  /// If the [primary] argument is true, the [controller] must be null.
623
  const BoxScrollView({
624
    Key? key,
625 626
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
627 628 629
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
630
    bool shrinkWrap = false,
631
    this.padding,
632 633
    double? cacheExtent,
    int? semanticChildCount,
634
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
635
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
636
    String? restorationId,
637
    Clip clipBehavior = Clip.hardEdge,
638 639 640 641
  }) : super(
    key: key,
    scrollDirection: scrollDirection,
    reverse: reverse,
642
    controller: controller,
643
    primary: primary,
644 645
    physics: physics,
    shrinkWrap: shrinkWrap,
646
    cacheExtent: cacheExtent,
647
    semanticChildCount: semanticChildCount,
648
    dragStartBehavior: dragStartBehavior,
649
    keyboardDismissBehavior: keyboardDismissBehavior,
650
    restorationId: restorationId,
651
    clipBehavior: clipBehavior,
652 653
  );

654
  /// The amount of space by which to inset the children.
655
  final EdgeInsetsGeometry? padding;
656 657 658 659

  @override
  List<Widget> buildSlivers(BuildContext context) {
    Widget sliver = buildChildLayout(context);
660
    EdgeInsetsGeometry? effectivePadding = padding;
661
    if (padding == null) {
662
      final MediaQueryData? mediaQuery = MediaQuery.maybeOf(context);
663 664 665 666 667 668 669 670 671 672 673
      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.
674
        sliver = MediaQuery(
675 676 677 678 679 680 681 682 683 684 685
          data: mediaQuery.copyWith(
            padding: scrollDirection == Axis.vertical
                ? mediaQueryHorizontalPadding
                : mediaQueryVerticalPadding,
          ),
          child: sliver,
        );
      }
    }

    if (effectivePadding != null)
686
      sliver = SliverPadding(padding: effectivePadding, sliver: sliver);
687 688 689
    return <Widget>[ sliver ];
  }

690
  /// Subclasses should override this method to build the layout model.
691 692 693 694
  @protected
  Widget buildChildLayout(BuildContext context);

  @override
695 696
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
697
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
698 699 700
  }
}

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

1100 1101 1102 1103 1104 1105
  /// 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.
  ///
1106
  /// Providing a non-null `itemCount` improves the ability of the [ListView] to
1107 1108
  /// estimate the maximum scroll extent.
  ///
1109 1110 1111
  /// The `itemBuilder` callback will be called only with indices greater than
  /// or equal to zero and less than `itemCount`.
  ///
1112 1113 1114 1115 1116 1117 1118
  /// The `itemBuilder` should always return a non-null widget, and actually
  /// create the widget instances when called. Avoid using a builder that
  /// returns a previously-constructed widget; if the list view's children are
  /// created in advance, or all at once when the [ListView] itself is created,
  /// it is more efficient to use the [ListView] constructor. Even more
  /// efficient, however, is to create the instances on demand using this
  /// constructor's `itemBuilder` callback.
1119
  ///
1120 1121 1122
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1123 1124 1125 1126
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None may be
  /// null.
1127 1128 1129 1130
  ///
  /// [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].
1131
  ListView.builder({
1132
    Key? key,
1133 1134
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1135 1136 1137
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1138
    bool shrinkWrap = false,
1139
    EdgeInsetsGeometry? padding,
1140
    this.itemExtent,
1141
    this.prototypeItem,
1142 1143
    required IndexedWidgetBuilder itemBuilder,
    int? itemCount,
1144 1145
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1146
    bool addSemanticIndexes = true,
1147 1148
    double? cacheExtent,
    int? semanticChildCount,
1149
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1150
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1151
    String? restorationId,
1152
    Clip clipBehavior = Clip.hardEdge,
1153
  }) : assert(itemCount == null || itemCount >= 0),
1154
       assert(semanticChildCount == null || semanticChildCount <= itemCount!),
1155 1156 1157 1158
       assert(
         itemExtent == null || prototypeItem == null,
         'You can only pass itemExtent or prototypeItem, not both.',
       ),
1159
       childrenDelegate = SliverChildBuilderDelegate(
1160 1161
         itemBuilder,
         childCount: itemCount,
1162
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1163
         addRepaintBoundaries: addRepaintBoundaries,
1164
         addSemanticIndexes: addSemanticIndexes,
1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? itemCount,
         dragStartBehavior: dragStartBehavior,
1178
         keyboardDismissBehavior: keyboardDismissBehavior,
1179
         restorationId: restorationId,
1180
         clipBehavior: clipBehavior,
1181
       );
1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198

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

1297 1298 1299 1300
  /// 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.
1301
  ///
1302
  /// {@tool snippet}
1303 1304 1305 1306 1307 1308
  ///
  /// This [ListView] uses a custom [SliverChildBuilderDelegate] to support child
  /// reordering.
  ///
  /// ```dart
  /// class MyListView extends StatefulWidget {
1309 1310
  ///   const MyListView({Key? key}) : super(key: key);
  ///
1311
  ///   @override
1312
  ///   State<MyListView> createState() => _MyListViewState();
1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337
  /// }
  ///
  /// 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) {
1338
  ///               final ValueKey<String> valueKey = key as ValueKey<String>;
1339 1340 1341 1342 1343 1344 1345 1346 1347 1348
  ///               final String data = valueKey.value;
  ///               return items.indexOf(data);
  ///             }
  ///           ),
  ///         ),
  ///       ),
  ///       bottomNavigationBar: BottomAppBar(
  ///         child: Row(
  ///           mainAxisAlignment: MainAxisAlignment.center,
  ///           children: <Widget>[
1349
  ///             TextButton(
1350
  ///               onPressed: () => _reverse(),
1351
  ///               child: const Text('Reverse items'),
1352 1353 1354 1355 1356 1357 1358 1359 1360
  ///             ),
  ///           ],
  ///         ),
  ///       ),
  ///     );
  ///   }
  /// }
  ///
  /// class KeepAlive extends StatefulWidget {
1361 1362 1363 1364
  ///   const KeepAlive({
  ///     required Key key,
  ///     required this.data,
  ///   }) : super(key: key);
1365 1366 1367 1368
  ///
  ///   final String data;
  ///
  ///   @override
1369
  ///   State<KeepAlive> createState() => _KeepAliveState();
1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383
  /// }
  ///
  /// 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}
1384
  const ListView.custom({
1385
    Key? key,
1386 1387
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1388 1389 1390
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1391
    bool shrinkWrap = false,
1392
    EdgeInsetsGeometry? padding,
1393
    this.itemExtent,
1394
    this.prototypeItem,
1395 1396 1397
    required this.childrenDelegate,
    double? cacheExtent,
    int? semanticChildCount,
1398 1399
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1400
    String? restorationId,
1401
    Clip clipBehavior = Clip.hardEdge,
1402
  }) : assert(childrenDelegate != null),
1403 1404 1405 1406
       assert(
         itemExtent == null || prototypeItem == null,
         'You can only pass itemExtent or prototypeItem, not both',
       ),
1407 1408 1409 1410 1411 1412 1413 1414 1415
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
Ian Hickson's avatar
Ian Hickson committed
1416
         cacheExtent: cacheExtent,
1417
         semanticChildCount: semanticChildCount,
1418 1419
         dragStartBehavior: dragStartBehavior,
         keyboardDismissBehavior: keyboardDismissBehavior,
1420
         restorationId: restorationId,
1421
         clipBehavior: clipBehavior,
1422
       );
1423

1424
  /// {@template flutter.widgets.list_view.itemExtent}
1425 1426 1427 1428 1429 1430 1431
  /// 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.
1432 1433 1434 1435 1436 1437 1438 1439
  ///
  /// See also:
  ///
  ///  * [SliverFixedExtentList], the sliver used internally when this property
  ///    is provided. It constrains its box children to have a specific given
  ///    extent along the main axis.
  ///  * The [prototypeItem] property, which allows forcing the children's
  ///    extent to be the same as the given widget.
1440
  /// {@endtemplate}
1441
  final double? itemExtent;
1442

1443
  /// {@template flutter.widgets.list_view.prototypeItem}
1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458
  /// If non-null, forces the children to have the same extent as the given
  /// widget in the scroll direction.
  ///
  /// Specifying an [prototypeItem] 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.
  ///
  /// See also:
  ///
  ///  * [SliverPrototypeExtentList], the sliver used internally when this
  ///    property is provided. It constrains its box children to have the same
  ///    extent as a prototype item along the main axis.
  ///  * The [itemExtent] property, which allows forcing the children's extent
  ///    to a given value.
1459
  /// {@endtemplate}
1460 1461
  final Widget? prototypeItem;

1462 1463 1464 1465 1466 1467
  /// 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.
1468
  final SliverChildDelegate childrenDelegate;
1469 1470 1471 1472

  @override
  Widget buildChildLayout(BuildContext context) {
    if (itemExtent != null) {
1473
      return SliverFixedExtentList(
1474
        delegate: childrenDelegate,
1475
        itemExtent: itemExtent!,
1476
      );
1477 1478 1479 1480 1481
    } else if (prototypeItem != null) {
      return SliverPrototypeExtentList(
        delegate: childrenDelegate,
        prototypeItem: prototypeItem!,
      );
1482
    }
1483
    return SliverList(delegate: childrenDelegate);
1484 1485 1486
  }

  @override
1487 1488
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1489
    properties.add(DoubleProperty('itemExtent', itemExtent, defaultValue: null));
1490
  }
1491

1492 1493
  // Helper method to compute the actual child count for the separated constructor.
  static int _computeActualChildCount(int itemCount) {
1494 1495
    return math.max(0, itemCount * 2 - 1);
  }
1496 1497
}

1498 1499
/// A scrollable, 2D array of widgets.
///
1500 1501
/// {@youtube 560 315 https://www.youtube.com/watch?v=bLOtZDTm4H8}
///
1502 1503 1504
/// The main axis direction of a grid is the direction in which it scrolls (the
/// [scrollDirection]).
///
1505 1506 1507
/// 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
1508
/// cross-axis extent. A custom [SliverGridDelegate] can produce an arbitrary 2D
1509 1510 1511 1512
/// arrangement of children, including arrangements that are unaligned or
/// overlapping.
///
/// To create a grid with a large (or infinite) number of children, use the
1513 1514 1515 1516 1517
/// [GridView.builder] constructor with either a
/// [SliverGridDelegateWithFixedCrossAxisCount] or a
/// [SliverGridDelegateWithMaxCrossAxisExtent] for the [gridDelegate].
///
/// To use a custom [SliverChildDelegate], use [GridView.custom].
1518 1519
///
/// To create a linear array of children, use a [ListView].
1520
///
1521 1522 1523
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
1524 1525 1526
/// ## Transitioning to [CustomScrollView]
///
/// A [GridView] is basically a [CustomScrollView] with a single [SliverGrid] in
1527
/// its [CustomScrollView.slivers] property.
1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544
///
/// 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.
///
1545
/// The [GridView], [GridView.count], and [GridView.extent]
1546
/// constructors' `children` arguments correspond to the [childrenDelegate]
1547 1548
/// being a [SliverChildListDelegate] with that same argument. The
/// [GridView.builder] constructor's `itemBuilder` and `childCount` arguments
1549 1550 1551
/// correspond to the [childrenDelegate] being a [SliverChildBuilderDelegate]
/// with the matching arguments.
///
1552
/// The [GridView.count] and [GridView.extent] constructors create
1553
/// custom grid delegates, and have equivalently named constructors on
1554 1555
/// [SliverGrid] to ease the transition: [SliverGrid.count] and
/// [SliverGrid.extent] respectively.
1556 1557 1558 1559 1560 1561
///
/// 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
1562
/// [SliverList] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
1563 1564
/// list.
///
1565
/// {@tool snippet}
1566
/// This example demonstrates how to create a [GridView] with two columns. The
1567
/// children are spaced apart using the `crossAxisSpacing` and `mainAxisSpacing`
1568
/// properties.
1569
///
1570
/// ![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)
1571 1572
///
/// ```dart
1573
/// GridView.count(
1574
///   primary: false,
1575 1576 1577
///   padding: const EdgeInsets.all(20),
///   crossAxisSpacing: 10,
///   mainAxisSpacing: 10,
1578 1579
///   crossAxisCount: 2,
///   children: <Widget>[
1580 1581
///     Container(
///       padding: const EdgeInsets.all(8),
1582
///       child: const Text("He'd have you all unravel at the"),
1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609
///       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],
///     ),
1610 1611 1612
///   ],
/// )
/// ```
1613
/// {@end-tool}
1614
///
1615
/// {@tool snippet}
1616 1617 1618
/// This example shows how to create the same grid as the previous example
/// using a [CustomScrollView] and a [SliverGrid].
///
1619
/// ![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)
1620 1621
///
/// ```dart
1622
/// CustomScrollView(
1623 1624
///   primary: false,
///   slivers: <Widget>[
1625
///     SliverPadding(
1626
///       padding: const EdgeInsets.all(20),
1627
///       sliver: SliverGrid.count(
1628 1629
///         crossAxisSpacing: 10,
///         mainAxisSpacing: 10,
1630 1631
///         crossAxisCount: 2,
///         children: <Widget>[
1632 1633
///           Container(
///             padding: const EdgeInsets.all(8),
1634
///             child: const Text("He'd have you all unravel at the"),
1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661
///             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],
///           ),
1662 1663 1664 1665 1666 1667
///         ],
///       ),
///     ),
///   ],
/// )
/// ```
1668
/// {@end-tool}
1669
///
1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700
/// By default, [GridView] will automatically pad the limits of the
/// grids's scrollable to avoid partial obstructions indicated by
/// [MediaQuery]'s padding. To avoid this behavior, override with a
/// zero [padding] property.
///
/// {@tool snippet}
/// The following example demonstrates how to override the default top padding
/// using [MediaQuery.removePadding].
///
/// ```dart
/// Widget myWidget(BuildContext context) {
///   return MediaQuery.removePadding(
///     context: context,
///     removeTop: true,
///     child: GridView.builder(
///       gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
///         crossAxisCount: 3,
///       ),
///       itemCount: 300,
///       itemBuilder: (BuildContext context, int index) {
///         return Card(
///           color: Colors.amber,
///           child: Center(child: Text('$index')),
///         );
///       }
///     ),
///   );
/// }
/// ```
/// {@end-tool}
///
1701 1702
/// See also:
///
1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713
///  * [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.
1714
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
1715
///    the scroll position without using a [ScrollController].
1716
///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
1717
class GridView extends BoxScrollView {
1718 1719 1720 1721
  /// Creates a scrollable, 2D array of widgets with a custom
  /// [SliverGridDelegate].
  ///
  /// The [gridDelegate] argument must not be null.
1722
  ///
1723 1724 1725 1726
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1727
  /// null.
1728
  GridView({
1729
    Key? key,
1730 1731
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1732 1733 1734
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1735
    bool shrinkWrap = false,
1736 1737
    EdgeInsetsGeometry? padding,
    required this.gridDelegate,
1738 1739
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1740
    bool addSemanticIndexes = true,
1741
    double? cacheExtent,
1742
    List<Widget> children = const <Widget>[],
1743
    int? semanticChildCount,
1744
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1745
    Clip clipBehavior = Clip.hardEdge,
1746
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1747
    String? restorationId,
1748
  }) : assert(gridDelegate != null),
1749
       childrenDelegate = SliverChildListDelegate(
1750
         children,
1751
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1752
         addRepaintBoundaries: addRepaintBoundaries,
1753
         addSemanticIndexes: addSemanticIndexes,
1754
       ),
1755 1756 1757 1758 1759 1760 1761 1762 1763
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1764 1765
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? children.length,
1766 1767
         dragStartBehavior: dragStartBehavior,
         keyboardDismissBehavior: keyboardDismissBehavior,
1768
         restorationId: restorationId,
1769
         clipBehavior: clipBehavior,
1770
       );
1771

1772 1773 1774 1775 1776 1777
  /// 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.
  ///
1778
  /// Providing a non-null `itemCount` improves the ability of the [GridView] to
1779 1780
  /// estimate the maximum scroll extent.
  ///
1781 1782
  /// `itemBuilder` will be called only with indices greater than or equal to
  /// zero and less than `itemCount`.
1783 1784
  ///
  /// The [gridDelegate] argument must not be null.
1785
  ///
1786 1787 1788 1789 1790
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. Both must not
  /// be null.
1791
  GridView.builder({
1792
    Key? key,
1793 1794
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1795 1796 1797
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1798
    bool shrinkWrap = false,
1799 1800 1801 1802
    EdgeInsetsGeometry? padding,
    required this.gridDelegate,
    required IndexedWidgetBuilder itemBuilder,
    int? itemCount,
1803 1804
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1805
    bool addSemanticIndexes = true,
1806 1807
    double? cacheExtent,
    int? semanticChildCount,
1808 1809
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1810
    String? restorationId,
1811
    Clip clipBehavior = Clip.hardEdge,
1812
  }) : assert(gridDelegate != null),
1813
       childrenDelegate = SliverChildBuilderDelegate(
1814 1815
         itemBuilder,
         childCount: itemCount,
1816
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1817
         addRepaintBoundaries: addRepaintBoundaries,
1818
         addSemanticIndexes: addSemanticIndexes,
1819
       ),
1820 1821 1822 1823 1824 1825 1826 1827 1828
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1829 1830
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? itemCount,
1831 1832
         dragStartBehavior: dragStartBehavior,
         keyboardDismissBehavior: keyboardDismissBehavior,
1833
         restorationId: restorationId,
1834
         clipBehavior: clipBehavior,
1835
       );
1836

1837 1838 1839
  /// Creates a scrollable, 2D array of widgets with both a custom
  /// [SliverGridDelegate] and a custom [SliverChildDelegate].
  ///
1840 1841
  /// To use an [IndexedWidgetBuilder] callback to build children, either use
  /// a [SliverChildBuilderDelegate] or use the [GridView.builder] constructor.
1842 1843
  ///
  /// The [gridDelegate] and [childrenDelegate] arguments must not be null.
1844
  const GridView.custom({
1845
    Key? key,
1846 1847
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1848 1849 1850
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1851
    bool shrinkWrap = false,
1852 1853 1854 1855 1856
    EdgeInsetsGeometry? padding,
    required this.gridDelegate,
    required this.childrenDelegate,
    double? cacheExtent,
    int? semanticChildCount,
1857
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1858
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1859
    String? restorationId,
1860
    Clip clipBehavior = Clip.hardEdge,
1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871
  }) : assert(gridDelegate != null),
       assert(childrenDelegate != null),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
1872 1873
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount,
1874
         dragStartBehavior: dragStartBehavior,
1875
         keyboardDismissBehavior: keyboardDismissBehavior,
1876
         restorationId: restorationId,
1877
         clipBehavior: clipBehavior,
1878
       );
1879

1880 1881 1882 1883
  /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
  /// the cross axis.
  ///
  /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate].
1884
  ///
1885 1886 1887 1888
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1889 1890
  /// null.
  ///
1891 1892
  /// See also:
  ///
1893
  ///  * [SliverGrid.count], the equivalent constructor for [SliverGrid].
1894
  GridView.count({
1895
    Key? key,
1896 1897
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1898 1899 1900
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1901
    bool shrinkWrap = false,
1902 1903
    EdgeInsetsGeometry? padding,
    required int crossAxisCount,
1904 1905 1906 1907 1908
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1909
    bool addSemanticIndexes = true,
1910
    double? cacheExtent,
1911
    List<Widget> children = const <Widget>[],
1912
    int? semanticChildCount,
1913
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1914
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1915
    String? restorationId,
1916
    Clip clipBehavior = Clip.hardEdge,
1917
  }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
1918 1919 1920 1921 1922
         crossAxisCount: crossAxisCount,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
1923
       childrenDelegate = SliverChildListDelegate(
1924
         children,
1925
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1926
         addRepaintBoundaries: addRepaintBoundaries,
1927
         addSemanticIndexes: addSemanticIndexes,
1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940
       ),
       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,
1941
         keyboardDismissBehavior: keyboardDismissBehavior,
1942
         restorationId: restorationId,
1943
         clipBehavior: clipBehavior,
1944
       );
1945

1946 1947
  /// Creates a scrollable, 2D array of widgets with tiles that each have a
  /// maximum cross-axis extent.
1948 1949
  ///
  /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate].
1950
  ///
1951 1952 1953 1954
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1955 1956
  /// null.
  ///
1957 1958
  /// See also:
  ///
1959
  ///  * [SliverGrid.extent], the equivalent constructor for [SliverGrid].
1960
  GridView.extent({
1961
    Key? key,
1962 1963
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
1964 1965 1966
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
1967
    bool shrinkWrap = false,
1968 1969
    EdgeInsetsGeometry? padding,
    required double maxCrossAxisExtent,
1970 1971 1972 1973 1974
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1975
    bool addSemanticIndexes = true,
1976
    double? cacheExtent,
1977
    List<Widget> children = const <Widget>[],
1978
    int? semanticChildCount,
1979
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
1980
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
1981
    String? restorationId,
1982
    Clip clipBehavior = Clip.hardEdge,
1983
  }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
1984 1985 1986 1987 1988
         maxCrossAxisExtent: maxCrossAxisExtent,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
1989
       childrenDelegate = SliverChildListDelegate(
1990
         children,
1991
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1992
         addRepaintBoundaries: addRepaintBoundaries,
1993
         addSemanticIndexes: addSemanticIndexes,
1994 1995 1996 1997 1998 1999 2000 2001 2002 2003
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
2004
         cacheExtent: cacheExtent,
2005 2006
         semanticChildCount: semanticChildCount ?? children.length,
         dragStartBehavior: dragStartBehavior,
2007
         keyboardDismissBehavior: keyboardDismissBehavior,
2008
         restorationId: restorationId,
2009
         clipBehavior: clipBehavior,
2010
       );
2011

2012 2013
  /// A delegate that controls the layout of the children within the [GridView].
  ///
2014
  /// The [GridView], [GridView.builder], and [GridView.custom] constructors let you specify this
2015 2016
  /// delegate explicitly. The other constructors create a [gridDelegate]
  /// implicitly.
2017 2018
  final SliverGridDelegate gridDelegate;

2019 2020 2021 2022 2023
  /// 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.
2024
  final SliverChildDelegate childrenDelegate;
2025

2026
  @override
2027
  Widget buildChildLayout(BuildContext context) {
2028
    return SliverGrid(
2029
      delegate: childrenDelegate,
2030 2031
      gridDelegate: gridDelegate,
    );
Adam Barth's avatar
Adam Barth committed
2032 2033
  }
}