chat_scroll_physics.dart 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import 'dart:math' as math;
  2. import 'package:flutter/gestures.dart';
  3. import 'package:flutter/material.dart';
  4. ///base on BouncingScrollPhysics
  5. ///
  6. class ChatScrollPhysics extends ScrollPhysics {
  7. /// Creates scroll physics that bounce back from the edge.
  8. const ChatScrollPhysics({required ScrollPhysics parent}) : super(parent: parent);
  9. // @override
  10. // ChatScrollPhysics applyTo(ScrollPhysics ancestor) {
  11. // return ChatScrollPhysics(parent: buildParent(ancestor));
  12. // }
  13. /// The multiple applied to overscroll to make it appear that scrolling past
  14. /// the edge of the scrollable contents is harder than scrolling the list.
  15. /// This is done by reducing the ratio of the scroll effect output vs the
  16. /// scroll gesture input.
  17. ///
  18. /// This factor starts at 0.52 and progressively becomes harder to overscroll
  19. /// as more of the area past the edge is dragged in (represented by an increasing
  20. /// `overscrollFraction` which starts at 0 when there is no overscroll).
  21. double frictionFactor(double overscrollFraction) =>
  22. 0.32 * math.pow(1 - overscrollFraction, 2);
  23. @override
  24. double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
  25. assert(offset != 0.0);
  26. assert(position.minScrollExtent <= position.maxScrollExtent);
  27. if (!position.outOfRange) return offset;
  28. final double overscrollPastStart =
  29. math.max(position.minScrollExtent - position.pixels, 0.0);
  30. final double overscrollPastEnd =
  31. math.max(position.pixels - position.maxScrollExtent, 0.0);
  32. final double overscrollPast =
  33. math.max(overscrollPastStart, overscrollPastEnd);
  34. final bool easing = (overscrollPastStart > 0.0 && offset < 0.0) ||
  35. (overscrollPastEnd > 0.0 && offset > 0.0);
  36. final double friction = easing
  37. // Apply less resistance when easing the overscroll vs tensioning.
  38. ? frictionFactor(
  39. (overscrollPast - offset.abs()) / position.viewportDimension)
  40. : frictionFactor(overscrollPast / position.viewportDimension);
  41. final double direction = offset.sign;
  42. return direction * _applyFriction(overscrollPast, offset.abs(), friction);
  43. }
  44. static double _applyFriction(
  45. double extentOutside, double absDelta, double gamma) {
  46. assert(absDelta > 0);
  47. double total = 0.0;
  48. if (extentOutside > 0) {
  49. final double deltaToLimit = extentOutside / gamma;
  50. if (absDelta < deltaToLimit) return absDelta * gamma;
  51. total += extentOutside;
  52. absDelta -= deltaToLimit;
  53. }
  54. return total + absDelta;
  55. }
  56. @override
  57. double applyBoundaryConditions(ScrollMetrics position, double value) {
  58. if (value < position.pixels && position.pixels <= position.minScrollExtent) // underscroll
  59. return value - position.pixels;
  60. if (value < position.minScrollExtent && position.minScrollExtent < position.pixels) // hit top edge
  61. return value - position.minScrollExtent;
  62. return 0.0;
  63. }
  64. @override
  65. Simulation? createBallisticSimulation(
  66. ScrollMetrics position, double velocity) {
  67. final Tolerance tolerance = this.tolerance;
  68. if (velocity.abs() >= tolerance.velocity || position.outOfRange) {
  69. return BouncingScrollSimulation(
  70. spring: spring,
  71. position: position.pixels,
  72. velocity: velocity * 0.91,
  73. // TODO(abarth): We should move this constant closer to the drag end.
  74. leadingExtent: position.minScrollExtent,
  75. trailingExtent: position.maxScrollExtent,
  76. tolerance: tolerance,
  77. );
  78. }
  79. return null;
  80. }
  81. // The ballistic simulation here decelerates more slowly than the one for
  82. // ClampingScrollPhysics so we require a more deliberate input gesture
  83. // to trigger a fling.
  84. @override
  85. double get minFlingVelocity => kMinFlingVelocity * 2.0;
  86. // Methodology:
  87. // 1- Use https://github.com/flutter/scroll_overlay to test with Flutter and
  88. // platform scroll views superimposed.
  89. // 2- Record incoming speed and make rapid flings in the test app.
  90. // 3- If the scrollables stopped overlapping at any moment, adjust the desired
  91. // output value of this function at that input speed.
  92. // 4- Feed new input/output set into a power curve fitter. Change function
  93. // and repeat from 2.
  94. // 5- Repeat from 2 with medium and slow flings.
  95. /// Momentum build-up function that mimics iOS's scroll speed increase with repeated flings.
  96. ///
  97. /// The velocity of the last fling is not an important factor. Existing speed
  98. /// and (related) time since last fling are factors for the velocity transfer
  99. /// calculations.
  100. @override
  101. double carriedMomentum(double existingVelocity) {
  102. return existingVelocity.sign *
  103. math.min(0.000816 * math.pow(existingVelocity.abs(), 1.967).toDouble(),
  104. 40000.0);
  105. }
  106. // Eyeballed from observation to counter the effect of an unintended scroll
  107. // from the natural motion of lifting the finger after a scroll.
  108. @override
  109. double get dragStartDistanceMotionThreshold => 3.5;
  110. }