Commit 2656006c authored by Hans Muller's avatar Hans Muller Committed by GitHub

OverscrollIndicator tracks horizontal drag input, etc (#5183)

parent 64fa825b
...@@ -56,7 +56,8 @@ class DragUpdateDetails { ...@@ -56,7 +56,8 @@ class DragUpdateDetails {
/// coordinates of [delta] and the other coordinate must be zero. /// coordinates of [delta] and the other coordinate must be zero.
DragUpdateDetails({ DragUpdateDetails({
this.delta: Offset.zero, this.delta: Offset.zero,
this.primaryDelta this.primaryDelta,
this.globalPosition
}) { }) {
assert(primaryDelta == null assert(primaryDelta == null
|| (primaryDelta == delta.dx && delta.dy == 0.0) || (primaryDelta == delta.dx && delta.dy == 0.0)
...@@ -79,6 +80,9 @@ class DragUpdateDetails { ...@@ -79,6 +80,9 @@ class DragUpdateDetails {
/// respectively). Otherwise, if the [GestureDragUpdateCallback] is for a /// respectively). Otherwise, if the [GestureDragUpdateCallback] is for a
/// two-dimensional drag (e.g., a pan), then this value is null. /// two-dimensional drag (e.g., a pan), then this value is null.
final double primaryDelta; final double primaryDelta;
/// The pointer's global position.
final Point globalPosition;
} }
/// Signature for when a pointer that is in contact with the screen and moving /// Signature for when a pointer that is in contact with the screen and moving
...@@ -184,7 +188,8 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -184,7 +188,8 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
if (onUpdate != null) { if (onUpdate != null) {
onUpdate(new DragUpdateDetails( onUpdate(new DragUpdateDetails(
delta: _getDeltaForDetails(delta), delta: _getDeltaForDetails(delta),
primaryDelta: _getPrimaryDeltaForDetails(delta) primaryDelta: _getPrimaryDeltaForDetails(delta),
globalPosition: event.position
)); ));
} }
} else { } else {
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async' show Timer; import 'dart:async' show Timer;
import 'dart:math' as math;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -12,67 +13,66 @@ const double _kMinIndicatorExtent = 0.0; ...@@ -12,67 +13,66 @@ const double _kMinIndicatorExtent = 0.0;
const double _kMaxIndicatorExtent = 64.0; const double _kMaxIndicatorExtent = 64.0;
const double _kMinIndicatorOpacity = 0.0; const double _kMinIndicatorOpacity = 0.0;
const double _kMaxIndicatorOpacity = 0.25; const double _kMaxIndicatorOpacity = 0.25;
const Duration _kIndicatorHideDuration = const Duration(milliseconds: 200);
const Duration _kIndicatorTimeoutDuration = const Duration(milliseconds: 500);
final Tween<double> _kIndicatorOpacity = new Tween<double>(begin: 0.0, end: 0.3); final Tween<double> _kIndicatorOpacity = new Tween<double>(begin: 0.0, end: 0.3);
// If an overscroll gesture lasts longer than this the hide timer will
// cause the indicator to fade-out.
const Duration _kTimeoutDuration = const Duration(milliseconds: 500);
// Fade-out duration if the fade-out was triggered by the timer.
const Duration _kTimeoutHideDuration = const Duration(milliseconds: 2000);
// Fade-out duration if the fade-out was triggered by an input gesture.
const Duration _kNormalHideDuration = const Duration(milliseconds: 600);
class _Painter extends CustomPainter { class _Painter extends CustomPainter {
_Painter({ _Painter({
this.scrollDirection, this.scrollDirection,
this.extent, // Indicator width or height, per scrollDirection. this.extent, // Indicator width or height, per scrollDirection.
this.dragPosition,
this.isLeading, // Similarly true if the indicator appears at the top/left. this.isLeading, // Similarly true if the indicator appears at the top/left.
this.color this.color
}); });
// See EdgeEffect setSize() in https://github.com/android
static final double _kSizeToRadius = 0.75 / math.sin(math.PI / 6.0);
final Axis scrollDirection; final Axis scrollDirection;
final double extent; final double extent;
final bool isLeading; final bool isLeading;
final Color color; final Color color;
final Point dragPosition;
void paintIndicator(Canvas canvas, Size size) { void paintIndicator(Canvas canvas, Size size) {
final double rectBias = extent / 2.0; final Paint paint = new Paint()..color = color;
final double arcBias = extent * 0.66; final double width = size.width;
final double height = size.height;
final Path path = new Path();
switch(scrollDirection) { switch(scrollDirection) {
case Axis.vertical: case Axis.vertical:
final double width = size.width; final double radius = width * _kSizeToRadius;
if (isLeading) { final double centerX = width / 2.0;
path.moveTo(0.0, 0.0); final double centerY = isLeading ? extent - radius : height - extent + radius;
path.relativeLineTo(width, 0.0); final double eventX = dragPosition?.x ?? 0.0;
path.relativeLineTo(0.0, rectBias); final double biasX = (0.5 - (1.0 - eventX / width)) * centerX;
path.relativeCubicTo(width * -0.25, arcBias, width * -0.75, arcBias, -width, 0.0); canvas.drawCircle(new Point(centerX + biasX, centerY), radius, paint);
} else {
path.moveTo(0.0, size.height);
path.relativeLineTo(width, 0.0);
path.relativeLineTo(0.0, -rectBias);
path.relativeCubicTo(width * -0.25, -arcBias, width * -0.75, -arcBias, -width, 0.0);
}
break; break;
case Axis.horizontal: case Axis.horizontal:
final double height = size.height; final double radius = height * _kSizeToRadius;
if (isLeading) { final double centerX = isLeading ? extent - radius : width - extent + radius;
path.moveTo(0.0, 0.0); final double centerY = height / 2.0;
path.relativeLineTo(0.0, height); final double eventY = dragPosition?.y ?? 0.0;
path.relativeLineTo(rectBias, 0.0); final double biasY = (0.5 - (1.0 - eventY / height)) * centerY;
path.relativeCubicTo(arcBias, height * -0.25, arcBias, height * -0.75, 0.0, -height); canvas.drawCircle(new Point(centerX, centerY + biasY), radius, paint);
} else {
path.moveTo(size.width, 0.0);
path.relativeLineTo(0.0, height);
path.relativeLineTo(-rectBias, 0.0);
path.relativeCubicTo(-arcBias, height * -0.25, -arcBias, height * -0.75, 0.0, -height);
}
break; break;
} }
path.close();
final Paint paint = new Paint()..color = color;
canvas.drawPath(path, paint);
} }
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
if (color.alpha == 0) if (color.alpha == 0 || size.isEmpty)
return; return;
paintIndicator(canvas, size); paintIndicator(canvas, size);
} }
...@@ -120,19 +120,23 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> { ...@@ -120,19 +120,23 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> {
final AnimationController _extentAnimation = new AnimationController( final AnimationController _extentAnimation = new AnimationController(
lowerBound: _kMinIndicatorExtent, lowerBound: _kMinIndicatorExtent,
upperBound: _kMaxIndicatorExtent, upperBound: _kMaxIndicatorExtent,
duration: _kIndicatorHideDuration duration: _kNormalHideDuration
); );
bool _scrollUnderway = false;
Timer _hideTimer; Timer _hideTimer;
Axis _scrollDirection; Axis _scrollDirection;
double _scrollOffset; double _scrollOffset;
double _minScrollOffset; double _minScrollOffset;
double _maxScrollOffset; double _maxScrollOffset;
Point _dragPosition;
void _hide() { void _hide([Duration duration=_kTimeoutHideDuration]) {
_scrollUnderway = false;
_hideTimer?.cancel(); _hideTimer?.cancel();
_hideTimer = null; _hideTimer = null;
if (!_extentAnimation.isAnimating) { if (!_extentAnimation.isAnimating) {
_extentAnimation.duration = duration;
_extentAnimation.reverse(); _extentAnimation.reverse();
} }
} }
...@@ -148,18 +152,24 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> { ...@@ -148,18 +152,24 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> {
} }
void _onScrollStarted(ScrollableState scrollable) { void _onScrollStarted(ScrollableState scrollable) {
assert(_scrollUnderway == false);
_scrollUnderway = true;
_updateState(scrollable); _updateState(scrollable);
} }
void _onScrollUpdated(ScrollableState scrollable) { void _onScrollUpdated(ScrollableState scrollable, DragUpdateDetails details) {
if (!_scrollUnderway) // The hide timer has run.
return;
final double value = scrollable.scrollOffset; final double value = scrollable.scrollOffset;
if (_isOverscroll(value)) { if (_isOverscroll(value)) {
_refreshHideTimer(); _refreshHideTimer();
// Hide the indicator as soon as user starts scrolling in the reverse direction of overscroll. // Hide the indicator as soon as user starts scrolling in the reverse direction of overscroll.
if (_isReverseScroll(value)) { if (_isReverseScroll(value)) {
_hide(); _hide(_kNormalHideDuration);
} else { } else {
// Changing the animation's value causes an implicit setState(). // Changing the animation's value causes an implicit setState().
_dragPosition = details?.globalPosition ?? Point.origin;
_extentAnimation.value = value < _minScrollOffset ? _minScrollOffset - value : value - _maxScrollOffset; _extentAnimation.value = value < _minScrollOffset ? _minScrollOffset - value : value - _maxScrollOffset;
} }
} }
...@@ -167,13 +177,16 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> { ...@@ -167,13 +177,16 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> {
} }
void _onScrollEnded(ScrollableState scrollable) { void _onScrollEnded(ScrollableState scrollable) {
if (!_scrollUnderway) // The hide timer has run.
return;
_updateState(scrollable); _updateState(scrollable);
_hide(); _hide(_kNormalHideDuration);
} }
void _refreshHideTimer() { void _refreshHideTimer() {
_hideTimer?.cancel(); _hideTimer?.cancel();
_hideTimer = new Timer(_kIndicatorTimeoutDuration, _hide); _hideTimer = new Timer(_kTimeoutDuration, _hide);
} }
bool _isOverscroll(double scrollOffset) { bool _isOverscroll(double scrollOffset) {
...@@ -183,7 +196,7 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> { ...@@ -183,7 +196,7 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> {
bool _isReverseScroll(double scrollOffset) { bool _isReverseScroll(double scrollOffset) {
final double delta = _scrollOffset - scrollOffset; final double delta = _scrollOffset - scrollOffset;
return scrollOffset < _minScrollOffset ? delta < 0 : delta > 0; return scrollOffset < _minScrollOffset ? delta < 0.0 : delta > 0.0;
} }
bool _handleScrollNotification(ScrollNotification notification) { bool _handleScrollNotification(ScrollNotification notification) {
...@@ -200,7 +213,7 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> { ...@@ -200,7 +213,7 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> {
_onScrollStarted(scrollable); _onScrollStarted(scrollable);
break; break;
case ScrollNotificationKind.updated: case ScrollNotificationKind.updated:
_onScrollUpdated(scrollable); _onScrollUpdated(scrollable, notification.dragUpdateDetails);
break; break;
case ScrollNotificationKind.ended: case ScrollNotificationKind.ended:
_onScrollEnded(scrollable); _onScrollEnded(scrollable);
...@@ -236,6 +249,7 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> { ...@@ -236,6 +249,7 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> {
foregroundPainter: _scrollDirection == null ? null : new _Painter( foregroundPainter: _scrollDirection == null ? null : new _Painter(
scrollDirection: _scrollDirection, scrollDirection: _scrollDirection,
extent: _extentAnimation.value, extent: _extentAnimation.value,
dragPosition: _dragPosition,
isLeading: _scrollOffset < _minScrollOffset, isLeading: _scrollOffset < _minScrollOffset,
color: _indicatorColor color: _indicatorColor
), ),
......
...@@ -365,7 +365,7 @@ class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -365,7 +365,7 @@ class ScrollableState<T extends Scrollable> extends State<T> {
_setScrollOffset(_controller.value); _setScrollOffset(_controller.value);
} }
void _setScrollOffset(double newScrollOffset) { void _setScrollOffset(double newScrollOffset, { DragUpdateDetails details }) {
if (_scrollOffset == newScrollOffset) if (_scrollOffset == newScrollOffset)
return; return;
setState(() { setState(() {
...@@ -374,6 +374,11 @@ class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -374,6 +374,11 @@ class ScrollableState<T extends Scrollable> extends State<T> {
PageStorage.of(context)?.writeState(context, _scrollOffset); PageStorage.of(context)?.writeState(context, _scrollOffset);
_startScroll(); _startScroll();
dispatchOnScroll(); dispatchOnScroll();
new ScrollNotification(
scrollable: this,
kind: ScrollNotificationKind.updated,
details: details
).dispatch(context);
_endScroll(); _endScroll();
} }
...@@ -381,9 +386,13 @@ class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -381,9 +386,13 @@ class ScrollableState<T extends Scrollable> extends State<T> {
/// ///
/// If a non-null [duration] is provided, the widget will animate to the new /// If a non-null [duration] is provided, the widget will animate to the new
/// scroll offset over the given duration with the given curve. /// scroll offset over the given duration with the given curve.
Future<Null> scrollBy(double scrollDelta, { Duration duration, Curve curve: Curves.ease }) { Future<Null> scrollBy(double scrollDelta, {
Duration duration,
Curve curve: Curves.ease,
DragUpdateDetails details
}) {
double newScrollOffset = scrollBehavior.applyCurve(_scrollOffset, scrollDelta); double newScrollOffset = scrollBehavior.applyCurve(_scrollOffset, scrollDelta);
return scrollTo(newScrollOffset, duration: duration, curve: curve); return scrollTo(newScrollOffset, duration: duration, curve: curve, details: details);
} }
/// Scroll this widget to the given scroll offset. /// Scroll this widget to the given scroll offset.
...@@ -394,13 +403,17 @@ class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -394,13 +403,17 @@ class ScrollableState<T extends Scrollable> extends State<T> {
/// This function does not accept a zero duration. To jump-scroll to /// This function does not accept a zero duration. To jump-scroll to
/// the new offset, do not provide a duration, rather than providing /// the new offset, do not provide a duration, rather than providing
/// a zero duration. /// a zero duration.
Future<Null> scrollTo(double newScrollOffset, { Duration duration, Curve curve: Curves.ease }) { Future<Null> scrollTo(double newScrollOffset, {
Duration duration,
Curve curve: Curves.ease,
DragUpdateDetails details
}) {
if (newScrollOffset == _scrollOffset) if (newScrollOffset == _scrollOffset)
return new Future<Null>.value(); return new Future<Null>.value();
if (duration == null) { if (duration == null) {
_stop(); _stop();
_setScrollOffset(newScrollOffset); _setScrollOffset(newScrollOffset, details: details);
return new Future<Null>.value(); return new Future<Null>.value();
} }
...@@ -412,7 +425,9 @@ class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -412,7 +425,9 @@ class ScrollableState<T extends Scrollable> extends State<T> {
_stop(); _stop();
_controller.value = scrollOffset; _controller.value = scrollOffset;
_startScroll(); _startScroll();
return _controller.animateTo(newScrollOffset, duration: duration, curve: curve).then(_endScroll); return _controller.animateTo(newScrollOffset, duration: duration, curve: curve).then((Null _) {
_endScroll();
});
} }
/// Update any in-progress scrolling physics to account for new scroll behavior. /// Update any in-progress scrolling physics to account for new scroll behavior.
...@@ -481,7 +496,9 @@ class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -481,7 +496,9 @@ class ScrollableState<T extends Scrollable> extends State<T> {
if (_simulation == null) if (_simulation == null)
return new Future<Null>.value(); return new Future<Null>.value();
_startScroll(); _startScroll();
return _controller.animateWith(_simulation).then(_endScroll); return _controller.animateWith(_simulation).then((Null _) {
_endScroll();
});
} }
/// Whether this scrollable should attempt to snap scroll offsets. /// Whether this scrollable should attempt to snap scroll offsets.
...@@ -549,7 +566,6 @@ class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -549,7 +566,6 @@ class ScrollableState<T extends Scrollable> extends State<T> {
assert(_numberOfInProgressScrolls > 0); assert(_numberOfInProgressScrolls > 0);
if (config.onScroll != null) if (config.onScroll != null)
config.onScroll(_scrollOffset); config.onScroll(_scrollOffset);
new ScrollNotification(this, ScrollNotificationKind.updated).dispatch(context);
} }
void _handleDragDown(_) { void _handleDragDown(_) {
...@@ -563,13 +579,19 @@ class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -563,13 +579,19 @@ class ScrollableState<T extends Scrollable> extends State<T> {
} }
void _handleDragStart(DragStartDetails details) { void _handleDragStart(DragStartDetails details) {
_startScroll(); _startScroll(details: details);
} }
void _startScroll() { void _startScroll({ DragStartDetails details }) {
_numberOfInProgressScrolls += 1; _numberOfInProgressScrolls += 1;
if (_numberOfInProgressScrolls == 1) if (_numberOfInProgressScrolls == 1) {
dispatchOnScrollStart(); dispatchOnScrollStart();
new ScrollNotification(
scrollable: this,
kind: ScrollNotificationKind.started,
details: details
).dispatch(context);
}
} }
/// Calls the onScrollStart callback. /// Calls the onScrollStart callback.
...@@ -579,25 +601,32 @@ class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -579,25 +601,32 @@ class ScrollableState<T extends Scrollable> extends State<T> {
assert(_numberOfInProgressScrolls == 1); assert(_numberOfInProgressScrolls == 1);
if (config.onScrollStart != null) if (config.onScrollStart != null)
config.onScrollStart(_scrollOffset); config.onScrollStart(_scrollOffset);
new ScrollNotification(this, ScrollNotificationKind.started).dispatch(context);
} }
void _handleDragUpdate(DragUpdateDetails details) { void _handleDragUpdate(DragUpdateDetails details) {
scrollBy(pixelOffsetToScrollOffset(details.primaryDelta)); scrollBy(pixelOffsetToScrollOffset(details.primaryDelta), details: details);
} }
void _handleDragEnd(DragEndDetails details) { void _handleDragEnd(DragEndDetails details) {
final double scrollVelocity = pixelDeltaToScrollOffset(details.velocity.pixelsPerSecond); final double scrollVelocity = pixelDeltaToScrollOffset(details.velocity.pixelsPerSecond);
fling(scrollVelocity).then(_endScroll); fling(scrollVelocity).then((Null _) {
_endScroll(details: details);
});
} }
Null _endScroll([Null _]) { void _endScroll({ DragEndDetails details }) {
_numberOfInProgressScrolls -= 1; _numberOfInProgressScrolls -= 1;
if (_numberOfInProgressScrolls == 0) { if (_numberOfInProgressScrolls == 0) {
_simulation = null; _simulation = null;
dispatchOnScrollEnd(); dispatchOnScrollEnd();
if (mounted) {
new ScrollNotification(
scrollable: this,
kind: ScrollNotificationKind.ended,
details: details
).dispatch(context);
}
} }
return null;
} }
/// Calls the dispatchOnScrollEnd callback. /// Calls the dispatchOnScrollEnd callback.
...@@ -607,8 +636,6 @@ class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -607,8 +636,6 @@ class ScrollableState<T extends Scrollable> extends State<T> {
assert(_numberOfInProgressScrolls == 0); assert(_numberOfInProgressScrolls == 0);
if (config.onScrollEnd != null) if (config.onScrollEnd != null)
config.onScrollEnd(_scrollOffset); config.onScrollEnd(_scrollOffset);
if (mounted)
new ScrollNotification(this, ScrollNotificationKind.ended).dispatch(context);
} }
final GlobalKey<RawGestureDetectorState> _gestureDetectorKey = new GlobalKey<RawGestureDetectorState>(); final GlobalKey<RawGestureDetectorState> _gestureDetectorKey = new GlobalKey<RawGestureDetectorState>();
...@@ -716,7 +743,14 @@ enum ScrollNotificationKind { ...@@ -716,7 +743,14 @@ enum ScrollNotificationKind {
/// * [NotificationListener] /// * [NotificationListener]
class ScrollNotification extends Notification { class ScrollNotification extends Notification {
/// Creates a notification about scrolling. /// Creates a notification about scrolling.
ScrollNotification(this.scrollable, this.kind); ScrollNotification({ this.scrollable, this.kind, dynamic details }) : _details = details {
assert(scrollable != null);
assert(kind != null);
assert(details == null
|| (kind == ScrollNotificationKind.started && details is DragStartDetails)
|| (kind == ScrollNotificationKind.updated && details is DragUpdateDetails)
|| (kind == ScrollNotificationKind.ended && details is DragEndDetails));
}
/// Indicates if we're at the start, middle, or end of a scroll. /// Indicates if we're at the start, middle, or end of a scroll.
final ScrollNotificationKind kind; final ScrollNotificationKind kind;
...@@ -724,6 +758,11 @@ class ScrollNotification extends Notification { ...@@ -724,6 +758,11 @@ class ScrollNotification extends Notification {
/// The scrollable that scrolled. /// The scrollable that scrolled.
final ScrollableState scrollable; final ScrollableState scrollable;
DragStartDetails get dragStartDetails => kind == ScrollNotificationKind.started ? _details : null;
DragUpdateDetails get dragUpdateDetails => kind == ScrollNotificationKind.updated ? _details : null;
DragEndDetails get dragEndDetails => kind == ScrollNotificationKind.ended ? _details : null;
final dynamic _details;
/// The number of scrollable widgets that have already received this /// The number of scrollable widgets that have already received this
/// notification. Typically listeners only respond to notifications /// notification. Typically listeners only respond to notifications
/// with depth = 0. /// with depth = 0.
......
...@@ -23,16 +23,29 @@ void main() { ...@@ -23,16 +23,29 @@ void main() {
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(notification.kind, equals(ScrollNotificationKind.started)); expect(notification.kind, equals(ScrollNotificationKind.started));
expect(notification.depth, equals(0)); expect(notification.depth, equals(0));
expect(notification.dragStartDetails, isNotNull);
expect(notification.dragStartDetails.globalPosition, equals(new Point(100.0, 100.0)));
expect(notification.dragUpdateDetails, isNull);
expect(notification.dragEndDetails, isNull);
await gesture.moveBy(new Offset(-10.0, -10.0)); await gesture.moveBy(new Offset(-10.0, -10.0));
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(notification.kind, equals(ScrollNotificationKind.updated)); expect(notification.kind, equals(ScrollNotificationKind.updated));
expect(notification.depth, equals(0)); expect(notification.depth, equals(0));
expect(notification.dragStartDetails, isNull);
expect(notification.dragUpdateDetails, isNotNull);
expect(notification.dragUpdateDetails.globalPosition, equals(new Point(90.0, 90.0)));
expect(notification.dragUpdateDetails.delta, equals(new Offset(0.0, -10.0)));
expect(notification.dragEndDetails, isNull);
await gesture.up(); await gesture.up();
await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1));
expect(notification.kind, equals(ScrollNotificationKind.ended)); expect(notification.kind, equals(ScrollNotificationKind.ended));
expect(notification.depth, equals(0)); expect(notification.depth, equals(0));
expect(notification.dragStartDetails, isNull);
expect(notification.dragUpdateDetails, isNull);
expect(notification.dragEndDetails, isNotNull);
expect(notification.dragEndDetails.velocity, equals(Velocity.zero));
}); });
testWidgets('Scroll notifcation depth', (WidgetTester tester) async { testWidgets('Scroll notifcation depth', (WidgetTester tester) async {
......
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