ViewPager2的滑动回弹动画

首先剖析回弹动画的成果,滑动小于肯定阈值时,松手后触发,页面回弹到原地位。

切入点

第一反馈就是onTouchEvent,发现ViewPager2没有重写,并且它是一个ViewGroup,咱们看它初始化的时候设置了什么,调用了 initialize,看到最终嵌套了一个自定义的RecyclerView,发现没有重写onTouchEvent,那么只有可能通过addOnScrollListener来拦挡触摸。

持续剖析initialize,咱们能够把指标锁定在ScrollEventAdapterPagerSnapHelperImpl,通过查看类形容,咱们能够根本确定是在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,计算出间隔核心的挪动量,最初挪动过来。