diff --git a/packages/flutter/lib/src/rendering/viewport_offset.dart b/packages/flutter/lib/src/rendering/viewport_offset.dart index 5a7aee7fb9..c7446e055c 100644 --- a/packages/flutter/lib/src/rendering/viewport_offset.dart +++ b/packages/flutter/lib/src/rendering/viewport_offset.dart @@ -55,7 +55,8 @@ ScrollDirection flipScrollDirection(ScrollDirection direction) { /// select which part of its content to display. As the user scrolls the /// 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: /// @@ -154,6 +155,9 @@ abstract class ViewportOffset extends ChangeNotifier { /// The direction in which the user is trying to change [pixels], relative to /// 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 /// scroll offset. For example, [RenderSliverFloatingPersistentHeader] will /// only expand a floating app bar when the [userScrollDirection] is in the diff --git a/packages/flutter/lib/src/widgets/scroll_physics.dart b/packages/flutter/lib/src/widgets/scroll_physics.dart index 3cd0738870..e015a332d4 100644 --- a/packages/flutter/lib/src/widgets/scroll_physics.dart +++ b/packages/flutter/lib/src/widgets/scroll_physics.dart @@ -81,16 +81,15 @@ class ScrollPhysics { /// Determines the overscroll by applying the boundary conditions. /// - /// Called by [ScrollPositionWithSingleContext.applyBoundaryConditions], which - /// is called by [ScrollPositionWithSingleContext.setPixels] just before the - /// [ScrollPosition.pixels] value is updated, to determine how much of the - /// offset is to be clamped off and sent to - /// [ScrollPositionWithSingleContext.didOverscrollBy]. + /// Called by [ScrollPosition.applyBoundaryConditions], which is called by + /// [ScrollPosition.setPixels] just before the [ScrollPosition.pixels] value + /// is updated, to determine how much of the offset is to be clamped off and + /// sent to [ScrollPosition.didOverscrollBy]. /// /// The `value` argument is guaranteed to not equal [pixels] when this is /// 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 /// 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, diff --git a/packages/flutter/lib/src/widgets/scroll_position.dart b/packages/flutter/lib/src/widgets/scroll_position.dart index bd7352cbca..c963ceadf9 100644 --- a/packages/flutter/lib/src/widgets/scroll_position.dart +++ b/packages/flutter/lib/src/widgets/scroll_position.dart @@ -20,14 +20,46 @@ import 'scroll_physics.dart'; export 'scroll_activity.dart' show ScrollHoldController; -// ## Subclassing ScrollPosition -// -// * Describe how to impelement [absorb] -// - May need to start an idle activity -// - May need to update the activity's idea of what the delegate is -// - etc -// * Need to call [didUpdateScrollDirection] when changing [userScrollDirection] +/// Determines which portion of the content is visible in a scroll view. +/// +/// The [pixels] value determines the scroll offset that the scroll view uses to +/// select which part of its content to display. As the user scrolls the +/// viewport, this value changes, which changes the content that is displayed. +/// +/// 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 { + /// Creates an object that determines which portion of the content is visible + /// in a scroll view. ScrollPosition({ @required this.physics, @required this.context, @@ -41,8 +73,19 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { 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; + + /// Where the scrolling is taking place. + /// + /// Typically implemented by [ScrollableState]. 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; @override @@ -72,11 +115,30 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { /// Take any current applicable state from the given [ScrollPosition]. /// /// 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 /// 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]. + /// + /// ## 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 @mustCallSuper void absorb(ScrollPosition other) { @@ -206,6 +268,12 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { 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 double applyBoundaryConditions(double value) { final double result = physics.applyBoundaryConditions(this, value); @@ -256,6 +324,25 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { 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 @mustCallSuper void applyNewDimensions() { @@ -294,21 +381,73 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { /// [State.dispose] method. final ValueNotifier isScrollingNotifier = new ValueNotifier(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 animateTo(double to, { @required Duration duration, @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); /// Deprecated. Use [jumpTo] or a custom [ScrollPosition] instead. @Deprecated('This will lead to bugs.') void jumpToWithoutSettling(double value); + /// Stop the current activity and start a [HoldScrollActivity]. 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); + /// 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 ScrollActivity get activity => _activity; ScrollActivity _activity; @@ -335,9 +474,9 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { wasScrolling = false; } _activity = newActivity; - isScrollingNotifier.value = activity.isScrolling; if (oldIgnorePointer != activity.shouldIgnorePointer) context.setIgnorePointer(activity.shouldIgnorePointer); + isScrollingNotifier.value = activity.isScrolling; if (!wasScrolling && _activity.isScrolling) didStartScroll(); } diff --git a/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart b/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart index 56883201a9..287f739fdd 100644 --- a/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart +++ b/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart @@ -76,25 +76,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc 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 void absorb(ScrollPosition other) { super.absorb(other); @@ -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 void applyNewDimensions() { super.applyNewDimensions(); context.setCanDrag(physics.shouldAcceptUserOffset(this)); } - - // SCROLL ACTIVITIES - @override void beginActivity(ScrollActivity newActivity) { if (newActivity == null) @@ -153,8 +118,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta)); } - /// End the current [ScrollActivity], replacing it with an - /// [IdleScrollActivity]. @override void goIdle() { beginActivity(new IdleScrollActivity(this)); @@ -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 ScrollDirection get userScrollDirection => _userScrollDirection; ScrollDirection _userScrollDirection = ScrollDirection.idle; @@ -200,35 +159,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc 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 Future animateTo(double to, { @required Duration duration, @@ -246,18 +176,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc 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 void jumpTo(double value) { goIdle(); @@ -272,7 +190,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc goBallistic(0.0); } - /// Deprecated. Use [jumpTo] or a custom [ScrollPosition] instead. @Deprecated('This will lead to bugs.') @override void jumpToWithoutSettling(double value) { @@ -287,8 +204,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc } } - ScrollDragController _currentDrag; - @override ScrollHoldController hold(VoidCallback holdCancelCallback) { final HoldScrollActivity activity = new HoldScrollActivity( @@ -299,11 +214,8 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc return activity; } - /// 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. + ScrollDragController _currentDrag; + @override Drag drag(DragStartDetails details, VoidCallback onDragCanceled) { final ScrollDragController drag = new ScrollDragController(