scroll_view.dart 81 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_delegate.dart';
21
import 'scroll_notification.dart';
22
import 'scroll_physics.dart';
Adam Barth's avatar
Adam Barth committed
23 24
import 'scrollable.dart';
import 'sliver.dart';
25
import 'sliver_prototype_extent_list.dart';
26
import 'viewport.dart';
Adam Barth's avatar
Adam Barth committed
27

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

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

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

116
  /// {@template flutter.widgets.scroll_view.scrollDirection}
117 118 119 120
  /// The [Axis] along which the scroll view's offset increases.
  ///
  /// For the direction in which active scrolling may be occurring, see
  /// [ScrollDirection].
121 122
  ///
  /// Defaults to [Axis.vertical].
123
  /// {@endtemplate}
Adam Barth's avatar
Adam Barth committed
124 125
  final Axis scrollDirection;

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

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

158
  /// {@template flutter.widgets.scroll_view.primary}
159 160 161
  /// Whether this is the primary scroll view associated with the parent
  /// [PrimaryScrollController].
  ///
162 163 164 165
  /// 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].
  ///
166 167 168 169 170
  /// 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.
  ///
171
  /// On iOS, this also identifies the scroll view that will scroll to top in
172 173
  /// response to a tap in the status bar.
  ///
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
  /// Cannot be true while a [ScrollController] is provided to `controller`,
  /// only one ScrollController can be associated with a ScrollView.
  ///
  /// Setting to false will explicitly prevent inheriting any
  /// [PrimaryScrollController].
  ///
  /// Defaults to null. When null, and a controller is not provided,
  /// [PrimaryScrollController.shouldInherit] is used to decide automatic
  /// inheritance.
  ///
  /// By default, the [PrimaryScrollController] that is injected by each
  /// [ModalRoute] is configured to automatically be inherited on
  /// [TargetPlatformVariant.mobile] for ScrollViews in the [Axis.vertical]
  /// scroll direction. Adding another to your app will override the
  /// PrimaryScrollController above it.
189 190 191 192 193 194
  ///
  /// The following video contains more information about scroll controllers,
  /// the PrimaryScrollController widget, and their impact on your apps:
  ///
  /// {@youtube 560 315 https://www.youtube.com/watch?v=33_0ABjFJUU}
  ///
195 196
  /// {@endtemplate}
  final bool? primary;
197

198
  /// {@template flutter.widgets.scroll_view.physics}
199 200 201 202 203
  /// 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.
  ///
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
  /// 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(),
  /// ```
224 225 226 227 228 229 230 231 232
  ///
  /// 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.)
233
  /// {@endtemplate}
234 235 236 237
  ///
  /// If an explicit [ScrollBehavior] is provided to [scrollBehavior], the
  /// [ScrollPhysics] provided by that behavior will take precedence after
  /// [physics].
238
  final ScrollPhysics? physics;
Adam Barth's avatar
Adam Barth committed
239

240 241 242 243 244 245 246 247
  /// {@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;

248
  /// {@template flutter.widgets.scroll_view.shrinkWrap}
249 250 251 252 253 254 255 256 257 258 259 260 261 262
  /// 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.
263 264
  ///
  /// {@youtube 560 315 https://www.youtube.com/watch?v=LUqDNnv_dh0}
265
  /// {@endtemplate}
266 267
  final bool shrinkWrap;

268 269
  /// The first child in the [GrowthDirection.forward] growth direction.
  ///
270 271 272 273 274
  /// 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.
275 276 277 278 279 280 281
  ///
  /// 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.
  ///
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
  /// Most scroll views by default are ordered [GrowthDirection.forward].
  /// Changing the default values of [ScrollView.anchor],
  /// [ScrollView.center], or both, can configure a scroll view for
  /// [GrowthDirection.reverse].
  ///
  /// {@tool dartpad}
  /// This sample shows a [CustomScrollView], with [Radio] buttons in the
  /// [AppBar.bottom] that change the [AxisDirection] to illustrate different
  /// configurations. The [CustomScrollView.anchor] and [CustomScrollView.center]
  /// properties are also set to have the 0 scroll offset positioned in the middle
  /// of the viewport, with [GrowthDirection.forward] and [GrowthDirection.reverse]
  /// illustrated on either side. The sliver that shares the
  /// [CustomScrollView.center] key is positioned at the [CustomScrollView.anchor].
  ///
  /// ** See code in examples/api/lib/rendering/growth_direction/growth_direction.0.dart **
  /// {@end-tool}
  ///
299 300 301
  /// See also:
  ///
  ///  * [anchor], which controls where the [center] as aligned in the viewport.
302
  final Key? center;
303

304
  /// {@template flutter.widgets.scroll_view.anchor}
305 306
  /// The relative position of the zero scroll offset.
  ///
307 308 309 310 311 312
  /// 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.
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
  ///
  /// Most scroll views by default are ordered [GrowthDirection.forward].
  /// Changing the default values of [ScrollView.anchor],
  /// [ScrollView.center], or both, can configure a scroll view for
  /// [GrowthDirection.reverse].
  ///
  /// {@tool dartpad}
  /// This sample shows a [CustomScrollView], with [Radio] buttons in the
  /// [AppBar.bottom] that change the [AxisDirection] to illustrate different
  /// configurations. The [CustomScrollView.anchor] and [CustomScrollView.center]
  /// properties are also set to have the 0 scroll offset positioned in the middle
  /// of the viewport, with [GrowthDirection.forward] and [GrowthDirection.reverse]
  /// illustrated on either side. The sliver that shares the
  /// [CustomScrollView.center] key is positioned at the [CustomScrollView.anchor].
  ///
  /// ** See code in examples/api/lib/rendering/growth_direction/growth_direction.0.dart **
  /// {@end-tool}
330
  /// {@endtemplate}
331 332
  final double anchor;

333
  /// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
334
  final double? cacheExtent;
335

336 337 338 339
  /// 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,
340
  /// while the [ListView.separated] constructor will use half that amount.
341 342 343 344 345 346 347 348
  ///
  /// 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.
349
  final int? semanticChildCount;
350

351 352 353
  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
  final DragStartBehavior dragStartBehavior;

354
  /// {@template flutter.widgets.scroll_view.keyboardDismissBehavior}
355 356
  /// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will
  /// dismiss the keyboard automatically.
357
  /// {@endtemplate}
358 359
  final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;

360
  /// {@macro flutter.widgets.scrollable.restorationId}
361
  final String? restorationId;
362

363
  /// {@macro flutter.material.Material.clipBehavior}
364 365 366 367
  ///
  /// Defaults to [Clip.hardEdge].
  final Clip clipBehavior;

368 369 370 371 372
  /// Returns the [AxisDirection] in which the scroll view scrolls.
  ///
  /// Combines the [scrollDirection] with the [reverse] boolean to obtain the
  /// concrete [AxisDirection].
  ///
373
  /// If the [scrollDirection] is [Axis.horizontal], the ambient
374
  /// [Directionality] is also considered when selecting the concrete
375 376 377 378
  /// [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].
379 380
  @protected
  AxisDirection getDirection(BuildContext context) {
381
    return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
382 383
  }

384 385
  /// Build the list of widgets to place inside the viewport.
  ///
386 387
  /// Subclasses should override this method to build the slivers for the inside
  /// of the viewport.
388 389
  ///
  /// To learn more about slivers, see [CustomScrollView.slivers].
390
  @protected
391
  List<Widget> buildSlivers(BuildContext context);
Adam Barth's avatar
Adam Barth committed
392

393 394 395 396 397
  /// 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.
398 399 400 401 402 403 404 405
  ///
  /// 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].
406 407 408 409 410 411 412
  @protected
  Widget buildViewport(
    BuildContext context,
    ViewportOffset offset,
    AxisDirection axisDirection,
    List<Widget> slivers,
  ) {
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
    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;
      }
    }());
428
    if (shrinkWrap) {
429
      return ShrinkWrappingViewport(
430 431 432
        axisDirection: axisDirection,
        offset: offset,
        slivers: slivers,
433
        clipBehavior: clipBehavior,
434 435
      );
    }
436
    return Viewport(
437 438 439
      axisDirection: axisDirection,
      offset: offset,
      slivers: slivers,
440
      cacheExtent: cacheExtent,
441 442
      center: center,
      anchor: anchor,
443
      clipBehavior: clipBehavior,
444 445 446
    );
  }

447 448
  @override
  Widget build(BuildContext context) {
449 450
    final List<Widget> slivers = buildSlivers(context);
    final AxisDirection axisDirection = getDirection(context);
451

452 453 454 455
    final bool effectivePrimary = primary
        ?? controller == null && PrimaryScrollController.shouldInherit(context, scrollDirection);

    final ScrollController? scrollController = effectivePrimary
456
        ? PrimaryScrollController.maybeOf(context)
457 458
        : controller;

459
    final Scrollable scrollable = Scrollable(
460
      dragStartBehavior: dragStartBehavior,
461
      axisDirection: axisDirection,
462
      controller: scrollController,
Adam Barth's avatar
Adam Barth committed
463
      physics: physics,
464
      scrollBehavior: scrollBehavior,
465
      semanticChildCount: semanticChildCount,
466
      restorationId: restorationId,
467
      viewportBuilder: (BuildContext context, ViewportOffset offset) {
468 469
        return buildViewport(context, offset, axisDirection, slivers);
      },
470
      clipBehavior: clipBehavior,
471
    );
472 473 474

    final Widget scrollableResult = effectivePrimary && scrollController != null
        // Further descendant ScrollViews will not inherit the same PrimaryScrollController
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
        ? 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;
    }
492
  }
493 494

  @override
495 496
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
497 498 499 500 501 502
    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));
503 504 505
  }
}

506
/// A [ScrollView] that creates custom scroll effects using [slivers].
507 508 509 510 511 512 513
///
/// 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].
///
514 515
/// [Widget]s in these [slivers] must produce [RenderSliver] objects.
///
516 517 518
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
519 520
/// {@animation 400 376 https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_scroll_view.mp4}
///
521
/// {@tool snippet}
522 523 524 525 526
///
/// This sample code shows a scroll view that contains a flexible pinned app
/// bar, a grid, and an infinite list.
///
/// ```dart
527
/// CustomScrollView(
528 529 530 531
///   slivers: <Widget>[
///     const SliverAppBar(
///       pinned: true,
///       expandedHeight: 250.0,
532 533
///       flexibleSpace: FlexibleSpaceBar(
///         title: Text('Demo'),
534 535
///       ),
///     ),
536
///     SliverGrid(
537
///       gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
538 539 540 541 542
///         maxCrossAxisExtent: 200.0,
///         mainAxisSpacing: 10.0,
///         crossAxisSpacing: 10.0,
///         childAspectRatio: 4.0,
///       ),
543
///       delegate: SliverChildBuilderDelegate(
544
///         (BuildContext context, int index) {
545
///           return Container(
546
///             alignment: Alignment.center,
547
///             color: Colors.teal[100 * (index % 9)],
548
///             child: Text('Grid Item $index'),
549 550 551 552 553
///           );
///         },
///         childCount: 20,
///       ),
///     ),
554
///     SliverFixedExtentList(
555
///       itemExtent: 50.0,
556
///       delegate: SliverChildBuilderDelegate(
557
///         (BuildContext context, int index) {
558
///           return Container(
559
///             alignment: Alignment.center,
560
///             color: Colors.lightBlue[100 * (index % 9)],
561
///             child: Text('List Item $index'),
562 563 564 565 566 567 568
///           );
///         },
///       ),
///     ),
///   ],
/// )
/// ```
569
/// {@end-tool}
570
///
571
/// {@tool dartpad}
572 573 574 575 576 577 578 579 580
/// 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.
///
581
/// ** See code in examples/api/lib/widgets/scroll_view/custom_scroll_view.1.dart **
582 583
/// {@end-tool}
///
584 585 586 587 588
/// ## 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
589
/// this announcement, the scroll view needs three pieces of information:
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
///
///   * 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].
///
605
/// This semantic index is not necessarily the same as the index of the widget in
Ian Hickson's avatar
Ian Hickson committed
606
/// the scrollable, because some widgets may not contribute semantic
607
/// information. Consider a [ListView.separated]: every other widget is a
608 609 610
/// 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
611
/// widgets. (The [ListView.separated] constructor handles this
Ian Hickson's avatar
Ian Hickson committed
612
/// automatically; this is only used here as an example.)
613 614 615 616 617
///
/// 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].
///
618 619 620 621 622 623 624 625 626 627
/// 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.
628
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
629
///    the scroll position without using a [ScrollController].
630 631
///  * [IndexedSemantics], which allows annotating child lists with an index
///    for scroll announcements.
Adam Barth's avatar
Adam Barth committed
632
class CustomScrollView extends ScrollView {
633 634
  /// Creates a [ScrollView] that creates custom scroll effects using slivers.
  ///
635
  /// See the [ScrollView] constructor for more details on these arguments.
636
  const CustomScrollView({
637 638 639 640 641 642 643 644 645 646 647
    super.key,
    super.scrollDirection,
    super.reverse,
    super.controller,
    super.primary,
    super.physics,
    super.scrollBehavior,
    super.shrinkWrap,
    super.center,
    super.anchor,
    super.cacheExtent,
648
    this.slivers = const <Widget>[],
649 650 651 652 653 654
    super.semanticChildCount,
    super.dragStartBehavior,
    super.keyboardDismissBehavior,
    super.restorationId,
    super.clipBehavior,
  });
Adam Barth's avatar
Adam Barth committed
655

656
  /// The slivers to place inside the viewport.
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751
  ///
  /// ## What is a sliver?
  ///
  /// > _**sliver** (noun): a small, thin piece of something._
  ///
  /// A _sliver_ is a widget backed by a [RenderSliver] subclass, i.e. one that
  /// implements the constraint/geometry protocol that uses [SliverConstraints]
  /// and [SliverGeometry].
  ///
  /// This is as distinct from those widgets that are backed by [RenderBox]
  /// subclasses, which use [BoxConstraints] and [Size] respectively, and are
  /// known as box widgets. (Widgets like [Container], [Row], and [SizedBox] are
  /// box widgets.)
  ///
  /// While boxes are much more straightforward (implementing a simple
  /// two-dimensional Cartesian layout system), slivers are much more powerful,
  /// and are optimized for one-axis scrolling environments.
  ///
  /// Slivers are hosted in viewports, also known as scroll views, most notably
  /// [CustomScrollView].
  ///
  /// ## Examples of slivers
  ///
  /// The Flutter framework has many built-in sliver widgets, and custom widgets
  /// can be created in the same manner. By convention, sliver widgets always
  /// start with the prefix `Sliver` and are always used in properties called
  /// `sliver` or `slivers` (as opposed to `child` and `children` which are used
  /// for box widgets).
  ///
  /// Examples of widgets unique to the sliver world include:
  ///
  /// * [SliverList], a lazily-loading list of variably-sized box widgets.
  /// * [SliverFixedExtentList], a lazily-loading list of box widgets that are
  ///   all forced to the same height.
  /// * [SliverPrototypeExtentList], a lazily-loading list of box widgets that
  ///   are all forced to the same height as a given prototype widget.
  /// * [SliverGrid], a lazily-loading grid of box widgets.
  /// * [SliverAnimatedList] and [SliverAnimatedGrid], animated variants of
  ///   [SliverList] and [SliverGrid].
  /// * [SliverFillRemaining], a widget that fills all remaining space in a
  ///   scroll view, and lays a box widget out inside that space.
  /// * [SliverFillViewport], a widget that lays a list of boxes out, each
  ///   being sized to fit the whole viewport.
  /// * [SliverPersistentHeader], a sliver that implements pinned and floating
  ///   headers, e.g. used to implement [SliverAppBar].
  /// * [SliverToBoxAdapter], a sliver that wraps a box widget.
  ///
  /// Examples of sliver variants of common box widgets include:
  ///
  /// * [SliverOpacity], [SliverAnimatedOpacity], and [SliverFadeTransition],
  ///   sliver versions of [Opacity], [AnimatedOpacity], and [FadeTransition].
  /// * [SliverIgnorePointer], a sliver version of [IgnorePointer].
  /// * [SliverLayoutBuilder], a sliver version of [LayoutBuilder].
  /// * [SliverOffstage], a sliver version of [Offstage].
  /// * [SliverPadding], a sliver version of [Padding].
  /// * [SliverReorderableList], a sliver version of [ReorderableList]
  /// * [SliverSafeArea], a sliver version of [SafeArea].
  /// * [SliverVisibility], a sliver version of [Visibility].
  ///
  /// ## Benefits of slivers over boxes
  ///
  /// The sliver protocol ([SliverConstraints] and [SliverGeometry]) enables
  /// _scroll effects_, such as floating app bars, widgets that expand and
  /// shrink during scroll, section headers that are pinned only while the
  /// section's children are visible, etc.
  ///
  /// {@youtube 560 315 https://www.youtube.com/watch?v=Mz3kHQxBjGg}
  ///
  /// ## Mixing slivers and boxes
  ///
  /// In general, slivers always wrap box widgets to actually render anything
  /// (for example, there is no sliver equivalent of [Text] or [Container]);
  /// the sliver part of the equation is mostly about how these boxes should
  /// be laid out in a viewport (i.e. when scrolling).
  ///
  /// Typically, the simplest way to combine boxes into a sliver environment is
  /// to use a [SliverList] (maybe using a [ListView, which is a convenient
  /// combination of a [CustomScrollView] and a [SliverList]). In rare cases,
  /// e.g. if a single [Divider] widget is needed between two [SliverGrid]s,
  /// a [SliverToBoxAdapter] can be used to wrap the box widgets.
  ///
  /// ## Performance considerations
  ///
  /// Because the purpose of scroll views is to, well, scroll, it is common
  /// for scroll views to contain more contents than are rendered on the screen
  /// at any particular time.
  ///
  /// To improve the performance of scroll views, the content can be rendered in
  /// _lazy_ widgets, notably [SliverList] and [SliverGrid] (and their variants,
  /// such as [SliverFixedExtentList] and [SliverAnimatedGrid]). These widgets
  /// ensure that only the portion of their child lists that are actually
  /// visible get built, laid out, and painted.
  ///
  /// The [ListView] and [GridView] widgets provide a convenient way to combine
  /// a [CustomScrollView] and a [SliverList] or [SliverGrid] (respectively).
Adam Barth's avatar
Adam Barth committed
752 753 754 755 756 757
  final List<Widget> slivers;

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

758
/// A [ScrollView] that uses a single child layout model.
759
///
760 761 762 763 764 765 766 767
/// {@template flutter.widgets.BoxScroll.scrollBehaviour}
/// [ScrollView]s are often decorated with [Scrollbar]s and overscroll indicators,
/// which are managed by the inherited [ScrollBehavior]. Placing a
/// [ScrollConfiguration] above a ScrollView can modify these behaviors for that
/// ScrollView, or can be managed app-wide by providing a ScrollBehavior to
/// [MaterialApp.scrollBehavior] or [CupertinoApp.scrollBehavior].
/// {@endtemplate}
///
768 769 770 771 772 773
/// 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.
774
abstract class BoxScrollView extends ScrollView {
775 776 777
  /// Creates a [ScrollView] uses a single child layout model.
  ///
  /// If the [primary] argument is true, the [controller] must be null.
778
  const BoxScrollView({
779 780 781 782 783 784 785
    super.key,
    super.scrollDirection,
    super.reverse,
    super.controller,
    super.primary,
    super.physics,
    super.shrinkWrap,
786
    this.padding,
787 788 789 790 791 792 793
    super.cacheExtent,
    super.semanticChildCount,
    super.dragStartBehavior,
    super.keyboardDismissBehavior,
    super.restorationId,
    super.clipBehavior,
  });
794

795
  /// The amount of space by which to inset the children.
796
  final EdgeInsetsGeometry? padding;
797 798 799 800

  @override
  List<Widget> buildSlivers(BuildContext context) {
    Widget sliver = buildChildLayout(context);
801
    EdgeInsetsGeometry? effectivePadding = padding;
802
    if (padding == null) {
803
      final MediaQueryData? mediaQuery = MediaQuery.maybeOf(context);
804 805 806 807 808 809 810 811 812 813 814
      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.
815
        sliver = MediaQuery(
816 817 818 819 820 821 822 823 824 825
          data: mediaQuery.copyWith(
            padding: scrollDirection == Axis.vertical
                ? mediaQueryHorizontalPadding
                : mediaQueryVerticalPadding,
          ),
          child: sliver,
        );
      }
    }

826
    if (effectivePadding != null) {
827
      sliver = SliverPadding(padding: effectivePadding, sliver: sliver);
828
    }
829 830 831
    return <Widget>[ sliver ];
  }

832
  /// Subclasses should override this method to build the layout model.
833 834 835 836
  @protected
  Widget buildChildLayout(BuildContext context);

  @override
837 838
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
839
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
840 841 842
  }
}

843
/// A scrollable list of widgets arranged linearly.
844
///
845 846
/// {@youtube 560 315 https://www.youtube.com/watch?v=KJpkjHGiI5A}
///
847 848 849 850 851
/// [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
852 853 854 855 856 857
/// 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
858 859 860 861
/// 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.
///
862 863 864
/// You can't specify both [itemExtent] and [prototypeItem], only one or none of
/// them.
///
865
/// There are four options for constructing a [ListView]:
866
///
867
///  1. The default constructor takes an explicit [List<Widget>] of children. This
868 869 870 871 872
///     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.
///
873 874 875
///  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
876 877
///     only for those children that are actually visible.
///
878 879 880 881 882 883 884
///  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,
885 886
///     a [SliverChildDelegate] can control the algorithm used to estimate the
///     size of children that are not actually visible.
887
///
888 889 890
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
891 892 893 894
/// 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.
///
895
/// {@tool snippet}
896 897 898
/// 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].
899
///
900 901
/// ![A ListView of 3 amber colored containers with sample text.](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view.png)
///
902 903
/// ```dart
/// ListView(
904
///   padding: const EdgeInsets.all(8),
905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924
///   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}
925
///
926
/// {@tool snippet}
927 928 929
/// 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.
930
///
931 932
/// ![A ListView of 3 amber colored containers with sample text.](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view_builder.png)
///
933
/// ```dart
934 935 936
/// final List<String> entries = <String>['A', 'B', 'C'];
/// final List<int> colorCodes = <int>[600, 500, 100];
///
937 938 939 940 941 942 943 944 945 946 947 948 949
/// Widget build(BuildContext context) {
///   return ListView.builder(
///     padding: const EdgeInsets.all(8),
///     itemCount: entries.length,
///     itemBuilder: (BuildContext context, int index) {
///       return Container(
///         height: 50,
///         color: Colors.amber[colorCodes[index]],
///         child: Center(child: Text('Entry ${entries[index]}')),
///       );
///     }
///   );
/// }
950 951
/// ```
/// {@end-tool}
952
///
953
/// {@tool snippet}
954 955 956 957
/// 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.
///
958 959 960
/// ![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)
///
961 962 963 964
/// ```dart
/// final List<String> entries = <String>['A', 'B', 'C'];
/// final List<int> colorCodes = <int>[600, 500, 100];
///
965 966 967 968 969 970 971 972 973 974 975 976 977 978
/// Widget build(BuildContext context) {
///   return ListView.separated(
///     padding: const EdgeInsets.all(8),
///     itemCount: entries.length,
///     itemBuilder: (BuildContext context, int index) {
///       return Container(
///         height: 50,
///         color: Colors.amber[colorCodes[index]],
///         child: Center(child: Text('Entry ${entries[index]}')),
///       );
///     },
///     separatorBuilder: (BuildContext context, int index) => const Divider(),
///   );
/// }
979
/// ```
980
/// {@end-tool}
981
///
982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
/// ## 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
1013
///    subtree's top render object child for keepalive. When the associated top
1014 1015 1016 1017 1018
///    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).
///
1019
///    This only works if `addAutomaticKeepAlives` and `addRepaintBoundaries`
1020 1021 1022 1023
///    are false since those parameters cause the [ListView] to wrap each child
///    widget subtree with other widgets.
///
///  * Using [AutomaticKeepAlive] widgets (inserted by default when
1024
///    `addAutomaticKeepAlives` is true). [AutomaticKeepAlive] allows descendant
1025 1026 1027
///    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.
1028 1029 1030
///
///    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
1031
///    have focus and no other descendants signaled for keepalive via a
1032 1033 1034 1035 1036
///    [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
1037 1038
///    [AutomaticKeepAliveClientMixin.wantKeepAlive] getter and calling
///    [AutomaticKeepAliveClientMixin.updateKeepAlive].
1039
///
1040 1041 1042
/// ## Transitioning to [CustomScrollView]
///
/// A [ListView] is basically a [CustomScrollView] with a single [SliverList] in
1043
/// its [CustomScrollView.slivers] property.
1044 1045 1046 1047 1048 1049 1050 1051 1052 1053
///
/// 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].
///
1054 1055 1056 1057
/// 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.
1058 1059 1060
///
/// The [childrenDelegate] property on [ListView] corresponds to the
/// [SliverList.delegate] (or [SliverFixedExtentList.delegate]) property. The
1061
/// [ListView] constructor's `children` argument corresponds to the
1062
/// [childrenDelegate] being a [SliverChildListDelegate] with that same
1063
/// argument. The [ListView.builder] constructor's `itemBuilder` and
Ian Hickson's avatar
Ian Hickson committed
1064 1065
/// `itemCount` arguments correspond to the [childrenDelegate] being a
/// [SliverChildBuilderDelegate] with the equivalent arguments.
1066 1067 1068 1069 1070
///
/// 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].
///
1071 1072 1073
/// [CustomScrollView]s don't automatically avoid obstructions from [MediaQuery]
/// like [ListView]s do. To reproduce the behavior, wrap the slivers in
/// [SliverSafeArea]s.
1074
///
1075 1076 1077 1078
/// Once code has been ported to use [CustomScrollView], other slivers, such as
/// [SliverGrid] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
/// list.
///
1079
/// {@tool snippet}
1080 1081 1082 1083 1084
///
/// Here are two brief snippets showing a [ListView] and its equivalent using
/// [CustomScrollView]:
///
/// ```dart
1085
/// ListView(
1086
///   padding: const EdgeInsets.all(20.0),
1087 1088 1089 1090 1091
///   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'),
1092 1093 1094
///   ],
/// )
/// ```
1095
/// {@end-tool}
1096
/// {@tool snippet}
1097 1098
///
/// ```dart
1099
/// CustomScrollView(
1100
///   slivers: <Widget>[
1101
///     SliverPadding(
1102
///       padding: const EdgeInsets.all(20.0),
1103 1104
///       sliver: SliverList(
///         delegate: SliverChildListDelegate(
1105
///           <Widget>[
1106
///             const Text("I'm dedicating every day to you"),
1107 1108 1109 1110 1111 1112 1113 1114 1115 1116
///             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'),
///           ],
///         ),
///       ),
///     ),
///   ],
/// )
/// ```
1117
/// {@end-tool}
1118
///
1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142
/// ## 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}'),
///             );
///           },
///         )
1143
///       : const Center(child: Text('No items')),
1144 1145 1146 1147 1148
///   );
/// }
/// ```
/// {@end-tool}
///
1149 1150
/// ## Selection of list items
///
1151
/// [ListView] has no built-in notion of a selected item or items. For a small
1152 1153 1154
/// example of how a caller might wire up basic item selection, see
/// [ListTile.selected].
///
1155 1156
/// {@tool dartpad}
/// This example shows a custom implementation of [ListTile] selection in a [ListView] or [GridView].
1157
/// Long press any [ListTile] to enable selection mode.
1158
///
1159
/// ** See code in examples/api/lib/widgets/scroll_view/list_view.0.dart **
1160 1161
/// {@end-tool}
///
1162 1163
/// {@macro flutter.widgets.BoxScroll.scrollBehaviour}
///
1164 1165
/// See also:
///
1166 1167 1168 1169
///  * [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.
1170
///  * [GridView], which is a scrollable, 2D array of widgets.
1171 1172
///  * [CustomScrollView], which is a scrollable widget that creates custom
///    scroll effects using slivers.
1173 1174
///  * [ListBody], which arranges its children in a similar manner, but without
///    scrolling.
1175
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
1176
///    the scroll position without using a [ScrollController].
1177
///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
1178 1179 1180 1181 1182
///  * 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)
1183
class ListView extends BoxScrollView {
1184 1185 1186 1187 1188 1189
  /// 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.
1190
  ///
1191 1192 1193 1194
  /// 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.
  ///
1195
  /// It is usually more efficient to create children on demand using
1196
  /// [ListView.builder] because it will create the widget children lazily as necessary.
1197
  ///
1198 1199 1200
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1201 1202 1203 1204
  /// [SliverChildListDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildListDelegate.addSemanticIndexes] property. None
  /// may be null.
1205
  ListView({
1206 1207 1208 1209 1210 1211 1212 1213
    super.key,
    super.scrollDirection,
    super.reverse,
    super.controller,
    super.primary,
    super.physics,
    super.shrinkWrap,
    super.padding,
1214
    this.itemExtent,
1215
    this.prototypeItem,
1216 1217
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1218
    bool addSemanticIndexes = true,
1219
    super.cacheExtent,
1220
    List<Widget> children = const <Widget>[],
1221
    int? semanticChildCount,
1222 1223 1224 1225
    super.dragStartBehavior,
    super.keyboardDismissBehavior,
    super.restorationId,
    super.clipBehavior,
1226 1227 1228 1229 1230
  }) : assert(
         itemExtent == null || prototypeItem == null,
         'You can only pass itemExtent or prototypeItem, not both.',
       ),
       childrenDelegate = SliverChildListDelegate(
1231
         children,
1232
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1233
         addRepaintBoundaries: addRepaintBoundaries,
1234
         addSemanticIndexes: addSemanticIndexes,
1235 1236 1237 1238
       ),
       super(
         semanticChildCount: semanticChildCount ?? children.length,
       );
1239

1240 1241 1242 1243 1244 1245
  /// 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.
  ///
1246
  /// Providing a non-null `itemCount` improves the ability of the [ListView] to
1247 1248
  /// estimate the maximum scroll extent.
  ///
1249 1250 1251
  /// The `itemBuilder` callback will be called only with indices greater than
  /// or equal to zero and less than `itemCount`.
  ///
1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267
  /// {@template flutter.widgets.ListView.builder.itemBuilder}
  /// It is legal for `itemBuilder` to return `null`. If it does, the scroll view
  /// will stop calling `itemBuilder`, even if it has yet to reach `itemCount`.
  /// By returning `null`, the [ScrollPosition.maxScrollExtent] will not be accurate
  /// unless the user has reached the end of the [ScrollView]. This can also cause the
  /// [Scrollbar] to grow as the user scrolls.
  ///
  /// For more accurate [ScrollMetrics], consider specifying `itemCount`.
  /// {@endtemplate}
  ///
  /// The `itemBuilder` should always 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.
1268
  ///
1269 1270
  /// {@macro flutter.widgets.PageView.findChildIndexCallback}
  ///
1271 1272 1273
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1274 1275 1276 1277
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None may be
  /// null.
1278
  ListView.builder({
1279 1280 1281 1282 1283 1284 1285 1286
    super.key,
    super.scrollDirection,
    super.reverse,
    super.controller,
    super.primary,
    super.physics,
    super.shrinkWrap,
    super.padding,
1287
    this.itemExtent,
1288
    this.prototypeItem,
1289
    required NullableIndexedWidgetBuilder itemBuilder,
1290
    ChildIndexGetter? findChildIndexCallback,
1291
    int? itemCount,
1292 1293
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1294
    bool addSemanticIndexes = true,
1295
    super.cacheExtent,
1296
    int? semanticChildCount,
1297 1298 1299 1300
    super.dragStartBehavior,
    super.keyboardDismissBehavior,
    super.restorationId,
    super.clipBehavior,
1301
  }) : assert(itemCount == null || itemCount >= 0),
1302
       assert(semanticChildCount == null || semanticChildCount <= itemCount!),
1303 1304 1305 1306
       assert(
         itemExtent == null || prototypeItem == null,
         'You can only pass itemExtent or prototypeItem, not both.',
       ),
1307
       childrenDelegate = SliverChildBuilderDelegate(
1308
         itemBuilder,
1309
         findChildIndexCallback: findChildIndexCallback,
1310
         childCount: itemCount,
1311
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1312
         addRepaintBoundaries: addRepaintBoundaries,
1313
         addSemanticIndexes: addSemanticIndexes,
1314 1315 1316 1317
       ),
       super(
         semanticChildCount: semanticChildCount ?? itemCount,
       );
1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334

  /// 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`.
  ///
1335 1336 1337 1338 1339 1340 1341
  /// The `itemBuilder` and `separatorBuilder` callbacks should always
  /// 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.
  ///
  /// {@macro flutter.widgets.ListView.builder.itemBuilder}
1342
  ///
1343 1344
  /// {@macro flutter.widgets.PageView.findChildIndexCallback}
  ///
1345
  /// {@tool snippet}
1346 1347 1348 1349 1350
  ///
  /// This example shows how to create [ListView] whose [ListTile] list items
  /// are separated by [Divider]s.
  ///
  /// ```dart
1351
  /// ListView.separated(
1352
  ///   itemCount: 25,
1353
  ///   separatorBuilder: (BuildContext context, int index) => const Divider(),
1354
  ///   itemBuilder: (BuildContext context, int index) {
1355 1356
  ///     return ListTile(
  ///       title: Text('item $index'),
1357 1358 1359 1360
  ///     );
  ///   },
  /// )
  /// ```
1361
  /// {@end-tool}
1362 1363 1364 1365
  ///
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1366 1367 1368 1369
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None may be
  /// null.
1370
  ListView.separated({
1371 1372 1373 1374 1375 1376 1377 1378
    super.key,
    super.scrollDirection,
    super.reverse,
    super.controller,
    super.primary,
    super.physics,
    super.shrinkWrap,
    super.padding,
1379
    required NullableIndexedWidgetBuilder itemBuilder,
1380
    ChildIndexGetter? findChildIndexCallback,
1381 1382
    required IndexedWidgetBuilder separatorBuilder,
    required int itemCount,
1383 1384
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1385
    bool addSemanticIndexes = true,
1386 1387 1388 1389 1390
    super.cacheExtent,
    super.dragStartBehavior,
    super.keyboardDismissBehavior,
    super.restorationId,
    super.clipBehavior,
1391
  }) : assert(itemCount >= 0),
1392
       itemExtent = null,
1393
       prototypeItem = null,
1394
       childrenDelegate = SliverChildBuilderDelegate(
1395 1396
         (BuildContext context, int index) {
           final int itemIndex = index ~/ 2;
1397
           if (index.isEven) {
1398
             return itemBuilder(context, itemIndex);
1399
           }
1400
           return separatorBuilder(context, itemIndex);
1401
         },
1402
         findChildIndexCallback: findChildIndexCallback,
1403
         childCount: _computeActualChildCount(itemCount),
1404 1405
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
1406
         addSemanticIndexes: addSemanticIndexes,
1407
         semanticIndexCallback: (Widget widget, int index) {
1408
           return index.isEven ? index ~/ 2 : null;
1409
         },
1410 1411
       ),
       super(
1412
         semanticChildCount: itemCount,
1413
       );
1414

1415 1416 1417 1418
  /// 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.
1419
  ///
1420
  /// {@tool snippet}
1421 1422 1423 1424 1425 1426
  ///
  /// This [ListView] uses a custom [SliverChildBuilderDelegate] to support child
  /// reordering.
  ///
  /// ```dart
  /// class MyListView extends StatefulWidget {
1427
  ///   const MyListView({super.key});
1428
  ///
1429
  ///   @override
1430
  ///   State<MyListView> createState() => _MyListViewState();
1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455
  /// }
  ///
  /// 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) {
1456
  ///               final ValueKey<String> valueKey = key as ValueKey<String>;
1457 1458 1459 1460 1461 1462 1463 1464 1465 1466
  ///               final String data = valueKey.value;
  ///               return items.indexOf(data);
  ///             }
  ///           ),
  ///         ),
  ///       ),
  ///       bottomNavigationBar: BottomAppBar(
  ///         child: Row(
  ///           mainAxisAlignment: MainAxisAlignment.center,
  ///           children: <Widget>[
1467
  ///             TextButton(
1468
  ///               onPressed: () => _reverse(),
1469
  ///               child: const Text('Reverse items'),
1470 1471 1472 1473 1474 1475 1476 1477 1478
  ///             ),
  ///           ],
  ///         ),
  ///       ),
  ///     );
  ///   }
  /// }
  ///
  /// class KeepAlive extends StatefulWidget {
1479 1480 1481 1482
  ///   const KeepAlive({
  ///     required Key key,
  ///     required this.data,
  ///   }) : super(key: key);
1483 1484 1485 1486
  ///
  ///   final String data;
  ///
  ///   @override
1487
  ///   State<KeepAlive> createState() => _KeepAliveState();
1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501
  /// }
  ///
  /// 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}
1502
  const ListView.custom({
1503 1504 1505 1506 1507 1508 1509 1510
    super.key,
    super.scrollDirection,
    super.reverse,
    super.controller,
    super.primary,
    super.physics,
    super.shrinkWrap,
    super.padding,
1511
    this.itemExtent,
1512
    this.prototypeItem,
1513
    required this.childrenDelegate,
1514 1515 1516 1517 1518 1519
    super.cacheExtent,
    super.semanticChildCount,
    super.dragStartBehavior,
    super.keyboardDismissBehavior,
    super.restorationId,
    super.clipBehavior,
1520
  }) : assert(
1521 1522
         itemExtent == null || prototypeItem == null,
         'You can only pass itemExtent or prototypeItem, not both',
1523
       );
1524

1525
  /// {@template flutter.widgets.list_view.itemExtent}
1526 1527 1528 1529 1530 1531 1532
  /// 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.
1533 1534 1535 1536 1537 1538 1539 1540
  ///
  /// 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.
1541
  /// {@endtemplate}
1542
  final double? itemExtent;
1543

1544
  /// {@template flutter.widgets.list_view.prototypeItem}
1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559
  /// 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.
1560
  /// {@endtemplate}
1561 1562
  final Widget? prototypeItem;

1563 1564 1565 1566 1567 1568
  /// 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.
1569
  final SliverChildDelegate childrenDelegate;
1570 1571 1572 1573

  @override
  Widget buildChildLayout(BuildContext context) {
    if (itemExtent != null) {
1574
      return SliverFixedExtentList(
1575
        delegate: childrenDelegate,
1576
        itemExtent: itemExtent!,
1577
      );
1578 1579 1580 1581 1582
    } else if (prototypeItem != null) {
      return SliverPrototypeExtentList(
        delegate: childrenDelegate,
        prototypeItem: prototypeItem!,
      );
1583
    }
1584
    return SliverList(delegate: childrenDelegate);
1585 1586 1587
  }

  @override
1588 1589
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1590
    properties.add(DoubleProperty('itemExtent', itemExtent, defaultValue: null));
1591
  }
1592

1593 1594
  // Helper method to compute the actual child count for the separated constructor.
  static int _computeActualChildCount(int itemCount) {
1595 1596
    return math.max(0, itemCount * 2 - 1);
  }
1597 1598
}

1599 1600
/// A scrollable, 2D array of widgets.
///
1601 1602
/// {@youtube 560 315 https://www.youtube.com/watch?v=bLOtZDTm4H8}
///
1603 1604 1605
/// The main axis direction of a grid is the direction in which it scrolls (the
/// [scrollDirection]).
///
1606 1607 1608
/// 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
1609
/// cross-axis extent. A custom [SliverGridDelegate] can produce an arbitrary 2D
1610 1611 1612 1613
/// arrangement of children, including arrangements that are unaligned or
/// overlapping.
///
/// To create a grid with a large (or infinite) number of children, use the
1614 1615 1616 1617 1618
/// [GridView.builder] constructor with either a
/// [SliverGridDelegateWithFixedCrossAxisCount] or a
/// [SliverGridDelegateWithMaxCrossAxisExtent] for the [gridDelegate].
///
/// To use a custom [SliverChildDelegate], use [GridView.custom].
1619 1620
///
/// To create a linear array of children, use a [ListView].
1621
///
1622 1623 1624
/// To control the initial scroll offset of the scroll view, provide a
/// [controller] with its [ScrollController.initialScrollOffset] property set.
///
1625 1626 1627
/// ## Transitioning to [CustomScrollView]
///
/// A [GridView] is basically a [CustomScrollView] with a single [SliverGrid] in
1628
/// its [CustomScrollView.slivers] property.
1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645
///
/// 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.
///
1646
/// The [GridView], [GridView.count], and [GridView.extent]
1647
/// constructors' `children` arguments correspond to the [childrenDelegate]
1648 1649
/// being a [SliverChildListDelegate] with that same argument. The
/// [GridView.builder] constructor's `itemBuilder` and `childCount` arguments
1650 1651 1652
/// correspond to the [childrenDelegate] being a [SliverChildBuilderDelegate]
/// with the matching arguments.
///
1653
/// The [GridView.count] and [GridView.extent] constructors create
1654
/// custom grid delegates, and have equivalently named constructors on
1655 1656
/// [SliverGrid] to ease the transition: [SliverGrid.count] and
/// [SliverGrid.extent] respectively.
1657 1658 1659 1660 1661 1662
///
/// 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
1663
/// [SliverList] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
1664 1665
/// list.
///
1666
/// {@tool snippet}
1667
/// This example demonstrates how to create a [GridView] with two columns. The
1668
/// children are spaced apart using the `crossAxisSpacing` and `mainAxisSpacing`
1669
/// properties.
1670
///
1671
/// ![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)
1672 1673
///
/// ```dart
1674
/// GridView.count(
1675
///   primary: false,
1676 1677 1678
///   padding: const EdgeInsets.all(20),
///   crossAxisSpacing: 10,
///   mainAxisSpacing: 10,
1679 1680
///   crossAxisCount: 2,
///   children: <Widget>[
1681 1682 1683
///     Container(
///       padding: const EdgeInsets.all(8),
///       color: Colors.teal[100],
1684
///       child: const Text("He'd have you all unravel at the"),
1685 1686 1687 1688
///     ),
///     Container(
///       padding: const EdgeInsets.all(8),
///       color: Colors.teal[200],
1689
///       child: const Text('Heed not the rabble'),
1690 1691 1692 1693
///     ),
///     Container(
///       padding: const EdgeInsets.all(8),
///       color: Colors.teal[300],
1694
///       child: const Text('Sound of screams but the'),
1695 1696 1697 1698
///     ),
///     Container(
///       padding: const EdgeInsets.all(8),
///       color: Colors.teal[400],
1699
///       child: const Text('Who scream'),
1700 1701 1702 1703
///     ),
///     Container(
///       padding: const EdgeInsets.all(8),
///       color: Colors.teal[500],
1704
///       child: const Text('Revolution is coming...'),
1705 1706 1707 1708
///     ),
///     Container(
///       padding: const EdgeInsets.all(8),
///       color: Colors.teal[600],
1709
///       child: const Text('Revolution, they...'),
1710
///     ),
1711 1712 1713
///   ],
/// )
/// ```
1714
/// {@end-tool}
1715
///
1716
/// {@tool snippet}
1717 1718 1719
/// This example shows how to create the same grid as the previous example
/// using a [CustomScrollView] and a [SliverGrid].
///
1720
/// ![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)
1721 1722
///
/// ```dart
1723
/// CustomScrollView(
1724 1725
///   primary: false,
///   slivers: <Widget>[
1726
///     SliverPadding(
1727
///       padding: const EdgeInsets.all(20),
1728
///       sliver: SliverGrid.count(
1729 1730
///         crossAxisSpacing: 10,
///         mainAxisSpacing: 10,
1731 1732
///         crossAxisCount: 2,
///         children: <Widget>[
1733 1734 1735
///           Container(
///             padding: const EdgeInsets.all(8),
///             color: Colors.green[100],
1736
///             child: const Text("He'd have you all unravel at the"),
1737 1738 1739 1740
///           ),
///           Container(
///             padding: const EdgeInsets.all(8),
///             color: Colors.green[200],
1741
///             child: const Text('Heed not the rabble'),
1742 1743 1744 1745
///           ),
///           Container(
///             padding: const EdgeInsets.all(8),
///             color: Colors.green[300],
1746
///             child: const Text('Sound of screams but the'),
1747 1748 1749 1750
///           ),
///           Container(
///             padding: const EdgeInsets.all(8),
///             color: Colors.green[400],
1751
///             child: const Text('Who scream'),
1752 1753 1754 1755
///           ),
///           Container(
///             padding: const EdgeInsets.all(8),
///             color: Colors.green[500],
1756
///             child: const Text('Revolution is coming...'),
1757 1758 1759 1760
///           ),
///           Container(
///             padding: const EdgeInsets.all(8),
///             color: Colors.green[600],
1761
///             child: const Text('Revolution, they...'),
1762
///           ),
1763 1764 1765 1766 1767 1768
///         ],
///       ),
///     ),
///   ],
/// )
/// ```
1769
/// {@end-tool}
1770
///
1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801
/// 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}
///
1802 1803 1804 1805
/// {@tool dartpad}
/// This example shows a custom implementation of [ListTile] selection in a [GridView] or [ListView].
/// Long press any ListTile to enable selection mode.
///
1806
/// ** See code in examples/api/lib/widgets/scroll_view/list_view.0.dart **
1807 1808
/// {@end-tool}
///
1809 1810
/// See also:
///
1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821
///  * [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.
1822
///  * [ScrollNotification] and [NotificationListener], which can be used to watch
1823
///    the scroll position without using a [ScrollController].
1824
///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
1825
class GridView extends BoxScrollView {
1826 1827 1828 1829
  /// Creates a scrollable, 2D array of widgets with a custom
  /// [SliverGridDelegate].
  ///
  /// The [gridDelegate] argument must not be null.
1830
  ///
1831 1832 1833 1834
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1835
  /// null.
1836
  GridView({
1837 1838 1839 1840 1841 1842 1843 1844
    super.key,
    super.scrollDirection,
    super.reverse,
    super.controller,
    super.primary,
    super.physics,
    super.shrinkWrap,
    super.padding,
1845
    required this.gridDelegate,
1846 1847
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1848
    bool addSemanticIndexes = true,
1849
    super.cacheExtent,
1850
    List<Widget> children = const <Widget>[],
1851
    int? semanticChildCount,
1852 1853 1854 1855
    super.dragStartBehavior,
    super.clipBehavior,
    super.keyboardDismissBehavior,
    super.restorationId,
1856
  }) : childrenDelegate = SliverChildListDelegate(
1857
         children,
1858
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1859
         addRepaintBoundaries: addRepaintBoundaries,
1860
         addSemanticIndexes: addSemanticIndexes,
1861
       ),
1862
       super(
1863
         semanticChildCount: semanticChildCount ?? children.length,
1864
       );
1865

1866 1867 1868 1869 1870 1871
  /// 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.
  ///
1872
  /// Providing a non-null `itemCount` improves the ability of the [GridView] to
1873 1874
  /// estimate the maximum scroll extent.
  ///
1875 1876
  /// `itemBuilder` will be called only with indices greater than or equal to
  /// zero and less than `itemCount`.
1877
  ///
1878 1879
  /// {@macro flutter.widgets.ListView.builder.itemBuilder}
  ///
1880 1881
  /// {@macro flutter.widgets.PageView.findChildIndexCallback}
  ///
1882
  /// The [gridDelegate] argument is required.
1883
  ///
1884 1885 1886
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
1887 1888 1889
  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
  /// `addSemanticIndexes` argument corresponds to the
  /// [SliverChildBuilderDelegate.addSemanticIndexes] property.
1890
  GridView.builder({
1891 1892 1893 1894 1895 1896 1897 1898
    super.key,
    super.scrollDirection,
    super.reverse,
    super.controller,
    super.primary,
    super.physics,
    super.shrinkWrap,
    super.padding,
1899
    required this.gridDelegate,
1900
    required NullableIndexedWidgetBuilder itemBuilder,
1901
    ChildIndexGetter? findChildIndexCallback,
1902
    int? itemCount,
1903 1904
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1905
    bool addSemanticIndexes = true,
1906
    super.cacheExtent,
1907
    int? semanticChildCount,
1908 1909 1910 1911
    super.dragStartBehavior,
    super.keyboardDismissBehavior,
    super.restorationId,
    super.clipBehavior,
1912
  }) : childrenDelegate = SliverChildBuilderDelegate(
1913
         itemBuilder,
1914
         findChildIndexCallback: findChildIndexCallback,
1915
         childCount: itemCount,
1916
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1917
         addRepaintBoundaries: addRepaintBoundaries,
1918
         addSemanticIndexes: addSemanticIndexes,
1919
       ),
1920
       super(
1921
         semanticChildCount: semanticChildCount ?? itemCount,
1922
       );
1923

1924 1925 1926
  /// Creates a scrollable, 2D array of widgets with both a custom
  /// [SliverGridDelegate] and a custom [SliverChildDelegate].
  ///
1927 1928
  /// To use an [IndexedWidgetBuilder] callback to build children, either use
  /// a [SliverChildBuilderDelegate] or use the [GridView.builder] constructor.
1929 1930
  ///
  /// The [gridDelegate] and [childrenDelegate] arguments must not be null.
1931
  const GridView.custom({
1932 1933 1934 1935 1936 1937 1938 1939
    super.key,
    super.scrollDirection,
    super.reverse,
    super.controller,
    super.primary,
    super.physics,
    super.shrinkWrap,
    super.padding,
1940 1941
    required this.gridDelegate,
    required this.childrenDelegate,
1942 1943 1944 1945 1946 1947
    super.cacheExtent,
    super.semanticChildCount,
    super.dragStartBehavior,
    super.keyboardDismissBehavior,
    super.restorationId,
    super.clipBehavior,
1948
  });
1949

1950 1951 1952 1953
  /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
  /// the cross axis.
  ///
  /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate].
1954
  ///
1955 1956 1957 1958
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
1959 1960
  /// null.
  ///
1961 1962
  /// See also:
  ///
1963
  ///  * [SliverGrid.count], the equivalent constructor for [SliverGrid].
1964
  GridView.count({
1965 1966 1967 1968 1969 1970 1971 1972
    super.key,
    super.scrollDirection,
    super.reverse,
    super.controller,
    super.primary,
    super.physics,
    super.shrinkWrap,
    super.padding,
1973
    required int crossAxisCount,
1974 1975 1976 1977 1978
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
1979
    bool addSemanticIndexes = true,
1980
    super.cacheExtent,
1981
    List<Widget> children = const <Widget>[],
1982
    int? semanticChildCount,
1983 1984 1985 1986
    super.dragStartBehavior,
    super.keyboardDismissBehavior,
    super.restorationId,
    super.clipBehavior,
1987
  }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
1988 1989 1990 1991 1992
         crossAxisCount: crossAxisCount,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
1993
       childrenDelegate = SliverChildListDelegate(
1994
         children,
1995
         addAutomaticKeepAlives: addAutomaticKeepAlives,
1996
         addRepaintBoundaries: addRepaintBoundaries,
1997
         addSemanticIndexes: addSemanticIndexes,
1998 1999 2000 2001
       ),
       super(
         semanticChildCount: semanticChildCount ?? children.length,
       );
2002

2003 2004
  /// Creates a scrollable, 2D array of widgets with tiles that each have a
  /// maximum cross-axis extent.
2005 2006
  ///
  /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate].
2007
  ///
2008 2009 2010 2011
  /// The `addAutomaticKeepAlives` argument corresponds to the
  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
  /// `addRepaintBoundaries` argument corresponds to the
  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
2012 2013
  /// null.
  ///
2014 2015
  /// See also:
  ///
2016
  ///  * [SliverGrid.extent], the equivalent constructor for [SliverGrid].
2017
  GridView.extent({
2018 2019 2020 2021 2022 2023 2024 2025
    super.key,
    super.scrollDirection,
    super.reverse,
    super.controller,
    super.primary,
    super.physics,
    super.shrinkWrap,
    super.padding,
2026
    required double maxCrossAxisExtent,
2027 2028 2029 2030 2031
    double mainAxisSpacing = 0.0,
    double crossAxisSpacing = 0.0,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
2032
    bool addSemanticIndexes = true,
2033
    super.cacheExtent,
2034
    List<Widget> children = const <Widget>[],
2035
    int? semanticChildCount,
2036 2037 2038 2039
    super.dragStartBehavior,
    super.keyboardDismissBehavior,
    super.restorationId,
    super.clipBehavior,
2040
  }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
2041 2042 2043 2044 2045
         maxCrossAxisExtent: maxCrossAxisExtent,
         mainAxisSpacing: mainAxisSpacing,
         crossAxisSpacing: crossAxisSpacing,
         childAspectRatio: childAspectRatio,
       ),
2046
       childrenDelegate = SliverChildListDelegate(
2047
         children,
2048
         addAutomaticKeepAlives: addAutomaticKeepAlives,
2049
         addRepaintBoundaries: addRepaintBoundaries,
2050
         addSemanticIndexes: addSemanticIndexes,
2051 2052 2053 2054
       ),
       super(
         semanticChildCount: semanticChildCount ?? children.length,
       );
2055

2056 2057
  /// A delegate that controls the layout of the children within the [GridView].
  ///
2058
  /// The [GridView], [GridView.builder], and [GridView.custom] constructors let you specify this
2059 2060
  /// delegate explicitly. The other constructors create a [gridDelegate]
  /// implicitly.
2061 2062
  final SliverGridDelegate gridDelegate;

2063 2064 2065 2066 2067
  /// 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.
2068
  final SliverChildDelegate childrenDelegate;
2069

2070
  @override
2071
  Widget buildChildLayout(BuildContext context) {
2072
    return SliverGrid(
2073
      delegate: childrenDelegate,
2074 2075
      gridDelegate: gridDelegate,
    );
Adam Barth's avatar
Adam Barth committed
2076 2077
  }
}