Commit 9fe68ef7 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Document ScrollPosition (#9731)

parent f64bfba8
...@@ -55,7 +55,8 @@ ScrollDirection flipScrollDirection(ScrollDirection direction) { ...@@ -55,7 +55,8 @@ ScrollDirection flipScrollDirection(ScrollDirection direction) {
/// select which part of its content to display. As the user scrolls the /// select which part of its content to display. As the user scrolls the
/// viewport, this value changes, which changes the content that is displayed. /// viewport, this value changes, which changes the content that is displayed.
/// ///
/// This object notifies its listeners when [pixels] changes. /// This object is a [Listable] that notifies its listeners when [pixels]
/// changes.
/// ///
/// See also: /// See also:
/// ///
...@@ -154,6 +155,9 @@ abstract class ViewportOffset extends ChangeNotifier { ...@@ -154,6 +155,9 @@ abstract class ViewportOffset extends ChangeNotifier {
/// The direction in which the user is trying to change [pixels], relative to /// The direction in which the user is trying to change [pixels], relative to
/// the viewport's [RenderViewport.axisDirection]. /// the viewport's [RenderViewport.axisDirection].
/// ///
/// If the user is not scrolling, this will return [ScrollDirection.idle] even
/// if there is an [activity] currently animating the position.
///
/// This is used by some slivers to determine how to react to a change in /// This is used by some slivers to determine how to react to a change in
/// scroll offset. For example, [RenderSliverFloatingPersistentHeader] will /// scroll offset. For example, [RenderSliverFloatingPersistentHeader] will
/// only expand a floating app bar when the [userScrollDirection] is in the /// only expand a floating app bar when the [userScrollDirection] is in the
......
...@@ -81,16 +81,15 @@ class ScrollPhysics { ...@@ -81,16 +81,15 @@ class ScrollPhysics {
/// Determines the overscroll by applying the boundary conditions. /// Determines the overscroll by applying the boundary conditions.
/// ///
/// Called by [ScrollPositionWithSingleContext.applyBoundaryConditions], which /// Called by [ScrollPosition.applyBoundaryConditions], which is called by
/// is called by [ScrollPositionWithSingleContext.setPixels] just before the /// [ScrollPosition.setPixels] just before the [ScrollPosition.pixels] value
/// [ScrollPosition.pixels] value is updated, to determine how much of the /// is updated, to determine how much of the offset is to be clamped off and
/// offset is to be clamped off and sent to /// sent to [ScrollPosition.didOverscrollBy].
/// [ScrollPositionWithSingleContext.didOverscrollBy].
/// ///
/// The `value` argument is guaranteed to not equal [pixels] when this is /// The `value` argument is guaranteed to not equal [pixels] when this is
/// called. /// called.
/// ///
/// It is possible for this method to be called when the [position] describes /// It is possible for this method to be called when the `position` describes
/// an already-out-of-bounds position. In that case, the boundary conditions /// an already-out-of-bounds position. In that case, the boundary conditions
/// should usually only prevent a further increase in the extent to which the /// should usually only prevent a further increase in the extent to which the
/// position is out of bounds, allowing a decrease to be applied successfully, /// position is out of bounds, allowing a decrease to be applied successfully,
......
...@@ -20,14 +20,46 @@ import 'scroll_physics.dart'; ...@@ -20,14 +20,46 @@ import 'scroll_physics.dart';
export 'scroll_activity.dart' show ScrollHoldController; export 'scroll_activity.dart' show ScrollHoldController;
// ## Subclassing ScrollPosition /// Determines which portion of the content is visible in a scroll view.
// ///
// * Describe how to impelement [absorb] /// The [pixels] value determines the scroll offset that the scroll view uses to
// - May need to start an idle activity /// select which part of its content to display. As the user scrolls the
// - May need to update the activity's idea of what the delegate is /// viewport, this value changes, which changes the content that is displayed.
// - etc ///
// * Need to call [didUpdateScrollDirection] when changing [userScrollDirection] /// The [ScrollPosition] applies [physics] to scrolling, and stores the
/// [minScrollExtent] and [maxScrollExtent].
///
/// Scrolling is controlled by the current [activity], which is set by
/// [beginActivity]. [ScrollPosition] itself does not start any activities.
/// Instead, concrete subclasses, such as [ScrollPositionWithSingleContext],
/// typically start activities in response to user input or instructions from a
/// [ScrollController].
///
/// This object is a [Listable] that notifies its listeners when [pixels]
/// changes.
///
/// ## Subclassing ScrollPosition
///
/// Over time, a [Scrollable] might have many different [ScrollPosition]
/// objects. For example, if [Scrollable.physics] changes type, [Scrollable]
/// creates a new [ScrollPosition] with the new physics. To transfer state from
/// the old instance to the new instance, subclasses implement [absorb]. See
/// [absorb] for more details.
///
/// Subclasses also need to call [didUpdateScrollDirection] whenever
/// [userScrollDirection] changes values.
///
/// See also:
///
/// * [Scrollable], which uses a [ScrollPosition] to determine which portion of
/// its content to display.
/// * [ScrollController], which can be used with [ListView], [GridView] and
/// other scrollable widgets to control a [ScrollPosition].
/// * [ScrollPositionWithSingleContext], which is the most commonly used
/// concrete subclass of [ScrollPosition].
abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
/// Creates an object that determines which portion of the content is visible
/// in a scroll view.
ScrollPosition({ ScrollPosition({
@required this.physics, @required this.physics,
@required this.context, @required this.context,
...@@ -41,8 +73,19 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { ...@@ -41,8 +73,19 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
absorb(oldPosition); absorb(oldPosition);
} }
/// How the scroll position should respond to user input.
///
/// For example, determines how the widget continues to animate after the
/// user stops dragging the scroll view.
final ScrollPhysics physics; final ScrollPhysics physics;
/// Where the scrolling is taking place.
///
/// Typically implemented by [ScrollableState].
final ScrollContext context; final ScrollContext context;
/// A label that is used in the [toString] output. Intended to aid with
/// identifying animation controller instances in debug output.
final String debugLabel; final String debugLabel;
@override @override
...@@ -72,11 +115,30 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { ...@@ -72,11 +115,30 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
/// Take any current applicable state from the given [ScrollPosition]. /// Take any current applicable state from the given [ScrollPosition].
/// ///
/// This method is called by the constructor if it is given an `oldPosition`. /// This method is called by the constructor if it is given an `oldPosition`.
/// The `other` argument might not have the same [runtimeType] as this object.
/// ///
/// This method can be destructive to the other [ScrollPosition]. The other /// This method can be destructive to the other [ScrollPosition]. The other
/// object must be disposed immediately after this call (in the same call /// object must be disposed immediately after this call (in the same call
/// stack, before microtask resolution, by whomever called this object's /// stack, before microtask resolution, by whomever called this object's
/// constructor). /// constructor).
///
/// If the old [ScrollPosition] object is a different [runtimeType] than this
/// one, the [ScrollActivity.resetActivity] method is invoked on the newly
/// adopted [ScrollActivity].
///
/// ## Overriding
///
/// Overrides of this method must call `super.absorb` after setting any
/// metrics-related or activity-related state, since this method may restart
/// the activity and scroll activities tend to use those metrics when being
/// restarted.
///
/// Overrides of this method might need to start an [IdleScrollActivity] if
/// they are unable to absorb the activity from the other [ScrollPosition].
///
/// Overrides of this method might also need to update the delegates of
/// absorbed scroll activities if they use themselves as a
/// [ScrollActivityDelegate].
@protected @protected
@mustCallSuper @mustCallSuper
void absorb(ScrollPosition other) { void absorb(ScrollPosition other) {
...@@ -206,6 +268,12 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { ...@@ -206,6 +268,12 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
notifyListeners(); notifyListeners();
} }
/// Returns the overscroll by applying the boundary conditions.
///
/// If the given value is in bounds, returns 0.0. Otherwise, returns the
/// amount of value that cannot be applied to [pixels] as a result of the
/// boundary conditions. If the [physics] allow out-of-bounds scrolling, this
/// method always returns 0.0.
@protected @protected
double applyBoundaryConditions(double value) { double applyBoundaryConditions(double value) {
final double result = physics.applyBoundaryConditions(this, value); final double result = physics.applyBoundaryConditions(this, value);
...@@ -256,6 +324,25 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { ...@@ -256,6 +324,25 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
return true; return true;
} }
/// Notifies the activity that the dimensions of the underlying viewport or
/// contents have changed.
///
/// Called after [applyViewportDimension] or [applyContentDimensions] have
/// changed the [minScrollExtent], the [maxScrollExtent], or the
/// [viewportDimension]. When this method is called, it should be called
/// _after_ any corrections are applied to [pixels] using [correctPixels], not
/// before.
///
/// The default implementation informs the [activity] of the new dimensions by
/// calling [ScrollActivityDelegate.applyNewDimensions].
///
/// See also:
///
/// * [applyViewportDimension], which is called when new
/// viewport dimensions are established.
/// * [applyContentDimensions], which is called after new
/// viewport dimensions are established, and also if new content dimensions
/// are established, and which calls [ScrollPosition.applyNewDimensions].
@protected @protected
@mustCallSuper @mustCallSuper
void applyNewDimensions() { void applyNewDimensions() {
...@@ -294,21 +381,73 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { ...@@ -294,21 +381,73 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
/// [State.dispose] method. /// [State.dispose] method.
final ValueNotifier<bool> isScrollingNotifier = new ValueNotifier<bool>(false); final ValueNotifier<bool> isScrollingNotifier = new ValueNotifier<bool>(false);
/// Animates the position from its current value to the given value.
///
/// Any active animation is canceled. If the user is currently scrolling, that
/// action is canceled.
///
/// The returned [Future] will complete when the animation ends, whether it
/// completed successfully or whether it was interrupted prematurely.
///
/// An animation will be interrupted whenever the user attempts to scroll
/// manually, or whenever another activity is started, or whenever the
/// animation reaches the edge of the viewport and attempts to overscroll. (If
/// the [ScrollPosition] does not overscroll but instead allows scrolling
/// beyond the extents, then going beyond the extents will not interrupt the
/// animation.)
///
/// The animation is indifferent to changes to the viewport or content
/// dimensions.
///
/// Once the animation has completed, the scroll position will attempt to
/// begin a ballistic activity in case its value is not stable (for example,
/// if it is scrolled beyond the extents and in that situation the scroll
/// position would normally bounce back).
///
/// The duration must not be zero. To jump to a particular value without an
/// animation, use [jumpTo].
///
/// The animation is typically handled by an [DrivenScrollActivity].
Future<Null> animateTo(double to, { Future<Null> animateTo(double to, {
@required Duration duration, @required Duration duration,
@required Curve curve, @required Curve curve,
}); });
/// Jumps the scroll position from its current value to the given value,
/// without animation, and without checking if the new value is in range.
///
/// Any active animation is canceled. If the user is currently scrolling, that
/// action is canceled.
///
/// If this method changes the scroll position, a sequence of start/update/end
/// scroll notifications will be dispatched. No overscroll notifications can
/// be generated by this method.
///
/// If settle is true then, immediately after the jump, a ballistic activity
/// is started, in case the value was out of range.
void jumpTo(double value); void jumpTo(double value);
/// Deprecated. Use [jumpTo] or a custom [ScrollPosition] instead. /// Deprecated. Use [jumpTo] or a custom [ScrollPosition] instead.
@Deprecated('This will lead to bugs.') @Deprecated('This will lead to bugs.')
void jumpToWithoutSettling(double value); void jumpToWithoutSettling(double value);
/// Stop the current activity and start a [HoldScrollActivity].
ScrollHoldController hold(VoidCallback holdCancelCallback); ScrollHoldController hold(VoidCallback holdCancelCallback);
/// Start a drag activity corresponding to the given [DragStartDetails].
///
/// The `onDragCanceled` argument will be invoked if the drag is ended
/// prematurely (e.g. from another activity taking over). See
/// [ScrollDragController.onDragCanceled] for details.
Drag drag(DragStartDetails details, VoidCallback dragCancelCallback); Drag drag(DragStartDetails details, VoidCallback dragCancelCallback);
/// The currently operative [ScrollActivity].
///
/// If the scroll position is not performing any more specific activity, the
/// activity will be an [IdleScrollActivity]. To determine whether the scroll
/// position is idle, check the [isScrollingNotifier].
///
/// Call [beginActivity] to change the current activity.
@protected @protected
ScrollActivity get activity => _activity; ScrollActivity get activity => _activity;
ScrollActivity _activity; ScrollActivity _activity;
...@@ -335,9 +474,9 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { ...@@ -335,9 +474,9 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
wasScrolling = false; wasScrolling = false;
} }
_activity = newActivity; _activity = newActivity;
isScrollingNotifier.value = activity.isScrolling;
if (oldIgnorePointer != activity.shouldIgnorePointer) if (oldIgnorePointer != activity.shouldIgnorePointer)
context.setIgnorePointer(activity.shouldIgnorePointer); context.setIgnorePointer(activity.shouldIgnorePointer);
isScrollingNotifier.value = activity.isScrolling;
if (!wasScrolling && _activity.isScrolling) if (!wasScrolling && _activity.isScrolling)
didStartScroll(); didStartScroll();
} }
......
...@@ -76,25 +76,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc ...@@ -76,25 +76,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
correctPixels(pixels + correction); correctPixels(pixels + correction);
} }
/// Take any current applicable state from the given [ScrollPosition].
///
/// This method is called by the constructor, before calling [ensureActivity],
/// if it is given an `oldPosition`. It adopts the old position's current
/// [activity] as its own.
///
/// This method is destructive to the other [ScrollPosition]. The other
/// object must be disposed immediately after this call (in the same call
/// stack, before microtask resolution, by whomever called this object's
/// constructor).
///
/// If the old [ScrollPosition] object is a different [runtimeType] than this
/// one, the [ScrollActivity.resetActivity] method is invoked on the newly
/// adopted [ScrollActivity].
///
/// When overriding this method, call `super.absorb` after setting any
/// metrics-related or activity-related state, since this method may restart
/// the activity and scroll activities tend to use those metrics when being
/// restarted.
@override @override
void absorb(ScrollPosition other) { void absorb(ScrollPosition other) {
super.absorb(other); super.absorb(other);
...@@ -113,28 +94,12 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc ...@@ -113,28 +94,12 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
} }
} }
/// Notifies the activity that the dimensions of the underlying viewport or
/// contents have changed.
///
/// When this method is called, it should be called _after_ any corrections
/// are applied to [pixels] using [correctPixels], not before.
///
/// See also:
///
/// * [ScrollPosition.applyViewportDimension], which is called when new
/// viewport dimensions are established.
/// * [ScrollPosition.applyContentDimensions], which is called after new
/// viewport dimensions are established, and also if new content dimensions
/// are established, and which calls [ScrollPosition.applyNewDimensions].
@override @override
void applyNewDimensions() { void applyNewDimensions() {
super.applyNewDimensions(); super.applyNewDimensions();
context.setCanDrag(physics.shouldAcceptUserOffset(this)); context.setCanDrag(physics.shouldAcceptUserOffset(this));
} }
// SCROLL ACTIVITIES
@override @override
void beginActivity(ScrollActivity newActivity) { void beginActivity(ScrollActivity newActivity) {
if (newActivity == null) if (newActivity == null)
...@@ -153,8 +118,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc ...@@ -153,8 +118,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta)); setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta));
} }
/// End the current [ScrollActivity], replacing it with an
/// [IdleScrollActivity].
@override @override
void goIdle() { void goIdle() {
beginActivity(new IdleScrollActivity(this)); beginActivity(new IdleScrollActivity(this));
...@@ -180,10 +143,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc ...@@ -180,10 +143,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
} }
} }
/// The direction that the user most recently began scrolling in.
///
/// If the user is not scrolling, this will return [ScrollDirection.idle] even
/// if there is an [activity] currently animating the position.
@override @override
ScrollDirection get userScrollDirection => _userScrollDirection; ScrollDirection get userScrollDirection => _userScrollDirection;
ScrollDirection _userScrollDirection = ScrollDirection.idle; ScrollDirection _userScrollDirection = ScrollDirection.idle;
...@@ -200,35 +159,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc ...@@ -200,35 +159,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
didUpdateScrollDirection(value); didUpdateScrollDirection(value);
} }
// FEATURES USED BY SCROLL CONTROLLERS
/// Animates the position from its current value to the given value.
///
/// Any active animation is canceled. If the user is currently scrolling, that
/// action is canceled.
///
/// The returned [Future] will complete when the animation ends, whether it
/// completed successfully or whether it was interrupted prematurely.
///
/// An animation will be interrupted whenever the user attempts to scroll
/// manually, or whenever another activity is started, or whenever the
/// animation reaches the edge of the viewport and attempts to overscroll. (If
/// the [ScrollPosition] does not overscroll but instead allows scrolling
/// beyond the extents, then going beyond the extents will not interrupt the
/// animation.)
///
/// The animation is indifferent to changes to the viewport or content
/// dimensions.
///
/// Once the animation has completed, the scroll position will attempt to
/// begin a ballistic activity in case its value is not stable (for example,
/// if it is scrolled beyond the extents and in that situation the scroll
/// position would normally bounce back).
///
/// The duration must not be zero. To jump to a particular value without an
/// animation, use [jumpTo].
///
/// The animation is handled by an [DrivenScrollActivity].
@override @override
Future<Null> animateTo(double to, { Future<Null> animateTo(double to, {
@required Duration duration, @required Duration duration,
...@@ -246,18 +176,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc ...@@ -246,18 +176,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
return activity.done; return activity.done;
} }
/// Jumps the scroll position from its current value to the given value,
/// without animation, and without checking if the new value is in range.
///
/// Any active animation is canceled. If the user is currently scrolling, that
/// action is canceled.
///
/// If this method changes the scroll position, a sequence of start/update/end
/// scroll notifications will be dispatched. No overscroll notifications can
/// be generated by this method.
///
/// If settle is true then, immediately after the jump, a ballistic activity
/// is started, in case the value was out of range.
@override @override
void jumpTo(double value) { void jumpTo(double value) {
goIdle(); goIdle();
...@@ -272,7 +190,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc ...@@ -272,7 +190,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
goBallistic(0.0); goBallistic(0.0);
} }
/// Deprecated. Use [jumpTo] or a custom [ScrollPosition] instead.
@Deprecated('This will lead to bugs.') @Deprecated('This will lead to bugs.')
@override @override
void jumpToWithoutSettling(double value) { void jumpToWithoutSettling(double value) {
...@@ -287,8 +204,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc ...@@ -287,8 +204,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
} }
} }
ScrollDragController _currentDrag;
@override @override
ScrollHoldController hold(VoidCallback holdCancelCallback) { ScrollHoldController hold(VoidCallback holdCancelCallback) {
final HoldScrollActivity activity = new HoldScrollActivity( final HoldScrollActivity activity = new HoldScrollActivity(
...@@ -299,11 +214,8 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc ...@@ -299,11 +214,8 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
return activity; return activity;
} }
/// Start a drag activity corresponding to the given [DragStartDetails]. ScrollDragController _currentDrag;
///
/// The `onDragCanceled` argument will be invoked if the drag is ended
/// prematurely (e.g. from another activity taking over). See
/// [ScrollDragController.onDragCanceled] for details.
@override @override
Drag drag(DragStartDetails details, VoidCallback onDragCanceled) { Drag drag(DragStartDetails details, VoidCallback onDragCanceled) {
final ScrollDragController drag = new ScrollDragController( final ScrollDragController drag = new ScrollDragController(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment