1. 程式人生 > >Android-->如何將RecyclerView打造成ViewPager的效果

Android-->如何將RecyclerView打造成ViewPager的效果

更新於: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 歡迎您的加入.