1. 程式人生 > >如何實現兩個ViewPager的聯動

如何實現兩個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;
}
我的想法是在當前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呼叫了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 標註(不包括為了刪除的那些不必要的程式碼)