From 49d32d35ea05a9a8fdf373936547df03605e0d31 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Tue, 2 May 2017 10:46:49 -0700 Subject: [PATCH] Add more docs about scrolling (#9711) --- packages/flutter/lib/src/rendering/debug.dart | 2 +- .../lib/src/widgets/scroll_controller.dart | 9 ++ .../lib/src/widgets/scroll_metrics.dart | 2 +- .../lib/src/widgets/scroll_notification.dart | 97 ++++++++++++++++++- .../lib/src/widgets/scroll_physics.dart | 24 +++++ .../lib/src/widgets/scroll_simulation.dart | 8 ++ .../flutter/lib/src/widgets/scrollable.dart | 80 +++++++++++++++ 7 files changed, 219 insertions(+), 3 deletions(-) diff --git a/packages/flutter/lib/src/rendering/debug.dart b/packages/flutter/lib/src/rendering/debug.dart index aa78e24802..de2c3638a5 100644 --- a/packages/flutter/lib/src/rendering/debug.dart +++ b/packages/flutter/lib/src/rendering/debug.dart @@ -62,7 +62,7 @@ bool debugPaintBaselinesEnabled = false; /// The color to use when painting alphabetic baselines. Color debugPaintAlphabeticBaselineColor = _kDebugPaintAlphabeticBaselineColor; -/// The color ot use when painting ideographic baselines. +/// The color to use when painting ideographic baselines. Color debugPaintIdeographicBaselineColor = _kDebugPaintIdeographicBaselineColor; /// Causes each Layer to paint a box around its bounds. diff --git a/packages/flutter/lib/src/widgets/scroll_controller.dart b/packages/flutter/lib/src/widgets/scroll_controller.dart index 8bdae95db4..1d671551cb 100644 --- a/packages/flutter/lib/src/widgets/scroll_controller.dart +++ b/packages/flutter/lib/src/widgets/scroll_controller.dart @@ -201,6 +201,15 @@ class ScrollController extends ChangeNotifier { return '$runtimeType#$hashCode(${description.join(", ")})'; } + /// Add additional information to the given description for use by [toString]. + /// + /// This method makes it easier for subclasses to coordinate to provide a + /// high-quality [toString] implementation. The [toString] implementation on + /// the [ScrollController] base class calls [debugFillDescription] to collect + /// useful information from subclasses to incorporate into its return value. + /// + /// If you override this, make sure to start your method with a call to + /// `super.debugFillDescription(description)`. @mustCallSuper void debugFillDescription(List description) { if (debugLabel != null) diff --git a/packages/flutter/lib/src/widgets/scroll_metrics.dart b/packages/flutter/lib/src/widgets/scroll_metrics.dart index eec43e0d00..57b5251526 100644 --- a/packages/flutter/lib/src/widgets/scroll_metrics.dart +++ b/packages/flutter/lib/src/widgets/scroll_metrics.dart @@ -7,7 +7,7 @@ import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; -/// A description of a [Scrollable]'s contents, useful for modelling the state +/// A description of a [Scrollable]'s contents, useful for modeling the state /// of its viewport. /// /// This class defines a current position, [pixels], and a range of values diff --git a/packages/flutter/lib/src/widgets/scroll_notification.dart b/packages/flutter/lib/src/widgets/scroll_notification.dart index 25a83c4244..b5df39498d 100644 --- a/packages/flutter/lib/src/widgets/scroll_notification.dart +++ b/packages/flutter/lib/src/widgets/scroll_notification.dart @@ -39,13 +39,41 @@ abstract class ViewportNotificationMixin extends Notification { } } +/// A [Notification] related to scrolling. +/// +/// [Scrollable] widgets notify their ancestors about scrolling-related changes. +/// The notifications have the following lifecycle: +/// +/// * A [ScrollStartNotification], which indicates that the widget has started +/// scrolling. +/// * Zero or more [ScrollUpdateNotification]s, which indicate that the widget +/// has changed its scroll position, mixed with zero or more +/// [OverscrollNotification]s, which indicate that the widget has not changed +/// its scroll position because the change would have caused its scroll +/// position to go outside its scroll bounds. +/// * Interspersed with the [ScrollUpdateNotification]s and +/// [OverscrollNotification]s are zero or more [UserScrollNotification]s, +/// which indicate that the user has changed the direciton in which they are +/// scrolling. +/// * A [ScrollEndNotification], which indicates that the widget has stopped +/// scrolling. +/// * A [UserScrollNotification], with a [UserScrollNotification.direciton] of +/// [ScrollDirection.idle]. +/// +/// Notifications bubble up through the tree, which means a given +/// [NotificationListener] will receive notifications for all descendant +/// [Scrollable] widgets. To focus on notifications from the nearest +/// [Scrollable] descendant, check that the [depth] property of the notification +/// is zero. abstract class ScrollNotification extends LayoutChangedNotification with ViewportNotificationMixin { - /// Creates a notification about scrolling. + /// Initializes fields for subclasses. ScrollNotification({ @required this.metrics, @required this.context, }); + // A description of a [Scrollable]'s contents, useful for modeling the state + /// of its viewport. final ScrollMetrics metrics; /// The build context of the widget that fired this notification. @@ -61,13 +89,24 @@ abstract class ScrollNotification extends LayoutChangedNotification with Viewpor } } +/// A notification that a [Scrollable] widget has started scrolling. +/// +/// See also: +/// +/// * [ScrollEndNotification], which indicates that scrolling has stopped. +/// * [ScrollNotification], which describes the notification lifecycle. class ScrollStartNotification extends ScrollNotification { + /// Creates a notification that a [Scrollable] widget has started scrolling. ScrollStartNotification({ @required ScrollMetrics metrics, @required BuildContext context, this.dragDetails, }) : super(metrics: metrics, context: context); + /// If the [Scrollable] started scrolling because of a drag, the details about + /// that drag start. + /// + /// Otherwise, null. final DragStartDetails dragDetails; @override @@ -78,7 +117,17 @@ class ScrollStartNotification extends ScrollNotification { } } +/// A notification that a [Scrollable] widget has changed its scroll position. +/// +/// See also: +/// +/// * [OverscrollNotification], which indicates that a [Scrollable] widget +/// has not changed its scroll position because the change would have caused +/// its scroll position to go outside its scroll bounds. +/// * [ScrollNotification], which describes the notification lifecycle. class ScrollUpdateNotification extends ScrollNotification { + /// Creates a notification that a [Scrollable] widget has changed its scroll + /// position. ScrollUpdateNotification({ @required ScrollMetrics metrics, @required BuildContext context, @@ -86,6 +135,10 @@ class ScrollUpdateNotification extends ScrollNotification { this.scrollDelta, }) : super(metrics: metrics, context: context); + /// If the [Scrollable] changed its scroll position because of a drag, the + /// details about that drag update. + /// + /// Otherwise, null. final DragUpdateDetails dragDetails; /// The distance by which the [Scrollable] was scrolled, in logical pixels. @@ -100,7 +153,18 @@ class ScrollUpdateNotification extends ScrollNotification { } } +/// A notification that a [Scrollable] widget has not changed its scroll position +/// because the change would have caused its scroll position to go outside of +/// its scroll bounds. +/// +/// See also: +/// +/// * [ScrollUpdateNotification], which indicates that a [Scrollable] widget +/// has changed its scroll position. +/// * [ScrollNotification], which describes the notification lifecycle. class OverscrollNotification extends ScrollNotification { + /// Creates a notification that a [Scrollable] widget has changed its scroll + /// position outside of its scroll bounds. OverscrollNotification({ @required ScrollMetrics metrics, @required BuildContext context, @@ -114,6 +178,10 @@ class OverscrollNotification extends ScrollNotification { assert(velocity != null); } + /// If the [Scrollable] overscrolled because of a drag, the details about that + /// drag update. + /// + /// Otherwise, null. final DragUpdateDetails dragDetails; /// The number of logical pixels that the [Scrollable] avoided scrolling. @@ -140,13 +208,31 @@ class OverscrollNotification extends ScrollNotification { } } +/// A notification that a [Scrollable] widget has stopped scrolling. +/// +/// See also: +/// +/// * [ScrollStartNotification], which indicates that scrolling has started. +/// * [ScrollNotification], which describes the notification lifecycle. class ScrollEndNotification extends ScrollNotification { + /// Creates a notification that a [Scrollable] widget has stopped scrolling. ScrollEndNotification({ @required ScrollMetrics metrics, @required BuildContext context, this.dragDetails, }) : super(metrics: metrics, context: context); + /// If the [Scrollable] stopped scrolling because of a drag, the details about + /// that drag end. + /// + /// Otherwise, null. + /// + /// If a drag ends with some residual velocity, a typical [ScrollPhysics] will + /// start a ballistic scroll, which delays the [ScrollEndNotification] until + /// the ballistic simulation completes, at which time [dragDetails] will + /// be null. If the residtual velocity is too small to trigger ballistic + /// scrolling, then the [ScrollEndNotification] will be dispatched immediately + /// and [dragDetails] will be non-null. final DragEndDetails dragDetails; @override @@ -157,13 +243,22 @@ class ScrollEndNotification extends ScrollNotification { } } +/// A notification that the user has changed the direction in which they are +/// scrolling. +/// +/// See also: +/// +/// * [ScrollNotification], which describes the notification lifecycle. class UserScrollNotification extends ScrollNotification { + /// Creates a notification that the user has changed the direction in which + /// they are scrolling. UserScrollNotification({ @required ScrollMetrics metrics, @required BuildContext context, this.direction, }) : super(metrics: metrics, context: context); + /// The direction in which the user is scrolling. final ScrollDirection direction; @override diff --git a/packages/flutter/lib/src/widgets/scroll_physics.dart b/packages/flutter/lib/src/widgets/scroll_physics.dart index 9af5b57644..3cd0738870 100644 --- a/packages/flutter/lib/src/widgets/scroll_physics.dart +++ b/packages/flutter/lib/src/widgets/scroll_physics.dart @@ -15,12 +15,34 @@ import 'scroll_simulation.dart'; export 'package:flutter/physics.dart' show Tolerance; +/// Determines the physics of a [Scrollable] widget. +/// +/// For example, determines how the [Scrollable] will behave when the user +/// reaches the maximum scroll extent or when the user stops scrolling. +/// +/// When starting a physics [Simulation], the current scroll position and +/// velocity are used as the initial conditions for the particle in the +/// simulation. The movement of the particle in the simulation is then used to +/// determine the scroll position for the widget. @immutable class ScrollPhysics { + /// Creates an object with the default scroll physics. const ScrollPhysics({ this.parent }); + /// If non-null, determines the default behavior for each method. + /// + /// If a subclass of [ScrollPhysics] does not override a method, that subclass + /// will inherit an implementation from this base class that defers to + /// [parent]. This mechanism lets you assemble novel combinations of + /// [ScrollPhysics] subclasses at runtime. final ScrollPhysics parent; + /// Return a [ScrollPhysics] with the same [runtimeType] where the [parent] + /// has been replaced with the given [parent]. + /// + /// The returned object will combine some of the behaviors from this + /// [ScrollPhysics] instance and some of the behaviors from the given + /// [ScrollPhysics] instance. ScrollPhysics applyTo(ScrollPhysics parent) => new ScrollPhysics(parent: parent); /// Used by [DragScrollActivity] and other user-driven activities to @@ -111,6 +133,7 @@ class ScrollPhysics { ratio: 1.1, ); + /// The spring to use for ballistic simulations. SpringDescription get spring => parent?.spring ?? _kDefaultSpring; /// The default accuracy to which scrolling is computed. @@ -121,6 +144,7 @@ class ScrollPhysics { distance: 1.0 / ui.window.devicePixelRatio // logical pixels ); + /// The tolerance to use for ballistic simulations. Tolerance get tolerance => parent?.tolerance ?? _kDefaultTolerance; /// The minimum distance an input pointer drag must have moved to diff --git a/packages/flutter/lib/src/widgets/scroll_simulation.dart b/packages/flutter/lib/src/widgets/scroll_simulation.dart index d1f2d2dde1..8f66069d70 100644 --- a/packages/flutter/lib/src/widgets/scroll_simulation.dart +++ b/packages/flutter/lib/src/widgets/scroll_simulation.dart @@ -120,8 +120,16 @@ class ClampingScrollSimulation extends Simulation { _distance = (velocity * _duration / _kInitialVelocityPenetration).abs(); } + /// The position of the particle at the beginning of the simulation. final double position; + + /// The velocity at which the particle is traveling at the beginning of the + /// simulation. final double velocity; + + /// The amount of friction the particle experiences as it travels. + /// + /// The more friction the particle experiences, the sooner it stops. final double friction; double _duration; diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index cbf75461aa..644d379c50 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -24,9 +24,53 @@ import 'viewport.dart'; export 'package:flutter/physics.dart' show Tolerance; +/// Signature used by [Scrollable] to build the viewport through which the +/// scrollable content is displayed. typedef Widget ViewportBuilder(BuildContext context, ViewportOffset position); +/// A widget that scrolls. +/// +/// [Scrollable] implements the interaction model for a scrollable widget, +/// including gesture recognition, but does not have an opinion about how the +/// viewport, which actually displays the children, is constructed. +/// +/// It's rare to construct a [Scrollable] directly. Instead, consider [ListView] +/// or [GridView], which combine scrolling, viewporting, and a layout model. To +/// combine layout models (or to use a custom layout mode), consider using +/// [CustomScrollView]. +/// +/// The static [Scrollable.of] and [Scrollable.ensureVisible] functions are +/// often used to interact with the [Scrollable] widget inside a [ListView] or +/// a [GridView]. +/// +/// To further customize scrolling behavior with a [Scrollable]: +/// +/// 1. You can provide a [viewportBuilder] to customize the child model. For +/// example, [SingleChildScrollView] uses a viewport that displays a single +/// box child whereas [CustomScrollView] uses a [Viewport] or a +/// [ShrinkWrappingViewport], both of which display a list of slivers. +/// +/// 2. You can provide a custom [ScrollController] that creates a custom +/// [ScrollPosition] subclass. For example, [PageView] uses a +/// [PageController], which creates a page-oriented scroll position subclass +/// that keeps the same page visible when the [Scrollable] resizes. +/// +/// 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. +/// * [SingleChildScrollView], which is a scrollable widget that has a single +/// child. class Scrollable extends StatefulWidget { + /// Creates a widget that scrolls. + /// + /// The [axisDirection] and [viewportBuilder] arguments must not be null. const Scrollable({ Key key, this.axisDirection: AxisDirection.down, @@ -37,14 +81,50 @@ class Scrollable extends StatefulWidget { assert(viewportBuilder != null), super (key: key); + /// The direction in which this widget scrolls. + /// + /// For example, if the [axisDirection] is [AxisDirection.down], increasing + /// the scroll position will cause content below the bottom of the viewport to + /// become visible through the viewport. Similarly, if [axisDirection] is + /// [AxisDirection.right], increasing the scroll position will cause content + /// beyond the right edge of the viewport to become visible through the + /// viewport. + /// + /// Defaults to [AxisDirection.down]. final AxisDirection axisDirection; + /// An object that can be used to control the position to which this widget is + /// scrolled. + /// + /// See also: + /// + /// * [ensureVisible], which animates the scroll position to reveal a given + /// [BuildContext]. final ScrollController controller; + /// How the widgets should respond to user input. + /// + /// For example, determines how the widget continues to animate after the + /// user stops dragging the scroll view. + /// + /// Defaults to matching platform conventions. final ScrollPhysics physics; + /// Builds the viewport through which the scrollable content is displayed. + /// + /// A typical viewport uses the given [ViewportOffset] to determine which part + /// of its content is actually visible through the viewport. + /// + /// See also: + /// + /// * [Viewport], which is a viewport that displays a list of slivers. + /// * [ShrinkWrappingViewport], which is a viewport that displays a list of + /// slivers and sizes itself based on the size of the slivers. final ViewportBuilder viewportBuilder; + /// The axis along which the scroll view scrolls. + /// + /// Determined by the [axisDirection]. Axis get axis => axisDirectionToAxis(axisDirection); @override