@@ -21,6 +21,7 @@ import 'scroll_notification.dart';
2121import 'scroll_physics.dart' ;
2222import 'scroll_position.dart' ;
2323import 'scroll_position_with_single_context.dart' ;
24+ import 'scroll_simulation.dart' ;
2425import 'scroll_view.dart' ;
2526import 'scrollable.dart' ;
2627import 'sliver.dart' ;
@@ -524,10 +525,24 @@ class _ForceImplicitScrollPhysics extends ScrollPhysics {
524525/// * [ScrollPhysics] , the base class which defines the API for scrolling
525526/// physics.
526527/// * [PageView.physics] , which can override the physics used by a page view.
528+ /// * [PageScrollSimulation] , which implements Android page view scroll physics, and
529+ /// used by this class.
527530class PageScrollPhysics extends ScrollPhysics {
528531 /// Creates physics for a [PageView] .
529532 const PageScrollPhysics ({ ScrollPhysics ? parent }) : super (parent: parent);
530533
534+ // See Android ViewPager constants
535+ // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:viewpager/viewpager/src/main/java/androidx/viewpager/widget/ViewPager.java;l=116;drc=1dcb8847e7aca80ee78c5d9864329b93dd276379
536+ static const int _kMaxSettleDuration = 600 ;
537+ static const double _kMinFlingDistance = 25.0 ;
538+ static const double _kMinFlingVelocity = 400.0 ;
539+
540+ @override
541+ double get minFlingDistance => _kMinFlingDistance;
542+
543+ @override
544+ double get minFlingVelocity => _kMinFlingVelocity;
545+
531546 @override
532547 PageScrollPhysics applyTo (ScrollPhysics ? ancestor) {
533548 return PageScrollPhysics (parent: buildParent (ancestor));
@@ -545,13 +560,24 @@ class PageScrollPhysics extends ScrollPhysics {
545560 return page * position.viewportDimension;
546561 }
547562
548- double _getTargetPixels (ScrollMetrics position, Tolerance tolerance, double velocity) {
549- double page = _getPage (position);
563+ double _getTargetPage (double page, Tolerance tolerance, double velocity) {
550564 if (velocity < - tolerance.velocity)
551565 page -= 0.5 ;
552566 else if (velocity > tolerance.velocity)
553567 page += 0.5 ;
554- return _getPixels (position, page.roundToDouble ());
568+ return page.roundToDouble ();
569+ }
570+
571+ double _getPageDelta (ScrollMetrics position, Tolerance tolerance, double velocity) {
572+ final double page = _getPage (position);
573+ final double targetPage = _getTargetPage (page, tolerance, velocity);
574+ return targetPage - page;
575+ }
576+
577+ double _getTargetPixels (ScrollMetrics position, Tolerance tolerance, double velocity) {
578+ final double page = _getPage (position);
579+ final double targetPage = _getTargetPage (page, tolerance, velocity);
580+ return _getPixels (position, targetPage);
555581 }
556582
557583 @override
@@ -563,11 +589,54 @@ class PageScrollPhysics extends ScrollPhysics {
563589 return super .createBallisticSimulation (position, velocity);
564590 final Tolerance tolerance = this .tolerance;
565591 final double target = _getTargetPixels (position, tolerance, velocity);
566- if (target != position.pixels)
567- return ScrollSpringSimulation (spring, position.pixels, target, velocity, tolerance: tolerance);
592+ if (target != position.pixels) {
593+ // See Android ViewPager smoothScrollTo logic
594+ // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:viewpager/viewpager/src/main/java/androidx/viewpager/widget/ViewPager.java;l=952;drc=1dcb8847e7aca80ee78c5d9864329b93dd276379
595+ final double delta = target - position.pixels;
596+ final double width = position.viewportDimension;
597+ final double halfWidth = width / 2 ;
598+ final double distanceRatio = math.min (1.0 , 1.0 * delta.abs () / width);
599+ final double distance = halfWidth + halfWidth * _distanceInfluenceForSnapDuration (distanceRatio);
600+ int duration;
601+ if (velocity.abs () > 0 ) {
602+ duration = 4 * (1000 * (distance / velocity).abs ()).round ();
603+ } else {
604+ final double pageDelta = _getPageDelta (position, tolerance, velocity).abs ();
605+ // A slightly different algorithm than on Android, because
606+ // Flutter has different velocity estimate, which is more likely to
607+ // return zero pointer velocity when user holds his finger after a fling,
608+ // compared to Android's VelocityTracker.
609+ //
610+ // It was not clear why exactly this happens, since the estimate logic is
611+ // the same as on Android, so it was decided to adjust this formula
612+ // to produce similar results.
613+ //
614+ // On Android it looks like this:
615+ // duration = ((pageDelta + 1) * 100).toInt();
616+ duration = ((pageDelta * 100 + 1 ) * 100 ).toInt ();
617+ }
618+ duration = math.min (duration, _kMaxSettleDuration);
619+ return PageScrollSimulation (
620+ position: position.pixels,
621+ target: target,
622+ duration: duration / 1000 ,
623+ );
624+ }
568625 return null ;
569626 }
570627
628+ // See Android ViewPager distanceInfluenceForSnapDuration.
629+ //
630+ // We want the duration of the page snap animation to be influenced by the distance that
631+ // the screen has to travel, however, we don't want this duration to be effected in a
632+ // purely linear fashion. Instead, we use this method to moderate the effect that the distance
633+ // of travel has on the overall snap duration.
634+ double _distanceInfluenceForSnapDuration (double value) {
635+ value -= 0.5 ; // center the values about 0.
636+ value *= 0.3 * math.pi / 2.0 ;
637+ return math.sin (value);
638+ }
639+
571640 @override
572641 bool get allowImplicitScrolling => false ;
573642}
0 commit comments