如何實現兩個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方法):
我的想法是在當前ViewPager滾動的相關程式碼處,呼叫private ViewPager mFollowViewPager; public void setFlolwViewPager(ViewPager page){ mFollowViewPager = page; }
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
變數在移動過後本應該導致的一些狀態變化(比如相關的變數)。讀者可以試一試,然後改進。