android開發之兩個ViewPager聯動

以前寫過一篇文章,講的是如何實現zaker5.0的引導界面效果,見 仿zaker最新版本引導界面的視圖聯動效果(修改viewpager實現) ,沒有寫完就了事了,這篇文章算是對那篇的繼續。

先來看看最終效果:
這裏寫圖片描述

聯動ViewPager的意思就是當一個viewpager在滑動的時候,另外一個ViewPager也跟着滑動,而且兩者是同步的。

如果ViewPager有關於移動距離的回調接口,這事兒就好辦了,遺憾的是沒有,只有一個OnPageChangeListener,我試過在OnPageChangeListener中根據onPageScrolled(int position, float positionOffset, int positionOffsetPixels)的參數來做,但是失敗了。

那就只有自定義ViewPager了。

我直接將ViewPager的源碼衝v4中拿出來,去掉不必要的一些東西,直到不會再出現找不到類爲止,

除了需要將ViewPager拿出來之外,還需要把相關的PagerAdapter類也拿出來,不然ViewPager使用的是自己的而adapter用的是v4中的,可能會出問題。

爲了實現聯動,在ViewPager中增加一個private變量mFollowViewPager(同時增加變量的set方法):

private ViewPager mFollowViewPager;
    public  void setFlolwViewPager(ViewPager page){
    mFollowViewPager = page;
}

mFollowViewPager表示的是隨着當前ViewPager滾動的另一個ViewPager。

我的想法是在當前ViewPager滾動的相關代碼處,調用mFollowViewPager的scrollTo方法。 那麼在哪裏加入比較好呢,經過仔細跟蹤ViewPager的行爲,我發現當手指未鬆開的時候,performDrag方法處理相關的移動,他調用了自己的scrollTo來實現自身的平移,因此我們只需要在performDrag方法中加入如下代碼:

//add by jcodecraeer
final float pageOffset =  scrollX / width;
    if(mFollowViewPager!=null){
        mFollowViewPager.scrollTo( (int)(pageOffset*mFollowViewPager.getWidth()), mFollowViewPager.getScrollY());
}

注意,並不是主ViewPager移動了多遠,mFollowViewPager就移動多遠,因爲兩個ViewPager的寬度可能不一樣,所以需要轉換一下,上面的代碼中final float pageOffset = scrollX / width;pageOffset
就只轉換得到的值。

改寫後的performDrag如下:

private boolean performDrag(float x) {
    boolean needsInvalidate = false;
    final float deltaX = mLastMotionX - x;
    mLastMotionX = x;

    float oldScrollX = getScrollX();
    float scrollX = oldScrollX + deltaX;
    final int width = getWidth();
    float leftBound = width * mFirstOffset;
    float rightBound = width * mLastOffset;
    boolean leftAbsolute = true;
    boolean rightAbsolute = true;
    final ItemInfo firstItem = mItems.get(0);
    final ItemInfo lastItem = mItems.get(mItems.size() - 1);
    if (firstItem.position != 0) {
        leftAbsolute = false;
        leftBound = firstItem.offset * width;
    }
    if (lastItem.position != mAdapter.getCount() - 1) {
        rightAbsolute = false;
        rightBound = lastItem.offset * width;
    }
    if (scrollX < leftBound) {
        if (leftAbsolute) {
            float over = leftBound - scrollX;
            needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width);
        }
        scrollX = leftBound;
    } else if (scrollX > rightBound) {
        if (rightAbsolute) {
            float over = scrollX - rightBound;
            needsInvalidate = mRightEdge.onPull(Math.abs(over) / width);
        }
        scrollX = rightBound;
    }
    // Don't lose the rounded component
    mLastMotionX += scrollX - (int) scrollX;
    scrollTo((int) scrollX, getScrollY());
    pageScrolled((int) scrollX);
    //add by jcodecraeer
    final float pageOffset =  scrollX / width;
    if(mFollowViewPager!=null){
        mFollowViewPager.scrollTo( (int)(pageOffset*mFollowViewPager.getWidth()), mFollowViewPager.getScrollY());  
    }
    return needsInvalidate;
}

光處理了手指未離開屏幕階段的平移還不夠,手指鬆開了,ViewPager還會自己繼續一定一段距離,因此mFollowViewPager也應該跟着移動,我們想下,手指鬆開是不是該在 case MotionEvent.ACTION_UP中處理的呢?

我們找到相關代碼:

case MotionEvent.ACTION_UP:
    if (mIsBeingDragged) {
        final VelocityTracker velocityTracker = mVelocityTracker;
        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
        int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
                velocityTracker, mActivePointerId);
        mPopulatePending = true;
        final int width = getWidth();
        final int scrollX = getScrollX();
        final ItemInfo ii = infoForCurrentScrollPosition();
        final int currentPage = ii.position;
        final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
        final int activePointerIndex =
                MotionEventCompat.findPointerIndex(ev, mActivePointerId);
        final float x = MotionEventCompat.getX(ev, activePointerIndex);
        final int totalDelta = (int) (x - mInitialMotionX);
        int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
                totalDelta);
        setCurrentItemInternal(nextPage, true, true, initialVelocity);

        mActivePointerId = INVALID_POINTER;
        endDrag();
        needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
    }

其中,setCurrentItemInternal(nextPage, true, true, initialVelocity)是關鍵,他的代碼如下:

void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
    if (mAdapter == null || mAdapter.getCount() <= 0) {
        setScrollingCacheEnabled(false);
        return;
    }
    if (!always && mCurItem == item && mItems.size() != 0) {
        setScrollingCacheEnabled(false);
        return;
    }
    if (item < 0) {
        item = 0;
    } else if (item >= mAdapter.getCount()) {
        item = mAdapter.getCount() - 1;
    }
    final int pageLimit = mOffscreenPageLimit;
    if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
        // We are doing a jump by more than one page.  To avoid
        // glitches, we want to keep all current pages in the view
        // until the scroll ends.
        for (int i=0; i<mItems.size(); i++) {
            mItems.get(i).scrolling = true;
        }
    }
    final boolean dispatchSelected = mCurItem != item;
    populate(item);
    scrollToItem(item, smoothScroll, velocity, dispatchSelected);
}

可以看到setCurrentItemInternal中調用了scrollToItem(item, smoothScroll, velocity, dispatchSelected);來實現手指鬆開後的繼續平移效果。也就是說對於mFollowViewPager,如果我們也同樣調用setCurrentItemInternal就可以使他也跟着移動了。照着這個思路我們改寫case MotionEvent.ACTION_UP的代碼段:

case MotionEvent.ACTION_UP:
    if (mIsBeingDragged) {
        final VelocityTracker velocityTracker = mVelocityTracker;
        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
        int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
                velocityTracker, mActivePointerId);
        mPopulatePending = true;
        final int width = getWidth();
        final int scrollX = getScrollX();
        final ItemInfo ii = infoForCurrentScrollPosition();
        final int currentPage = ii.position;
        final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
        final int activePointerIndex =
                MotionEventCompat.findPointerIndex(ev, mActivePointerId);
        final float x = MotionEventCompat.getX(ev, activePointerIndex);
        final int totalDelta = (int) (x - mInitialMotionX);
        int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
                totalDelta);
        setCurrentItemInternal(nextPage, true, true, initialVelocity);
        //add by jcodecraeer
        if(mFollowViewPager!=null){
            mFollowViewPager.setCurrentItemInternal(nextPage, true, true, initialVelocity);
        }
        mActivePointerId = INVALID_POINTER;
        endDrag();
        needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
    }

至此,我們完成了所有的修改,其實也沒改幾行。

那麼在activity中如何使用改造後的ViewPager讓兩個ViewPager聯動呢?假設有一個是mViewPager,有一個是mFollowViewPager,我想讓mFollowViewPager隨着mViewPager動,則:

mPager.setFollowViewPager(mFollowViewPager);

需要注意的是在我接下來給出的demo中,我屏蔽了followViewPager的所有觸摸事件,讓主ViewPager覆蓋在followViewPager之上,這跟我要實現的效果穩合的。如果你要讓followViewPager也能反過來使主ViewPager也能跟着移動不妨反過來調用:

mFollowViewPager.setFollowViewPager(mPager);

但是我不確定這種雙向調用是否會出現問題,因爲我並沒有很嚴格的考慮從mFollowViewPager變量在移動過後本應該導致的一些狀態變化(比如相關的變量)。讀者可以試一試,然後改進。

關於ViewPager被改造的地方都用add by jcodecraeer 標註(不包括爲了刪除的那些不必要的代碼)

示例代碼戳Here

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章