ViewPager2的滑动回弹动画
首先剖析回弹动画的成果,滑动小于肯定阈值时,松手后触发,页面回弹到原地位。
切入点
第一反馈就是onTouchEvent
,发现ViewPager2
没有重写,并且它是一个ViewGroup
,咱们看它初始化的时候设置了什么,调用了 initialize
,看到最终嵌套了一个自定义的RecyclerView
,发现没有重写onTouchEvent
,那么只有可能通过addOnScrollListener
来拦挡触摸。
持续剖析initialize
,咱们能够把指标锁定在ScrollEventAdapter
和PagerSnapHelperImpl
,通过查看类形容,咱们能够根本确定是在PagerSnapHelperImpl
中实现的。
源码剖析
咱们首要指标是追踪addOnScrollListener
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException { ... mRecyclerView = recyclerView; if (mRecyclerView != null) { setupCallbacks(); ... }}private void setupCallbacks() throws IllegalStateException { ... mRecyclerView.addOnScrollListener(mScrollListener); ...}
mScrollListener
:
@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); // 滚动进行后 if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) { mScrolled = false; snapToTargetExistingView(); }}@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) { // 解决visible change的状况,具体看办法正文 if (dx != 0 || dy != 0) { mScrolled = true; }}
snapToTargetExistingView
:
void snapToTargetExistingView() { ... View snapView = findSnapView(layoutManager); if (snapView == null) { return; } int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView); if (snapDistance[0] != 0 || snapDistance[1] != 0) { mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]); }}
这段源码一眼看去,就是找了个什么snapView
,而后计算出了一个snapDistance
,而后滚动到该地位,八九不离十了,外围就在这两个办法中。
咱们持续剖析下:
snapView
是指什么?snapDistance
是指什么?
findSnapView
public View findSnapView(RecyclerView.LayoutManager layoutManager) { if (layoutManager.canScrollVertically()) { return findCenterView(layoutManager, getVerticalHelper(layoutManager)); } ... return null;}
findCenterView
:
@Nullableprivate View findCenterView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) { // 拿到rv以后可见的几个子View int childCount = layoutManager.getChildCount(); if (childCount == 0) { return null; } View closestChild = null; // 计算出rv的中点 final int center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; int absClosest = Integer.MAX_VALUE; // 取间隔rv中心点最近的子View for (int i = 0; i < childCount; i++) { final View child = layoutManager.getChildAt(i); // 计算出子View的中点 int childCenter = helper.getDecoratedStart(child) + (helper.getDecoratedMeasurement(child) / 2); int absDistance = Math.abs(childCenter - center); /* if child center is closer than previous closest, set it as closest */ // 记录最靠近的子View和间隔 if (absDistance < absClosest) { absClosest = absDistance; closestChild = child; } } return closestChild;}
可知snapView
就是最靠近RecyclerView
两头点的子View
calculateDistanceToFinalSnap
public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { int[] out = new int[2]; ... if (layoutManager.canScrollVertically()) { out[1] = distanceToCenter(targetView, getVerticalHelper(layoutManager)); } ... return out;}
distanceToCenter
:
private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView, OrientationHelper helper) { // 跟findCenterView一样 // 计算出targetView中心点间隔rv中心点的间隔 final int childCenter = helper.getDecoratedStart(targetView) + (helper.getDecoratedMeasurement(targetView) / 2); final int containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; return childCenter - containerCenter;}
可知snapDistance
就是snapView
的两头点间隔RecyclerView
的两头点的间隔
论断
到此源码就剖析完了,滑动回弹的实现形式是:滑动进行后,遍历以后可见的子View
,找到最靠近中心点的View
,计算出间隔核心的挪动量,最初挪动过来。