共计 3234 个字符,预计需要花费 9 分钟才能阅读完成。
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
,计算出间隔核心的挪动量,最初挪动过来。
正文完