import 'dart:math' as math; import 'package:flutter/gestures.dart'; import 'package:flutter/physics.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'shop_scroll_coordinator.dart'; /// 滑动位置信息 class ShopScrollPosition extends ScrollPosition implements ScrollActivityDelegate { final ShopScrollCoordinator coordinator; // 协调器 ScrollDragController _currentDrag; double _heldPreviousVelocity = 0.0; ShopScrollPosition( {@required ScrollPhysics physics, @required ScrollContext context, double initialPixels = 0.0, bool keepScrollOffset = true, ScrollPosition oldPosition, String debugLabel, @required this.coordinator}) : super( physics: physics, context: context, keepScrollOffset: keepScrollOffset, oldPosition: oldPosition, debugLabel: debugLabel, ) { // 如果oldPosition不为null,则父级将首先调用Absorb(),它可以设置_pixels和_activity. if (!hasPixels && initialPixels != null) correctPixels(initialPixels); if (activity == null) goIdle(); assert(activity != null); } @override AxisDirection get axisDirection => context.axisDirection; @override double setPixels(double newPixels) { assert(activity.isScrolling); return super.setPixels(newPixels); } @override void absorb(ScrollPosition other) { super.absorb(other); if (other is! ScrollPositionWithSingleContext) { goIdle(); return; } activity.updateDelegate(this); final ShopScrollPosition typedOther = other as ShopScrollPosition; _userScrollDirection = typedOther._userScrollDirection; assert(_currentDrag == null); if (typedOther._currentDrag != null) { _currentDrag = typedOther._currentDrag; _currentDrag.updateDelegate(this); typedOther._currentDrag = null; } } @override void applyNewDimensions() { super.applyNewDimensions(); context.setCanDrag(physics.shouldAcceptUserOffset(this)); } /// 返回未使用的增量。 /// 正增量表示下降(在上方显示内容),负增量向上(在下方显示内容)。 double applyClampedDragUpdate(double delta) { assert(delta != 0.0); // 如果我们要朝向maxScrollExtent(负滚动偏移),那么我们在minScrollExtent方向上 // 可以达到的最大距离是负无穷大。 例如,如果我们已经过度滚动,则滚动以减少过度滚动不应禁止过度滚动。 // 如果我们要朝minScrollExtent(正滚动偏移量)方向移动,那么我们在minScrollExtent方向 // 上可以达到的最大距离是我们现在所处的位置(如果我们已经过度滚动) // (在这种情况下,像素小于minScrollExtent),或者如果minScrollExtent可以 我们不是。 // 换句话说,我们不能通过applyClampedDragUpdate进入过滚动状态。 // 尽管如此,可能通过多种方式进入了过度滚动的情况。 一种是物理是否允许通过 // applyFullDragUpdate(请参见下文)。也可能会发生过度滚动的情况,例如 如果使用滚动控制器人工设置了滚动位置。 final double min = delta < 0.0 ? -double.infinity : math.min(minScrollExtent, pixels); // max的逻辑是等效的,但反向。 final double max = delta > 0.0 ? double.infinity : math.max(maxScrollExtent, pixels); final double oldPixels = pixels; final double newPixels = (pixels - delta).clamp(min, max) as double; final double clampedDelta = newPixels - pixels; if (clampedDelta == 0.0) return delta; final double overScroll = physics.applyBoundaryConditions(this, newPixels); final double actualNewPixels = newPixels - overScroll; final double offset = actualNewPixels - oldPixels; if (offset != 0.0) { forcePixels(actualNewPixels); didUpdateScrollPositionBy(offset); } return delta + offset; } // Returns the overScroll. 返回过度滚动。 double applyFullDragUpdate(double delta) { assert(delta != 0.0); final double oldPixels = pixels; // Apply friction: 施加摩擦: final double newPixels = pixels - physics.applyPhysicsToUserOffset(this, delta); if (oldPixels == newPixels) return 0.0; // 增量一定很小,我们在添加浮点数时将其删除了 // Check for overScroll: 检查过度滚动: final double overScroll = physics.applyBoundaryConditions(this, newPixels); final double actualNewPixels = newPixels - overScroll; if (actualNewPixels != oldPixels) { forcePixels(actualNewPixels); didUpdateScrollPositionBy(actualNewPixels - oldPixels); } return overScroll; } /// 当手指滑动时,该方法会获取到滑动距离 /// [delta]滑动距离,正增量表示下滑,负增量向上滑 /// 我们需要把子部件的 滑动数据 交给协调器处理,主部件无干扰 @override void applyUserOffset(double delta) { ScrollDirection userScrollDirection = delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse; if (debugLabel != coordinator.pageLabel) return coordinator.applyUserOffset(delta, userScrollDirection, this); updateUserScrollDirection(userScrollDirection); setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta)); } @override void beginActivity(ScrollActivity newActivity) { _heldPreviousVelocity = 0.0; if (newActivity == null) return; assert(newActivity.delegate == this); super.beginActivity(newActivity); _currentDrag?.dispose(); _currentDrag = null; if (!activity.isScrolling) updateUserScrollDirection(ScrollDirection.idle); } /// 将[用户滚动方向]设置为给定值。 /// 如果更改了该值,则将分派[User ScrollNotification]。 @protected @visibleForTesting void updateUserScrollDirection(ScrollDirection value) { assert(value != null); if (userScrollDirection == value) return; _userScrollDirection = value; didUpdateScrollDirection(value); } @override ScrollHoldController hold(VoidCallback holdCancelCallback) { final double previousVelocity = activity.velocity; final HoldScrollActivity holdActivity = HoldScrollActivity(delegate: this, onHoldCanceled: holdCancelCallback); beginActivity(holdActivity); _heldPreviousVelocity = previousVelocity; return holdActivity; } @override Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) { final ScrollDragController drag = ScrollDragController( delegate: this, details: details, onDragCanceled: dragCancelCallback, carriedVelocity: physics.carriedMomentum(_heldPreviousVelocity), motionStartDistanceThreshold: physics.dragStartDistanceMotionThreshold, ); beginActivity(DragScrollActivity(this, drag)); assert(_currentDrag == null); _currentDrag = drag; return drag; } @override void goIdle() { beginActivity(IdleScrollActivity(this)); } /// 以特定的速度开始一个物理驱动的模拟,该模拟确定[pixels]位置。 /// 此方法遵从[ScrollPhysics.createBallisticSimulation],该方法通常在当前位置超出 /// 范围时提供滑动模拟,而在当前位置超出范围但具有非零速度时提供摩擦模拟。 /// 速度应以每秒逻辑像素为单位。 @override void goBallistic(double velocity, [bool fromCoordinator = false]) { if (debugLabel != coordinator.pageLabel) { if (velocity > 0.0) coordinator.goBallistic(velocity); } else { if (fromCoordinator && velocity <= 0.0) return; if (coordinator.pageExpand == PageExpandState.Expanding) return; } assert(pixels != null); final Simulation simulation = physics.createBallisticSimulation(this, velocity); if (simulation != null) { beginActivity(BallisticScrollActivity(this, simulation, context.vsync)); } else { goIdle(); } } @override bool applyContentDimensions(double minScrollExtent, double maxScrollExtent, [bool fromCoordinator = false]) { if (debugLabel == coordinator.pageLabel && !fromCoordinator) return coordinator.applyContentDimensions( minScrollExtent, maxScrollExtent, this); return super.applyContentDimensions(minScrollExtent, maxScrollExtent); } @override ScrollDirection get userScrollDirection => _userScrollDirection; ScrollDirection _userScrollDirection = ScrollDirection.idle; @override Future animateTo( double to, { @required Duration duration, @required Curve curve, }) { if (nearEqual(to, pixels, physics.tolerance.distance)) { // 跳过动画,直接移到我们已经靠近的位置。 jumpTo(to); return Future.value(); } final DrivenScrollActivity activity = DrivenScrollActivity( this, from: pixels, to: to, duration: duration, curve: curve, vsync: context.vsync, ); beginActivity(activity); return activity.done; } @override void jumpTo(double value) { goIdle(); if (pixels != value) { final double oldPixels = pixels; forcePixels(value); notifyListeners(); didStartScroll(); didUpdateScrollPositionBy(pixels - oldPixels); didEndScroll(); } goBallistic(0.0); } @Deprecated('This will lead to bugs.') @override void jumpToWithoutSettling(double value) { goIdle(); if (pixels != value) { final double oldPixels = pixels; forcePixels(value); notifyListeners(); didStartScroll(); didUpdateScrollPositionBy(pixels - oldPixels); didEndScroll(); } } @override void dispose() { _currentDrag?.dispose(); _currentDrag = null; super.dispose(); } @override void debugFillDescription(List description) { super.debugFillDescription(description); description.add('${context.runtimeType}'); description.add('$physics'); description.add('$activity'); description.add('$userScrollDirection'); } @override void pointerScroll(double delta) { // TODO: implement pointerScroll } }