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


更新于:2017-2-16
以前的實(shí)現(xiàn)方式, 雖然面前可以達(dá)到效果, 但是著實(shí)有點(diǎn)low,
現(xiàn)在提供一種體驗(yàn)相當(dāng)好的解決方案:SnapHelper

以下是實(shí)現(xiàn)代碼: 其實(shí)就是同時(shí)處理OnScrollListener事件和OnFlingListener事件.
比我之前的方法多了一個(gè)OnFlingListener事件的監(jiān)聽(tīng).

public class ViewPagerSnapHelper extends SnapHelper {

    /**
     * 每一頁(yè)中, 含有多少個(gè)item
     */
    int mPageItemCount = 1;
    /**
     * 當(dāng)前頁(yè)面索引
     */
    int mCurrentPageIndex = 0;
    RecyclerView mRecyclerView;
    PageListener mPageListener;

    /**
     * 需要滾動(dòng)到目標(biāo)的頁(yè)面索引
     */
    int mTargetIndex = RecyclerView.NO_POSITION;

    /**
     * fling操作時(shí),需要鎖住目標(biāo)索引位置
     */
    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;
            //滾動(dòng)結(jié)束后, 目標(biāo)的索引位置和當(dāng)前的索引位置相同, 表示已經(jīng)完成了頁(yè)面切換
            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;
        //隨便返回一個(gè)補(bǔ)位空的view,就行.不需要通過(guò)這個(gè)View計(jì)算位置.
        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;
    }

    /**
     * 只會(huì)在onFling的時(shí)候調(diào)用
     */
    @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;
    }

    /**
     * 獲取當(dāng)前應(yīng)該顯示第幾頁(yè)
     */
    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()) {
            //除掉整頁(yè)距離之后的距離
            final float offset = verticalScrollOffset * 1.f % mRecyclerView.getMeasuredHeight();
            final float page = verticalScrollOffset * 1.f / mRecyclerView.getMeasuredHeight();//前面還有多少頁(yè)
            index = (int) Math.floor(page);//前面還有多少頁(yè), 取整
            if (offset == 0) {
                return index;
            }

            if (currentVerticalScrollOffset <= verticalScrollOffset) {
                //向上滾動(dòng)
                if (offset >= mRecyclerView.getMeasuredHeight() / 2) {
                    //超過(guò)一半的距離
                    index = mCurrentPageIndex + 1;
                } else {
                    if (velocityY > 0) {
                        index = mCurrentPageIndex + 1;
                    } else {
                        index = mCurrentPageIndex;
                    }
                }

            } else {
                //向下滾動(dòng)
                if (offset >= mRecyclerView.getMeasuredHeight() / 2) {
                    //超過(guò)一半的距離
                    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) {
                //向左滾動(dòng)
                if (offset >= mRecyclerView.getMeasuredWidth() / 2) {
                    //超過(guò)一半的距離
                    index = mCurrentPageIndex + 1;
                } else {
                    if (velocityX > 0) {
                        index = mCurrentPageIndex + 1;
                    } else {
                        index = mCurrentPageIndex;
                    }
                }

            } else {
                //向右滾動(dòng)
                if (offset >= mRecyclerView.getMeasuredWidth() / 2) {
                    //超過(guò)一半的距離
                    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;
    }

    /**
     * 頁(yè)面選擇回調(diào)監(jiān)聽(tīng)
     */
    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);

在配合我之前寫(xiě)的RecyclerViewPagerAdapter(必須), 就可以輕松實(shí)現(xiàn)效果了.


如題所示,

都支持橫向和縱向, 暫不支持StaggeredGridLayoutManager布局管理.

如圖:
在LinearLayoutManager中:


這里寫(xiě)圖片描述

在GridLayoutManager中:


這里寫(xiě)圖片描述

1:當(dāng)adapter中Item的數(shù)量不足時(shí), 需要用假數(shù)據(jù)填充.
否則最后一頁(yè)顯示不全, 達(dá)不到頁(yè)面的效果.

@Override
public int getItemCount() {
    rawSize = mAllDatas == null ? 0 : mAllDatas.size();
    final int itemCount = mRecyclerViewPager.getItemCount();
    final double ceil = Math.ceil(rawSize * 1f / itemCount);//當(dāng)給定的item個(gè)數(shù)不足以填充一屏?xí)r, 使用占位item
    return (int) (ceil * itemCount);
}

2:為了達(dá)到沾滿整屏的效果, 需要?jiǎng)討B(tài)計(jì)算每一個(gè)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);
    }
}

/**
 * 計(jì)算每個(gè)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:一切準(zhǔn)備好了之后,核心的滾動(dòng)計(jì)算要開(kāi)始了.

private OnScrollListener mOnScrollListener = new OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        if (newState == SCROLL_STATE_DRAGGING) {
            //開(kāi)始滾動(dòng)
            mVerticalScrollOffsetStart = recyclerView.computeVerticalScrollOffset();
            mHorizontalScrollOffsetStart = recyclerView.computeHorizontalScrollOffset();
        } else if (newState == SCROLL_STATE_IDLE) {
            //滾動(dòng)結(jié)束之后
            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) {
                //橫向滾動(dòng)
                final float page = horizontalScrollOffset * 1.f / rawWidth;//當(dāng)前滾動(dòng)到了第幾頁(yè)
                final double floor = Math.floor(page);//前一頁(yè)
                final double ceil = Math.ceil(page);//后一頁(yè)
                final int offset;
                final int offsetWidth;//滑動(dòng)之后,  剩余屏幕的寬度

                if (horizontalScrollOffset > mHorizontalScrollOffsetStart) {
                    pagerIndex = (int) floor;

                    //左滑動(dòng)
                    offset = (int) (horizontalScrollOffset - floor * rawWidth);
                    offsetWidth = rawWidth - offset;
                    if (offset >= rawWidth / 3) {
                        dx = offsetWidth;
                    } else {
                        dx = -offset;
                    }

                } else if (mHorizontalScrollOffsetStart > horizontalScrollOffset) {
                    pagerIndex = (int) ceil;

                    //右滑動(dòng)
                    offset = (int) (ceil * rawWidth - horizontalScrollOffset);//橫向滾動(dòng)了多少距離
                    offsetWidth = rawWidth - offset;
                    if (offset >= rawWidth / 3) {
                        dx = -offsetWidth;
                    } else {
                        dx = offset;
                    }
                }

            } else if (horizontalScrollOffset == 0 && verticalScrollOffset != 0) {
                //豎向滾動(dòng)
                final float page = verticalScrollOffset * 1.f / rawHeight;//當(dāng)前滾動(dòng)到了第幾頁(yè)
                final double floor = Math.floor(page);//前一頁(yè)
                final double ceil = Math.ceil(page);//后一頁(yè)
                final int offset;
                final int offsetHeight;//滑動(dòng)之后,  剩余屏幕的高度

                if (verticalScrollOffset > mVerticalScrollOffsetStart) {
                    pagerIndex = (int) floor;

                    //上滑動(dòng)
                    offset = (int) (verticalScrollOffset - floor * rawHeight);
                    offsetHeight = rawHeight - offset;
                    if (offset >= rawHeight / 3) {
                        dy = offsetHeight;
                    } else {
                        dy = -offset;
                    }

                } else if (mVerticalScrollOffsetStart > verticalScrollOffset) {
                    pagerIndex = (int) ceil;

                    //下滑動(dòng)
                    offset = (int) (ceil * rawHeight - verticalScrollOffset);//橫向滾動(dòng)了多少距離
                    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:其他需要注意的東西

//重寫(xiě)此方法, 讓手指快速滑動(dòng)的時(shí)候, 慢一點(diǎn)...
@Override
public boolean fling(int velocityX, int velocityY) {
    return super.fling((int) (velocityX * 0.3f), (int) (velocityY * 0.3f));
}


開(kāi)源地址: https://github.com/angcyo/RecyclerViewPager


至此: 文章就結(jié)束了,如有疑問(wèn): QQ群 Android:274306954 Swift:399799363 歡迎您的加入.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捺典,一起剝皮案震驚了整個(gè)濱河市顾患,隨后出現(xiàn)的幾起案子阵具,更是在濱河造成了極大的恐慌砸狞,老刑警劉巖梁只,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绝淡,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡睬罗,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)旭斥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)容达,“玉大人,你說(shuō)我怎么就攤上這事垂券』ㄑ危” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵菇爪,是天一觀的道長(zhǎng)算芯。 經(jīng)常有香客問(wèn)我,道長(zhǎng)凳宙,這世上最難降的妖魔是什么熙揍? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮氏涩,結(jié)果婚禮上届囚,老公的妹妹穿的比我還像新娘。我一直安慰自己是尖,他們只是感情好意系,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著析砸,像睡著了一般昔字。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上首繁,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音陨囊,去河邊找鬼弦疮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蜘醋,可吹牛的內(nèi)容都是我干的胁塞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼压语,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼啸罢!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起胎食,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤扰才,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后厕怜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體衩匣,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蕾总,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了琅捏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片生百。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖柄延,靈堂內(nèi)的尸體忽然破棺而出蚀浆,到底是詐尸還是另有隱情,我是刑警寧澤搜吧,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布蜡坊,位于F島的核電站,受9級(jí)特大地震影響赎败,放射性物質(zhì)發(fā)生泄漏秕衙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一僵刮、第九天 我趴在偏房一處隱蔽的房頂上張望据忘。 院中可真熱鬧,春花似錦搞糕、人聲如沸勇吊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)汉规。三九已至,卻和暖如春驹吮,著一層夾襖步出監(jiān)牢的瞬間针史,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工碟狞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留啄枕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓族沃,卻偏偏與公主長(zhǎng)得像频祝,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子脆淹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容