仿小米下載熱榜,RecyclerView item輪流入場

具體使用方法在這:項目地址java

小米下載熱榜

仿

下面說下具體實現: 首先,既然作了,那就多作幾種模式,左右兩邊均可以設置輪流進入或是輪流退出,因此先定義兩個枚舉類來講明是哪一種模式:git

public enum ScrollDirection {
        LEFT, // 從右到左
        RIGHT, // 從左到又
        BOTH // 都支持
    }

    public enum Mode {
        IN, // 入場
        OUT, // 出場
        BOTH // 都支持
    }

    public void setSupportScrollDirection(ScrollDirection scrollDirection) {
        mIsSupportLeft = scrollDirection == ScrollDirection.LEFT || scrollDirection == ScrollDirection.BOTH;
        mIsSupportRight = scrollDirection == ScrollDirection.RIGHT || scrollDirection == ScrollDirection.BOTH;
    }

    public void setSupportMode(Mode mode) {
        mIsSupportIn = mode == Mode.IN || mode == Mode.BOTH;
        mIsSupportOut = mode == Mode.OUT || mode == Mode.BOTH;
    }
複製代碼

而後說下思路,這是個ViewPager嵌套RecyclerView,隨着頁面的滑動,item按照RecyclerView進入屏幕的比例設置必定的偏移量,大體就是這樣,其中的一些細節後面再講。 按照上面的思路,首先咱們須要給ViewPager添加滑動監聽,在監聽中經過獲取RecyclerView相對於整個屏幕的座標位置,來判斷它處於什麼狀態,正在進入屏幕仍是滑出屏幕,從左邊進入仍是右邊進入,該不應執行入場出場動畫。添加監聽就不說了,獲取RecyclerView的位置能夠經過View#getGlobalVisibleRect(Rect r)這個方法來實現,該方法會返回一個boolean值說明目標View是否在屏幕內,若是在屏幕內的話,會將在屏幕內的部分的座標寫入Rect參數中,這裏須要注意一下是屏幕內*的部分,以下圖,黑色的框爲屏幕,藍色的是RecyclerView,紅色的是被賦值的Rectgithub

getGlobalVisibleRect

因爲咱們的動畫須要在進入或者退出時執行,因此先要判斷一下何時是進入,何時是退出,若是以前不在屏幕中那麼說明是正在進入,若是以前徹底落在了屏幕中說明是正在退出,因此代碼大體是這樣的:佈局

// 若是被回收了,那麼根佈局的parent爲null,不然爲ViewRootImpl
        if (mRecyclerView.getRootView().getParent() == null) {
            return;
        }

        boolean isInScreen = mRecyclerView.getGlobalVisibleRect(mBounds);

        if (!isInScreen) {
            mIsDoingAnimation = false;
            mIsEntering = true;
            return;
        }

        getLeftAndRight();

        // RecyclerView徹底進入屏幕
        if (mLeft >= 0 && mRight <= mWindowSize.x) {
            mIsDoingAnimation = false;
            mIsEntering = false;
            return;
        }
複製代碼

首先判斷了如下RecyclerView是否被ViewPager回收了,若是被回收了,也就是RecyclerView沒有父View了,getGlobalVisibleRect獲取到的信息就不是咱們想要的,緣由這裏就不說了,具體能夠看下getGlobalVisibleRect的源碼。這裏採用了一個我在Debug的時候發現的方法來判斷是否被回收了,若是被回收了,那麼根佈局的parentnull,不然爲ViewRootImpl。 而後經過getGlobalVisibleRect方法來判斷RecyclerView是否在屏幕中,若是不在則說明下次出如今屏幕中的時候就是正在進入,將mIsEntering設置爲true,這裏還有個變量mIsDoingAnimation是用來標記當前是否在執行動畫的,具體做用後面再講。 接着經過getLeftAndRight方法來獲取RecyclerView真實的左右座標,有了真實的座標才能夠判斷是否徹底進入屏幕。若是徹底進入屏幕則說明下次滑動時就是正在退出屏幕,將mIsEntering設置爲false,同時將mIsDoingAnimation也設置爲falsegetLeftAndRight方法實現以下:優化

/** * 計算出左右的座標,getGlobalVisibleRect獲取的是進入屏幕內的左右座標 */
    private void getLeftAndRight() {
        if (mBounds.left < mWindowSize.x && mBounds.right >= mWindowSize.x) {
            mLeft = mBounds.left;
            mRight = mLeft + mRecyclerViewWidth;
        } else if (mBounds.left <= 0 && mBounds.right > 0) {
            mRight = mBounds.right;
            mLeft = mRight - mRecyclerViewWidth;
        }
    }
複製代碼

判斷完是正在進入屏幕仍是退出屏幕,接下來就能夠根據這個來執行相應的動畫了,而這個動畫實際上就是在ViewPager橫向滑動的時候來設置RecyclerView每一個item的水平偏移量,那麼咱們就須要經過LayoutManager來獲取在屏幕中展現的每一個item的對象:動畫

private void getChildList() {
        mChildList.clear();
        mChildCount = mLayoutManager.getChildCount();
        for (int i = 0; i < mChildCount; i++) {
            mChildList.add(mLayoutManager.getChildAt(i));
        }
    }
複製代碼

獲取到了這些對象以後,咱們須要對他們進行從新佈局,這時還須要item的左右margin值才能佈局到正確的位置,這裏我只獲取了第一item的margin,正常狀況下不會給每一個item設置不一樣的marginspa

private void getMargin() {
        ViewGroup.LayoutParams params = mChildList.get(0).getLayoutParams();
        if (params instanceof ViewGroup.MarginLayoutParams) {
            mLeftMargin = ((ViewGroup.MarginLayoutParams) params).leftMargin;
            mRightMargin = ((ViewGroup.MarginLayoutParams) params).rightMargin;
        } else {
            mLeftMargin = 0;
            mRightMargin = 0;
        }
    }
複製代碼

有了上面的東西就能夠根據進入屏幕的比例來計算出每一個item應該偏移的距離了。以入場爲例,在RecyclerView剛進入屏幕的一瞬間每一個item從上到下都比前一個多偏移一個單位距離,假設單位距離爲x,那麼從上到下依次偏移x,2x,3x,·····,(n-1)x,nx隨着進入屏幕的部分愈來愈多,每一個item的偏移量逐漸減少直到回到原來的位置。單位距離採用RecyclerView減去左右margin再除以當前展現的item個數來計算,以下:code

mUnitOffset = (mRecyclerViewWidth - mLeftMargin - mRightMargin) / mChildCount;
複製代碼

這樣隨着RecyclerView逐漸進入屏幕,只需將最初的偏移量減去進入屏幕部分的寬度就能夠獲得想要的偏移量。 下面是設置偏移量的代碼:cdn

private void relayoutChild(int dx, int direction) {
        float offset;

        // 讓index爲0的子View也有偏移量
        for (int i = 1; i <= mChildCount; i++) {
            offset = (mUnitOffset * i - dx) * 1.25f;
            if (offset < 0) {
                offset = 0;
            }
            // 左爲1 右爲-1 在右邊要反向偏移
            mChildList.get(i - 1).setX(offset * direction + mLeftMargin);
        }
        mRecyclerView.requestLayout();
    }
複製代碼

for循環中,從1開始循環是爲了讓第一個item也有偏移量,還將計算出的偏移量稍微放大了一點,否則感受偏移的有點少了,很差看。其中direction傳入1表示右側,-1表示左側。右側的偏移量只須要將算出來的偏移量取反就好了,同時都要加上marginLeft確保能佈局到正確的位置。 有了上面這個方法後就能夠去佈局了,只須要再判斷一下是在左邊仍是右邊就好了:對象

// 狀態改變,從新獲取子view
        if (!mIsDoingAnimation) {
            getChildList();
            if (mChildCount == 0) {
                return;
            }
            getMargin();
            mUnitOffset = (mRecyclerViewWidth - mLeftMargin - mRightMargin) / mChildCount;
            mIsDoingAnimation = true;
        }

        // 這裏判斷的是去除左右margin後 item進入或退出屏幕纔會執行動畫
        if (mLeft + mLeftMargin <= mWindowSize.x && mRight >= mWindowSize.x && mIsSupportRight) {
            relayoutChild(mWindowSize.x - mLeft - mLeftMargin, RIGHT);
        } else if (mLeft <= 0 && mRight + mRightMargin >= 0 && mIsSupportLeft) {
            relayoutChild(mRight - mRightMargin, LEFT);
        }
複製代碼

這裏若是以前不在執行動畫,那麼須要從新獲取子View,這樣RecyclerView豎向無論滑動到哪裏均可以獲取到對應的一個子View列表。後面判斷在左側仍是右側須要算上左右margin值,也就是當item滑動到屏幕邊緣時纔會執行動畫,而不是RecyclerView滑動到屏幕邊緣就執行動畫,否則可能部分item還沒進入屏幕就已經回到原來的位置或是還沒開始偏移就已經到屏幕外面去了,具體效果能夠本身試試。 最終在ViewPager滑動時的監聽以下:

if (mRecyclerView == null) {
            return;
        }

        // 若是被回收了,那麼根佈局的parent爲null,不然爲ViewRootImpl
        if (mRecyclerView.getRootView().getParent() == null) {
            return;
        }

        boolean isInScreen = mRecyclerView.getGlobalVisibleRect(mBounds);

        if (!isInScreen) {
            // 僅支持out動畫時,徹底移出屏幕須要重置位置
            if (mIsSupportOut && !mIsSupportIn && !mIsEntering) {
                resetChild();
            }
            mIsDoingAnimation = false;
            mIsEntering = true;
            return;
        }

        getLeftAndRight();

        // RecyclerView徹底進入屏幕
        if (mLeft >= 0 && mRight <= mWindowSize.x) {
            mIsDoingAnimation = false;
            mIsEntering = false;
            return;
        }

        if (!shouldDoAnimation()) {
            return;
        }

        // 狀態改變,從新獲取子view
        if (!mIsDoingAnimation) {
            getChildList();
            if (mChildCount == 0) {
                return;
            }
            getMargin();
            mUnitOffset = (mRecyclerViewWidth - mLeftMargin - mRightMargin) / mChildCount;
            mIsDoingAnimation = true;
        }

        // 這裏判斷的是去除左右margin後 item進入或退出屏幕纔會執行動畫
        if (mLeft + mLeftMargin <= mWindowSize.x && mRight >= mWindowSize.x && mIsSupportRight) {
            relayoutChild(mWindowSize.x - mLeft - mLeftMargin, RIGHT);
        } else if (mLeft <= 0 && mRight + mRightMargin >= 0 && mIsSupportLeft) {
            relayoutChild(mRight - mRightMargin, LEFT);
        }
複製代碼

原理大體就是這樣了,最後還要注意釋放資源,防止內存泄漏,不僅是在ViewPager銷燬時須要調用,在PagerAdapterdestroyItem中也要調用:

public void onDestroy() {
        mParent.removeOnPageChangeListener(mViewPagerListener);
        mParent = null;
        mRecyclerView = null;
        mLayoutManager = null;
        mChildList.clear();
    }
複製代碼

總結

總體來說比較簡單,目前實現是一個TakeTurnHelper對應一個RecyclerView,能夠優化一下,改爲能夠對應多個RecyclerView,之後想起來再搞,如今懶得改了。

相關文章
相關標籤/搜索