乐趣区

关于android:ViewPager2的滑动回弹动画

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

@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,计算出间隔核心的挪动量,最初挪动过来。

退出移动版