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
:
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);
// 滚动进行后
if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) {
mScrolled = false;
snapToTargetExistingView();}
}
@Override
public 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
:
@Nullable
private 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
,计算出间隔核心的挪动量,最初挪动过来。