Android-->如何將RecyclerView打造成ViewPager的效果
阿新 • • 發佈:2019-01-08
更新於: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));
}
至此: 文章就結束了,如有疑問: QQ群 Android:274306954 Swift:399799363 歡迎您的加入.