更新于:2017-2-16
以前的实现方式, 虽然面前可以达到效果, 但是着实有点low,
现在提供一种体验相当好的解决方案:SnapHelper
以下是实现代码: 其实就是同时处理OnScrollListener事件和OnFlingListener事件.
比我之前的方法多了一个OnFlingListener事件的监听.
public class ViewPagerSnapHelper extends SnapHelper {
/**
* 每一页中, 含有多少个item
*/
int mPageItemCount = 1;
/**
* 当前页面索引
*/
int mCurrentPageIndex = 0;
RecyclerView mRecyclerView;
PageListener mPageListener;
/**
* 需要滚动到目标的页面索引
*/
int mTargetIndex = RecyclerView.NO_POSITION;
/**
* fling操作时,需要锁住目标索引位置
*/
boolean isFling = false;
int scrollState;
private RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
scrollState = newState;
L.w("scroll state : " + newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
onScrollEnd();
} else if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
isFling = false;
} else if (newState == RecyclerView.SCROLL_STATE_SETTLING) {
}
}
};
public ViewPagerSnapHelper(int pageItemCount) {
if (pageItemCount < 1) {
throw new IllegalStateException("page item count need greater than 1");
}
this.mPageItemCount = pageItemCount;
}
protected void onScrollEnd() {
int old = mCurrentPageIndex;
int index = getPagerIndex(0, 0);
//L.i("current->" + mCurrentPageIndex + " index->" + index + " target->" + mTargetIndex);
if (index == mTargetIndex) {
mCurrentPageIndex = mTargetIndex;
//滚动结束后, 目标的索引位置和当前的索引位置相同, 表示已经完成了页面切换
if (old != mCurrentPageIndex) {
//L.e("page from->" + old + " to->" + mCurrentPageIndex);
}
if (mPageListener != null) {
mPageListener.onPageSelector(mCurrentPageIndex);
}
}
}
@Override
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException {
if (recyclerView == null) {
throw new NullPointerException("RecyclerView not be null");
}
mRecyclerView = recyclerView;
super.attachToRecyclerView(recyclerView);
mRecyclerView.addOnScrollListener(mScrollListener);
}
@Nullable
@Override
public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
@NonNull View targetView) {
int[] out = new int[2];
if (layoutManager.canScrollHorizontally()) {
out[0] = mTargetIndex * mRecyclerView.getMeasuredWidth() - mRecyclerView.computeHorizontalScrollOffset();
} else {
out[0] = 0;
}
if (layoutManager.canScrollVertically()) {
out[1] = mTargetIndex * mRecyclerView.getMeasuredHeight() - mRecyclerView.computeVerticalScrollOffset();
} else {
out[1] = 0;
}
return out;
}
@Nullable
@Override
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
int childCount = mRecyclerView.getLayoutManager().getChildCount();
final int pagerIndex = getPagerIndex(0, 0);
if (childCount == 0 || isFling) {
return null;
}
mTargetIndex = pagerIndex;
//随便返回一个补位空的view,就行.不需要通过这个View计算位置.
return mRecyclerView.getLayoutManager().getChildAt(0);
}
@Override
public boolean onFling(int velocityX, int velocityY) {
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager == null) {
return false;
}
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
if (adapter == null) {
return false;
}
int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
boolean handle = Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity;
//L.w("onFling " + handle + " " + isFling);
if (isFling) {
return false;
}
if (handle) {
if (mTargetIndex != RecyclerView.NO_POSITION) {
mCurrentPageIndex = mTargetIndex;
}
if (velocityX > 0 || velocityY > 0) {
mTargetIndex = fixPagerIndex(mCurrentPageIndex + 1);
} else if (velocityX < 0 || velocityY < 0) {
mTargetIndex = fixPagerIndex(mCurrentPageIndex - 1);
} else {
mTargetIndex = fixPagerIndex(mCurrentPageIndex);
}
int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, null);
if (snapDistance[0] != 0 || snapDistance[1] != 0) {
isFling = true;
mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]);
} else {
onScrollEnd();
}
}
return handle;
}
/**
* 只会在onFling的时候调用
*/
@Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,
int velocityY) {
final int itemCount = layoutManager.getItemCount();
if (itemCount == 0) {
return RecyclerView.NO_POSITION;
}
mTargetIndex = fixPagerIndex(getPagerIndex(velocityX, velocityY));
return mTargetIndex * mPageItemCount;
}
/**
* 获取当前应该显示第几页
*/
private int getPagerIndex(int velocityX, int velocityY) {
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) {
//超过一半的距离
index = mCurrentPageIndex + 1;
} else {
if (velocityY > 0) {
index = mCurrentPageIndex + 1;
} else {
index = mCurrentPageIndex;
}
}
} else {
//向下滚动
if (offset >= mRecyclerView.getMeasuredHeight() / 2) {
//超过一半的距离
if (velocityY < 0) {
index = mCurrentPageIndex - 1;
} else {
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 {
if (velocityX > 0) {
index = mCurrentPageIndex + 1;
} else {
index = mCurrentPageIndex;
}
}
} else {
//向右滚动
if (offset >= mRecyclerView.getMeasuredWidth() / 2) {
//超过一半的距离
if (velocityX < 0) {
index = mCurrentPageIndex - 1;
} else {
index = mCurrentPageIndex;
}
} else {
index = mCurrentPageIndex - 1;
}
}
}
return index;
}
private int fixPagerIndex(int index) {
int maxIndex = mRecyclerView.getLayoutManager().getItemCount() / mPageItemCount - 1;
int minIndex = 0;
index = Math.max(minIndex, Math.min(index, maxIndex));
if (index < mCurrentPageIndex) {
index = mCurrentPageIndex - 1;
} else if (index > mCurrentPageIndex) {
index = mCurrentPageIndex + 1;
}
return index;
}
/**
* 页面选择回调监听
*/
public ViewPagerSnapHelper setPageListener(PageListener pageListener) {
mPageListener = pageListener;
return this;
}
public interface PageListener {
void onPageSelector(int position);
}
}
使用方法:
new ViewPagerSnapHelper(getItemCount()).setPageListener(new ViewPagerSnapHelper.PageListener() {
@Override
public void onPageSelector(int position) {
onViewPagerSelect(position);
}
}).attachToRecyclerView(recyclerView);
在配合我之前写的RecyclerViewPagerAdapter(必须), 就可以轻松实现效果了.
如题所示,
都支持横向和纵向, 暂不支持StaggeredGridLayoutManager布局管理.
如图:
在LinearLayoutManager中:
在GridLayoutManager中:
1:当adapter中Item的数量不足时, 需要用假数据填充.
否则最后一页显示不全, 达不到页面的效果.
@Override
public int getItemCount() {
rawSize = mAllDatas == null ? 0 : mAllDatas.size();
final int itemCount = mRecyclerViewPager.getItemCount();
final double ceil = Math.ceil(rawSize * 1f / itemCount);//当给定的item个数不足以填充一屏时, 使用占位item
return (int) (ceil * itemCount);
}
2:为了达到沾满整屏的效果, 需要动态计算每一个Item的宽高
@Override
protected void onBindView(RBaseViewHolder holder, int position, T bean) {
holder.itemView.setLayoutParams(new ViewGroup.LayoutParams(mRecyclerViewPager.getItemWidth(),
mRecyclerViewPager.getItemHeight()));
if (holder.getItemViewType() == 200) {
onBindRawView(holder, position, bean);
}
}
/**
* 计算每个Item的宽度
*/
public int getItemWidth() {
final LayoutManager layoutManager = getLayoutManager();
int itemWidth = 0;
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
final int spanCount = gridLayoutManager.getSpanCount();
if (gridLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL) {
itemWidth = getRawWidth() / (mItemCount / spanCount);
} else {
itemWidth = getRawWidth() / spanCount;
}
} else if (layoutManager instanceof LinearLayoutManager) {
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
if (linearLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL) {
itemWidth = getRawWidth() / mItemCount;
} else {
itemWidth = getRawWidth();
}
}
return itemWidth;
}
public int getItemHeight() {
final LayoutManager layoutManager = getLayoutManager();
int itemHeight = 0;
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
final int spanCount = gridLayoutManager.getSpanCount();
if (gridLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL) {
itemHeight = getRawHeight() / spanCount;
} else {
itemHeight = getRawHeight() / (mItemCount / spanCount);
}
} else if (layoutManager instanceof LinearLayoutManager) {
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
if (linearLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL) {
itemHeight = getRawHeight();
} else {
itemHeight = getRawHeight() / mItemCount;
}
}
return itemHeight;
}
3:一切准备好了之后,核心的滚动计算要开始了.
private OnScrollListener mOnScrollListener = new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == SCROLL_STATE_DRAGGING) {
//开始滚动
mVerticalScrollOffsetStart = recyclerView.computeVerticalScrollOffset();
mHorizontalScrollOffsetStart = recyclerView.computeHorizontalScrollOffset();
} else if (newState == SCROLL_STATE_IDLE) {
//滚动结束之后
final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset();
final int horizontalScrollOffset = recyclerView.computeHorizontalScrollOffset();
final int rawWidth = getRawWidth();
final int rawHeight = getRawHeight();
int pagerIndex = mCurrentPager;
int dx = 0, dy = 0;
if (verticalScrollOffset == 0 && horizontalScrollOffset != 0) {
//横向滚动
final float page = horizontalScrollOffset * 1.f / rawWidth;//当前滚动到了第几页
final double floor = Math.floor(page);//前一页
final double ceil = Math.ceil(page);//后一页
final int offset;
final int offsetWidth;//滑动之后, 剩余屏幕的宽度
if (horizontalScrollOffset > mHorizontalScrollOffsetStart) {
pagerIndex = (int) floor;
//左滑动
offset = (int) (horizontalScrollOffset - floor * rawWidth);
offsetWidth = rawWidth - offset;
if (offset >= rawWidth / 3) {
dx = offsetWidth;
} else {
dx = -offset;
}
} else if (mHorizontalScrollOffsetStart > horizontalScrollOffset) {
pagerIndex = (int) ceil;
//右滑动
offset = (int) (ceil * rawWidth - horizontalScrollOffset);//横向滚动了多少距离
offsetWidth = rawWidth - offset;
if (offset >= rawWidth / 3) {
dx = -offsetWidth;
} else {
dx = offset;
}
}
} else if (horizontalScrollOffset == 0 && verticalScrollOffset != 0) {
//竖向滚动
final float page = verticalScrollOffset * 1.f / rawHeight;//当前滚动到了第几页
final double floor = Math.floor(page);//前一页
final double ceil = Math.ceil(page);//后一页
final int offset;
final int offsetHeight;//滑动之后, 剩余屏幕的高度
if (verticalScrollOffset > mVerticalScrollOffsetStart) {
pagerIndex = (int) floor;
//上滑动
offset = (int) (verticalScrollOffset - floor * rawHeight);
offsetHeight = rawHeight - offset;
if (offset >= rawHeight / 3) {
dy = offsetHeight;
} else {
dy = -offset;
}
} else if (mVerticalScrollOffsetStart > verticalScrollOffset) {
pagerIndex = (int) ceil;
//下滑动
offset = (int) (ceil * rawHeight - verticalScrollOffset);//横向滚动了多少距离
offsetHeight = rawHeight - offset;
if (offset >= rawHeight / 3) {
dy = -offsetHeight;
} else {
dy = offset;
}
}
} else {
pagerIndex = 0;
}
to(dx, dy);
onViewPagerSelect(pagerIndex);
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
}
};
4:其他需要注意的东西
//重写此方法, 让手指快速滑动的时候, 慢一点...
@Override
public boolean fling(int velocityX, int velocityY) {
return super.fling((int) (velocityX * 0.3f), (int) (velocityY * 0.3f));
}
开源地址: https://github.com/angcyo/RecyclerViewPager
至此: 文章就结束了,如有疑问: QQ群 Android:274306954 Swift:399799363 欢迎您的加入.