本效果基于SnapHelper的源码的基础上进行修改,区分处理了onFiling事件和onScroll事件:
上代码:
private final RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
boolean mScrolled = false;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
// SCROLL_STATE_IDLE是当屏幕停止滚动时 0
//SCROLL_STATE_TOUCH_SCROLL是当用户在以触屏方式滚动屏幕并且手指仍然还在屏幕上时 1
//SCROLL_STATE_FLING是当用户由于之前划动屏幕并抬起手指,屏幕产生惯性滑动时 2
if (newState != RecyclerView.SCROLL_STATE_IDLE) {
return;
}
//当滚动停止且产生位移的时候执行对齐方法
if (mScrolled) {
mScrolled = false;
snapToTargetExistingView();
return;
}
//当不在位移的时候将当前的位置提供出来
if (mPageListener != null) {
mPageListener.onPageSelector(mCurrentPageIndex);
}
}
Scroll事件的处理逻辑
/**
* 获取当前应该显示第几页
*/
private int getPagerIndex() {
final int verticalScrollOffset = mRecyclerView.computeVerticalScrollOffset();
final int horizontalScrollOffset = mRecyclerView.computeHorizontalScrollOffset();
final int currentVerticalScrollOffset = mCurrentPageIndex * mRecyclerView.getMeasuredHeight();
final int currentHorizontalScrollOffset = mCurrentPageIndex * mRecyclerView.getMeasuredWidth();
int index = 0;
if (mRecyclerView.getLayoutManager().canScrollVertically()) {
//除掉整页距离之后的距离
final float offset = verticalScrollOffset * 1.f % mRecyclerView.getMeasuredHeight();
final float page = verticalScrollOffset * 1.f / mRecyclerView.getMeasuredHeight();//前面还有多少页
index = (int) Math.floor(page);//前面还有多少页, 取整
if (offset == 0) {
return index;
}
if (currentVerticalScrollOffset <= verticalScrollOffset) {
//向上滚动
if (offset >= mRecyclerView.getMeasuredHeight() / 2) {
//超过屏幕 1/2 就开始滑动
index = mCurrentPageIndex + 1;
} else {
index = mCurrentPageIndex;
}
} else {
//向下滚动
if (offset >= mRecyclerView.getMeasuredHeight() / 2) {
//超过屏幕 1/2 就开始滑动
index = mCurrentPageIndex;
} else {
index = mCurrentPageIndex - 1;
}
}
} else if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
final float offset = horizontalScrollOffset * 1.f % mRecyclerView.getMeasuredWidth();
final float page = horizontalScrollOffset * 1.f / mRecyclerView.getMeasuredWidth();
index = (int) Math.floor(page);
if (offset == 0) {
return index;
}
if (currentHorizontalScrollOffset <= horizontalScrollOffset) {
//向左滚动
if (offset >= mRecyclerView.getMeasuredWidth() / 2) {
//超过一半的距离
index = mCurrentPageIndex + 1;
} else {
index = mCurrentPageIndex;
}
} else {
//向右滚动
if (offset >= mRecyclerView.getMeasuredWidth() / 2) {
//超过一半的距离
index = mCurrentPageIndex;
} else {
index = mCurrentPageIndex - 1;
}
}
}
return index;
}
fling事件的处理逻辑
/**
* 当fling事件执行后找到fling最终的position
*
* @param layoutManager
* @param velocityX
* @param velocityY
* @return
*/
private int findTargetSnapPosition(LayoutManager layoutManager, int velocityX, int velocityY) {
final int itemCount = layoutManager.getItemCount();
if (itemCount == 0) {
return RecyclerView.NO_POSITION;
}
int index = RecyclerView.NO_POSITION;
if (layoutManager.canScrollHorizontally()) {
if (velocityX > 0) {
return mCurrentPageIndex + 1;
} else {
return mCurrentPageIndex - 1;
}
}
if (layoutManager.canScrollVertically()) {
if (velocityY > 0) {
return mCurrentPageIndex + 1;
} else {
return mCurrentPageIndex - 1;
}
}
return index;
}
最终snap逻辑:
/**
* 滑动事件的真正执行
*
* @param layoutManager
* @param position
*/
private void handleScrollToPosition(LayoutManager layoutManager, int position) {
int itemCount = layoutManager.getItemCount();
if (itemCount == 0) {
return;
}
if (position >= itemCount) {
return;
}
mCurrentPageIndex = position;
int[] snapDistance = calculateDistanceToFinalSnap(layoutManager);
if (snapDistance[0] != 0 || snapDistance[1] != 0) {
mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]);
}
}
计算真正需要滑动的距离:
@SuppressWarnings("WeakerAccess")
@Nullable
public int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager) {
int[] out = new int[2];
if (layoutManager.canScrollHorizontally()) {
out[0] = mCurrentPageIndex * mRecyclerView.getMeasuredWidth() - mRecyclerView.computeHorizontalScrollOffset();
} else {
out[0] = 0;
}
if (layoutManager.canScrollVertically()) {
out[1] = mCurrentPageIndex * mRecyclerView.getMeasuredHeight() - mRecyclerView.computeVerticalScrollOffset();
} else {
out[1] = 0;
}
return out;
}