Unverified Commit 4ad47fb4 authored by Jan Kuß's avatar Jan Kuß Committed by GitHub

Fix `StretchingOverscrollIndicator` not handling directional changes correctly (#116548)

* Fix overscroll behaviour of `StretchingOverscrollIndicator` on directional change while scrolling

* Remove trailing spaces

* Change naming of `_StretchDirection` values to `trailing` and `leading`

* Remove trailing space

* Apply suggestions from code review

* Fix stretching overscroll indicator direction on fling

* Fix issue when changing direction while recede animation is playing

* Remove trailing space

* Add test for changing direction during recede animation
parent 1f85497e
...@@ -101,4 +101,5 @@ Junhua Lin <1075209054@qq.com> ...@@ -101,4 +101,5 @@ Junhua Lin <1075209054@qq.com>
Tomasz Gucio <tgucio@gmail.com> Tomasz Gucio <tgucio@gmail.com>
Jason C.H <ctrysbita@outlook.com> Jason C.H <ctrysbita@outlook.com>
Hubert Jóźwiak <hjozwiakdx@gmail.com> Hubert Jóźwiak <hjozwiakdx@gmail.com>
Eli Albert <crasowas@gmail.com> Eli Albert <crasowas@gmail.com>
\ No newline at end of file Jan Kuß <jan@kuss.dev>
...@@ -601,6 +601,15 @@ class _GlowingOverscrollIndicatorPainter extends CustomPainter { ...@@ -601,6 +601,15 @@ class _GlowingOverscrollIndicatorPainter extends CustomPainter {
} }
} }
enum _StretchDirection {
/// The [trailing] direction indicates that the content will be stretched toward
/// the trailing edge.
trailing,
/// The [leading] direction indicates that the content will be stretched toward
/// the leading edge.
leading,
}
/// A Material Design visual indication that a scroll view has overscrolled. /// A Material Design visual indication that a scroll view has overscrolled.
/// ///
/// A [StretchingOverscrollIndicator] listens for [ScrollNotification]s in order /// A [StretchingOverscrollIndicator] listens for [ScrollNotification]s in order
...@@ -689,6 +698,9 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi ...@@ -689,6 +698,9 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi
late final _StretchController _stretchController = _StretchController(vsync: this); late final _StretchController _stretchController = _StretchController(vsync: this);
ScrollNotification? _lastNotification; ScrollNotification? _lastNotification;
OverscrollNotification? _lastOverscrollNotification; OverscrollNotification? _lastOverscrollNotification;
double _totalOverscroll = 0.0;
bool _accepted = true; bool _accepted = true;
bool _handleScrollNotification(ScrollNotification notification) { bool _handleScrollNotification(ScrollNotification notification) {
...@@ -706,9 +718,11 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi ...@@ -706,9 +718,11 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi
assert(notification.metrics.axis == widget.axis); assert(notification.metrics.axis == widget.axis);
if (_accepted) { if (_accepted) {
_totalOverscroll += notification.overscroll;
if (notification.velocity != 0.0) { if (notification.velocity != 0.0) {
assert(notification.dragDetails == null); assert(notification.dragDetails == null);
_stretchController.absorbImpact(notification.velocity.abs()); _stretchController.absorbImpact(notification.velocity.abs(), _totalOverscroll);
} else { } else {
assert(notification.overscroll != 0.0); assert(notification.overscroll != 0.0);
if (notification.dragDetails != null) { if (notification.dragDetails != null) {
...@@ -716,38 +730,40 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi ...@@ -716,38 +730,40 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi
// which is the furthest distance a single pointer could pull on the // which is the furthest distance a single pointer could pull on the
// screen. This is because more than one pointer will multiply the // screen. This is because more than one pointer will multiply the
// amount of overscroll - https://github.com/flutter/flutter/issues/11884 // amount of overscroll - https://github.com/flutter/flutter/issues/11884
final double viewportDimension = notification.metrics.viewportDimension; final double viewportDimension = notification.metrics.viewportDimension;
final double distanceForPull = final double distanceForPull = _totalOverscroll.abs() / viewportDimension;
(notification.overscroll.abs() / viewportDimension) + _stretchController.pullDistance;
final double clampedOverscroll = clampDouble(distanceForPull, 0, 1.0); final double clampedOverscroll = clampDouble(distanceForPull, 0, 1.0);
_stretchController.pull(clampedOverscroll); _stretchController.pull(clampedOverscroll, _totalOverscroll);
} }
} }
} }
} else if (notification is ScrollEndNotification || notification is ScrollUpdateNotification) { } else if (notification is ScrollEndNotification || notification is ScrollUpdateNotification) {
// Since the overscrolling ended, we reset the total overscroll amount.
_totalOverscroll = 0;
_stretchController.scrollEnd(); _stretchController.scrollEnd();
} }
_lastNotification = notification; _lastNotification = notification;
return false; return false;
} }
AlignmentGeometry _getAlignmentForAxisDirection(double overscroll) { AlignmentGeometry _getAlignmentForAxisDirection(_StretchDirection stretchDirection) {
// Accounts for reversed scrollables by checking the AxisDirection // Accounts for reversed scrollables by checking the AxisDirection
switch (widget.axisDirection) { switch (widget.axisDirection) {
case AxisDirection.up: case AxisDirection.up:
return overscroll > 0 return stretchDirection == _StretchDirection.trailing
? AlignmentDirectional.topCenter ? AlignmentDirectional.topCenter
: AlignmentDirectional.bottomCenter; : AlignmentDirectional.bottomCenter;
case AxisDirection.right: case AxisDirection.right:
return overscroll > 0 return stretchDirection == _StretchDirection.trailing
? Alignment.centerRight ? Alignment.centerRight
: Alignment.centerLeft; : Alignment.centerLeft;
case AxisDirection.down: case AxisDirection.down:
return overscroll > 0 return stretchDirection == _StretchDirection.trailing
? AlignmentDirectional.bottomCenter ? AlignmentDirectional.bottomCenter
: AlignmentDirectional.topCenter; : AlignmentDirectional.topCenter;
case AxisDirection.left: case AxisDirection.left:
return overscroll > 0 return stretchDirection == _StretchDirection.trailing
? Alignment.centerLeft ? Alignment.centerLeft
: Alignment.centerRight; : Alignment.centerRight;
} }
...@@ -784,7 +800,7 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi ...@@ -784,7 +800,7 @@ class _StretchingOverscrollIndicatorState extends State<StretchingOverscrollIndi
} }
final AlignmentGeometry alignment = _getAlignmentForAxisDirection( final AlignmentGeometry alignment = _getAlignmentForAxisDirection(
_lastOverscrollNotification?.overscroll ?? 0.0 _stretchController.stretchDirection,
); );
final double viewportDimension = _lastOverscrollNotification?.metrics.viewportDimension ?? mainAxisSize; final double viewportDimension = _lastOverscrollNotification?.metrics.viewportDimension ?? mainAxisSize;
...@@ -836,6 +852,9 @@ class _StretchController extends ChangeNotifier { ...@@ -836,6 +852,9 @@ class _StretchController extends ChangeNotifier {
double get pullDistance => _pullDistance; double get pullDistance => _pullDistance;
double _pullDistance = 0.0; double _pullDistance = 0.0;
_StretchDirection get stretchDirection => _stretchDirection;
_StretchDirection _stretchDirection = _StretchDirection.trailing;
// Constants from Android. // Constants from Android.
static const double _exponentialScalar = math.e / 0.33; static const double _exponentialScalar = math.e / 0.33;
static const double _stretchIntensity = 0.016; static const double _stretchIntensity = 0.016;
...@@ -847,7 +866,7 @@ class _StretchController extends ChangeNotifier { ...@@ -847,7 +866,7 @@ class _StretchController extends ChangeNotifier {
/// Handle a fling to the edge of the viewport at a particular velocity. /// Handle a fling to the edge of the viewport at a particular velocity.
/// ///
/// The velocity must be positive. /// The velocity must be positive.
void absorbImpact(double velocity) { void absorbImpact(double velocity, double totalOverscroll) {
assert(velocity >= 0.0); assert(velocity >= 0.0);
velocity = clampDouble(velocity, 1, 10000); velocity = clampDouble(velocity, 1, 10000);
_stretchSizeTween.begin = _stretchSize.value; _stretchSizeTween.begin = _stretchSize.value;
...@@ -855,6 +874,7 @@ class _StretchController extends ChangeNotifier { ...@@ -855,6 +874,7 @@ class _StretchController extends ChangeNotifier {
_stretchController.duration = Duration(milliseconds: (velocity * 0.02).round()); _stretchController.duration = Duration(milliseconds: (velocity * 0.02).round());
_stretchController.forward(from: 0.0); _stretchController.forward(from: 0.0);
_state = _StretchState.absorb; _state = _StretchState.absorb;
_stretchDirection = totalOverscroll > 0 ? _StretchDirection.trailing : _StretchDirection.leading;
} }
/// Handle a user-driven overscroll. /// Handle a user-driven overscroll.
...@@ -862,8 +882,19 @@ class _StretchController extends ChangeNotifier { ...@@ -862,8 +882,19 @@ class _StretchController extends ChangeNotifier {
/// The `normalizedOverscroll` argument should be the absolute value of the /// The `normalizedOverscroll` argument should be the absolute value of the
/// scroll distance in logical pixels, divided by the extent of the viewport /// scroll distance in logical pixels, divided by the extent of the viewport
/// in the main axis. /// in the main axis.
void pull(double normalizedOverscroll) { void pull(double normalizedOverscroll, double totalOverscroll) {
assert(normalizedOverscroll >= 0.0); assert(normalizedOverscroll >= 0.0);
final _StretchDirection newStretchDirection = totalOverscroll > 0 ? _StretchDirection.trailing : _StretchDirection.leading;
if (_stretchDirection != newStretchDirection && _state == _StretchState.recede) {
// When the stretch direction changes while we are in the recede state, we need to ignore the change.
// If we don't, the stretch will instantly jump to the new direction with the recede animation still playing, which causes
// a unwanted visual abnormality (https://github.com/flutter/flutter/pull/116548#issuecomment-1414872567).
// By ignoring the directional change until the recede state is finished, we can avoid this.
return;
}
_stretchDirection = newStretchDirection;
_pullDistance = normalizedOverscroll; _pullDistance = normalizedOverscroll;
_stretchSizeTween.begin = _stretchSize.value; _stretchSizeTween.begin = _stretchSize.value;
final double linearIntensity =_stretchIntensity * _pullDistance; final double linearIntensity =_stretchIntensity * _pullDistance;
......
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