Commit 4bac2596 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Shift more code to ScrollPosition (#9637)

Previously, ScrollPosition did not know about ScrollActivities. However, all
the concrete subclasses of ScrollPosition that we know about need to use
ScrollActivities, so they ended up with a bunch of delegate boilerplate code.

This patch teaches ScrollPosition about ScrollActivities but doesn't have any
opinion about how to start or interact with those activities.

This patch is more refactoring to prepare for nested and linked scrolling.
parent 36f7a266
......@@ -15,6 +15,7 @@ import 'scroll_position_with_single_context.dart';
class ScrollController extends ChangeNotifier {
ScrollController({
this.initialScrollOffset: 0.0,
this.debugLabel,
}) {
assert(initialScrollOffset != null);
}
......@@ -25,6 +26,8 @@ class ScrollController extends ChangeNotifier {
/// controller will have their offset initialized to this value.
final double initialScrollOffset;
final String debugLabel;
/// The currently attached positions.
///
/// This should not be mutated directly. [ScrollPosition] objects can be added
......@@ -152,6 +155,7 @@ class ScrollController extends ChangeNotifier {
context: context,
initialPixels: initialScrollOffset,
oldPosition: oldPosition,
debugLabel: debugLabel,
);
}
......@@ -164,6 +168,8 @@ class ScrollController extends ChangeNotifier {
@mustCallSuper
void debugFillDescription(List<String> description) {
if (debugLabel != null)
description.add(debugLabel);
if (initialScrollOffset != 0.0)
description.add('initialScrollOffset: ${initialScrollOffset.toStringAsFixed(1)}, ');
if (_positions.isEmpty) {
......
......@@ -12,19 +12,36 @@ import 'package:flutter/scheduler.dart';
import 'basic.dart';
import 'framework.dart';
import 'gesture_detector.dart';
import 'scroll_activity.dart';
import 'scroll_context.dart';
import 'scroll_metrics.dart';
import 'scroll_notification.dart';
import 'scroll_physics.dart';
// ## 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]
abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
ScrollPosition({
@required this.physics,
@required this.context,
ScrollPosition oldPosition,
this.debugLabel,
}) {
assert(physics != null);
assert(context != null);
assert(context.vsync != null);
if (oldPosition != null)
absorb(oldPosition);
}
final ScrollPhysics physics;
final ScrollContext context;
final String debugLabel;
@override
double get minScrollExtent => _minScrollExtent;
......@@ -62,11 +79,21 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
@mustCallSuper
void absorb(ScrollPosition other) {
assert(other != null);
assert(other.context == context);
assert(_pixels == null);
_minScrollExtent = other.minScrollExtent;
_maxScrollExtent = other.maxScrollExtent;
_pixels = other._pixels;
_viewportDimension = other.viewportDimension;
assert(activity == null);
assert(other.activity != null);
_activity = other.activity;
other._activity = null;
if (other.runtimeType != runtimeType)
activity.resetActivity();
context.setIgnorePointer(activity.shouldIgnorePointer);
isScrollingNotifier.value = activity.isScrolling;
}
/// Update the scroll position ([pixels]) to a given pixel value.
......@@ -228,7 +255,11 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
}
@protected
void applyNewDimensions();
@mustCallSuper
void applyNewDimensions() {
assert(pixels != null);
activity.applyNewDimensions();
}
/// Animates the position such that the given object is as visible as possible
/// by just scrolling this position.
......@@ -277,13 +308,83 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
Drag drag(DragStartDetails details, VoidCallback dragCancelCallback);
@protected
void didUpdateScrollPositionBy(double delta);
ScrollActivity get activity => _activity;
ScrollActivity _activity;
@protected
void didOverscrollBy(double value);
/// Change the current [activity], disposing of the old one and
/// sending scroll notifications as necessary.
///
/// If the argument is null, this method has no effect. This is convenient for
/// cases where the new activity is obtained from another method, and that
/// method might return null, since it means the caller does not have to
/// explictly null-check the argument.
void beginActivity(ScrollActivity newActivity) {
if (newActivity == null)
return;
bool wasScrolling, oldIgnorePointer;
if (_activity != null) {
oldIgnorePointer = _activity.shouldIgnorePointer;
wasScrolling = _activity.isScrolling;
if (wasScrolling && !newActivity.isScrolling)
didEndScroll();
_activity.dispose();
} else {
oldIgnorePointer = false;
wasScrolling = false;
}
_activity = newActivity;
isScrollingNotifier.value = activity.isScrolling;
if (oldIgnorePointer != activity.shouldIgnorePointer)
context.setIgnorePointer(activity.shouldIgnorePointer);
if (!wasScrolling && _activity.isScrolling)
didStartScroll();
}
// NOTIFICATION DISPATCH
/// Called by [beginActivity] to report when an activity has started.
void didStartScroll() {
activity.dispatchScrollStartNotification(cloneMetrics(), context.notificationContext);
}
/// Called by [setPixels] to report a change to the [pixels] position.
void didUpdateScrollPositionBy(double delta) {
activity.dispatchScrollUpdateNotification(cloneMetrics(), context.notificationContext, delta);
}
/// Called by [beginActivity] to report when an activity has ended.
void didEndScroll() {
activity.dispatchScrollEndNotification(cloneMetrics(), context.notificationContext);
}
/// Called by [setPixels] to report overscroll when an attempt is made to
/// change the [pixels] position. Overscroll is the amount of change that was
/// not applied to the [pixels] value.
void didOverscrollBy(double value) {
assert(activity.isScrolling);
activity.dispatchOverscrollNotification(cloneMetrics(), context.notificationContext, value);
}
/// Dispatches a notification that the [userScrollDirection] has changed.
///
/// Subclasses should call this function when they change [userScrollDirection].
void didUpdateScrollDirection(ScrollDirection direction) {
new UserScrollNotification(metrics: cloneMetrics(), context: context.notificationContext, direction: direction).dispatch(context.notificationContext);
}
@override
void dispose() {
assert(pixels != null);
activity?.dispose(); // it will be null if it got absorbed by another ScrollPosition
_activity = null;
super.dispose();
}
@override
void debugFillDescription(List<String> description) {
if (debugLabel != null)
description.add(debugLabel);
super.debugFillDescription(description);
description.add('range: ${minScrollExtent?.toStringAsFixed(1)}..${maxScrollExtent?.toStringAsFixed(1)}');
description.add('viewport: ${viewportDimension?.toStringAsFixed(1)}');
......
......@@ -48,15 +48,13 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
/// implementation of that method.
ScrollPositionWithSingleContext({
@required ScrollPhysics physics,
@required this.context,
@required ScrollContext context,
double initialPixels: 0.0,
ScrollPosition oldPosition,
}) : super(physics: physics, oldPosition: oldPosition) {
String debugLabel,
}) : super(physics: physics, context: context, oldPosition: oldPosition, debugLabel: debugLabel) {
// If oldPosition is not null, the superclass will first call absorb(),
// which may set _pixels and _activity.
assert(physics != null);
assert(context != null);
assert(context.vsync != null);
if (pixels == null && initialPixels != null)
correctPixels(initialPixels);
if (activity == null)
......@@ -64,8 +62,6 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
assert(activity != null);
}
final ScrollContext context;
@override
AxisDirection get axisDirection => context.axisDirection;
......@@ -100,35 +96,21 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
/// the activity and scroll activities tend to use those metrics when being
/// restarted.
@override
void absorb(ScrollPosition otherPosition) {
assert(otherPosition != null);
if (otherPosition is! ScrollPositionWithSingleContext) {
super.absorb(otherPosition);
void absorb(ScrollPosition other) {
super.absorb(other);
if (other is! ScrollPositionWithSingleContext) {
goIdle();
return;
}
final ScrollPositionWithSingleContext other = otherPosition;
assert(other != this);
assert(other.context == context);
super.absorb(other);
_userScrollDirection = other._userScrollDirection;
activity.updateDelegate(this);
final ScrollPositionWithSingleContext typedOther = other;
_userScrollDirection = typedOther._userScrollDirection;
assert(_currentDrag == null);
if (other._currentDrag != null) {
other._currentDrag.updateDelegate(this);
_currentDrag = other._currentDrag;
other._currentDrag = null;
if (typedOther._currentDrag != null) {
_currentDrag = typedOther._currentDrag;
_currentDrag.updateDelegate(this);
typedOther._currentDrag = null;
}
assert(activity == null);
assert(other.activity != null);
other.activity.updateDelegate(this);
_activity = other.activity;
other._activity = null;
if (other.runtimeType != runtimeType)
activity.resetActivity();
context.setIgnorePointer(shouldIgnorePointer);
isScrollingNotifier.value = _activity.isScrolling;
}
/// Notifies the activity that the dimensions of the underlying viewport or
......@@ -144,56 +126,25 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
/// * [ScrollPosition.applyContentDimensions], which is called after new
/// viewport dimensions are established, and also if new content dimensions
/// are established, and which calls [ScrollPosition.applyNewDimensions].
@mustCallSuper
@override
void applyNewDimensions() {
assert(pixels != null);
activity.applyNewDimensions();
super.applyNewDimensions();
context.setCanDrag(physics.shouldAcceptUserOffset(this));
}
// SCROLL ACTIVITIES
@protected
ScrollActivity get activity => _activity;
ScrollActivity _activity;
@protected
bool get shouldIgnorePointer => activity?.shouldIgnorePointer;
/// Change the current [activity], disposing of the old one and
/// sending scroll notifications as necessary.
///
/// If the argument is null, this method has no effect. This is convenient for
/// cases where the new activity is obtained from another method, and that
/// method might return null, since it means the caller does not have to
/// explictly null-check the argument.
@override
void beginActivity(ScrollActivity newActivity) {
if (newActivity == null)
return;
assert(newActivity.delegate == this);
bool wasScrolling, oldIgnorePointer;
if (_activity != null) {
oldIgnorePointer = _activity.shouldIgnorePointer;
wasScrolling = _activity.isScrolling;
if (wasScrolling && !newActivity.isScrolling)
_didEndScroll();
_activity.dispose();
} else {
oldIgnorePointer = false;
wasScrolling = false;
}
super.beginActivity(newActivity);
_currentDrag?.dispose();
_currentDrag = null;
_activity = newActivity;
isScrollingNotifier.value = activity.isScrolling;
if (!activity.isScrolling)
updateUserScrollDirection(ScrollDirection.idle);
if (oldIgnorePointer != shouldIgnorePointer)
context.setIgnorePointer(shouldIgnorePointer);
if (!wasScrolling && _activity.isScrolling)
_didStartScroll();
}
@override
......@@ -246,7 +197,7 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
if (userScrollDirection == value)
return;
_userScrollDirection = value;
_didUpdateScrollDirection(value);
didUpdateScrollDirection(value);
}
// FEATURES USED BY SCROLL CONTROLLERS
......@@ -314,9 +265,9 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
final double oldPixels = pixels;
forcePixels(value);
notifyListeners();
_didStartScroll();
didStartScroll();
didUpdateScrollPositionBy(pixels - oldPixels);
_didEndScroll();
didEndScroll();
}
goBallistic(0.0);
}
......@@ -330,9 +281,9 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
final double oldPixels = pixels;
forcePixels(value);
notifyListeners();
_didStartScroll();
didStartScroll();
didUpdateScrollPositionBy(pixels - oldPixels);
_didEndScroll();
didEndScroll();
}
}
......@@ -362,48 +313,11 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc
@override
void dispose() {
assert(pixels != null);
_currentDrag?.dispose();
_currentDrag = null;
activity?.dispose(); // it will be null if it got absorbed by another ScrollPosition
_activity = null;
super.dispose();
}
// NOTIFICATION DISPATCH
/// Called by [beginActivity] to report when an activity has started.
void _didStartScroll() {
activity.dispatchScrollStartNotification(cloneMetrics(), context.notificationContext);
}
/// Called by [setPixels] to report a change to the [pixels] position.
@override
void didUpdateScrollPositionBy(double delta) {
activity.dispatchScrollUpdateNotification(cloneMetrics(), context.notificationContext, delta);
}
/// Called by [beginActivity] to report when an activity has ended.
void _didEndScroll() {
activity.dispatchScrollEndNotification(cloneMetrics(), context.notificationContext);
}
/// Called by [setPixels] to report overscroll when an attempt is made to
/// change the [pixels] position. Overscroll is the amount of change that was
/// not applied to the [pixels] value.
@override
void didOverscrollBy(double value) {
assert(activity.isScrolling);
activity.dispatchOverscrollNotification(cloneMetrics(), context.notificationContext, value);
}
/// Called by [updateUserScrollDirection] to report that the
/// [userScrollDirection] has changed.
void _didUpdateScrollDirection(ScrollDirection direction) {
new UserScrollNotification(metrics: cloneMetrics(), context: context.notificationContext, direction: direction).dispatch(context.notificationContext);
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
......
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