首页 > 代码库 > 如何实现两个ViewPager的联动

如何实现两个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方法):

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private ViewPager mFollowViewPager;  
  2. public  void setFlolwViewPager(ViewPager page){  
  3. mFollowViewPager = page;  
  4. }  

我的想法是在当前ViewPager滚动的相关代码处,调用mFollowViewPager的scrollTo方法。 那么在哪里加入比较好呢,经过仔细跟踪ViewPager的行为,我发现当手指未松开的时候,performDrag方法处理相关的移动,他调用了自己的scrollTo来实现自身的平移,因此我们只需要在performDrag方法中加入如下代码:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //add by jcodecraeer  
  2. final float pageOffset =  scrollX / width;  
  3. if(mFollowViewPager!=null){  
  4. mFollowViewPager.scrollTo( (int)(pageOffset*mFollowViewPager.getWidth()), mFollowViewPager.getScrollY());  
  5. }  

 

注意,并不是主ViewPager移动了多远,mFollowViewPager就移动多远,因为两个ViewPager的宽度可能不一样,所以需要转换一下,上面的代码中final float pageOffset =  scrollX / width;pageOffset
就只转换得到的值。

改写后的performDrag如下:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1.   private boolean performDrag(float x) {  
  2.       boolean needsInvalidate = false;  
  3.   
  4.       final float deltaX = mLastMotionX - x;  
  5.       mLastMotionX = x;  
  6.   
  7.         
  8.       float oldScrollX = getScrollX();  
  9.       float scrollX = oldScrollX + deltaX;  
  10.       final int width = getWidth();  
  11.   
  12.       float leftBound = width * mFirstOffset;  
  13.       float rightBound = width * mLastOffset;  
  14.       boolean leftAbsolute = true;  
  15.       boolean rightAbsolute = true;  
  16.   
  17.       final ItemInfo firstItem = mItems.get(0);  
  18.       final ItemInfo lastItem = mItems.get(mItems.size() - 1);  
  19.       if (firstItem.position != 0) {  
  20.           leftAbsolute = false;  
  21.           leftBound = firstItem.offset * width;  
  22.       }  
  23.       if (lastItem.position != mAdapter.getCount() - 1) {  
  24.           rightAbsolute = false;  
  25.           rightBound = lastItem.offset * width;  
  26.       }  
  27.   
  28.       if (scrollX leftBound) {  
  29.           if (leftAbsolute) {  
  30.               float over = leftBound - scrollX;  
  31.               needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width);  
  32.           }  
  33.           scrollX = leftBound;  
  34.       } else if (scrollX > rightBound) {  
  35.           if (rightAbsolute) {  
  36.               float over = scrollX - rightBound;  
  37.               needsInvalidate = mRightEdge.onPull(Math.abs(over) / width);  
  38.           }  
  39.           scrollX = rightBound;  
  40.       }  
  41.       // Don‘t lose the rounded component  
  42.       mLastMotionX += scrollX - (int) scrollX;  
  43.       scrollTo((int) scrollX, getScrollY());  
  44.       pageScrolled((int) scrollX);  
  45.       //add by jcodecraeer  
  46. final float pageOffset =  scrollX / width;  
  47. if(mFollowViewPager!=null){  
  48.     mFollowViewPager.scrollTo( (int)(pageOffset*mFollowViewPager.getWidth()), mFollowViewPager.getScrollY());     
  49. }  
  50.       return needsInvalidate;  
  51.   }  

 

光处理了手指未离开屏幕阶段的平移还不够,手指松开了,ViewPager还会自己继续一定一段距离,因此mFollowViewPager也应该跟着移动,我们想下,手指松开是不是该在  case MotionEvent.ACTION_UP中处理的呢?

我们找到相关代码:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. case MotionEvent.ACTION_UP:  
  2.     if (mIsBeingDragged) {  
  3.         final VelocityTracker velocityTracker = mVelocityTracker;  
  4.         velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);  
  5.         int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(  
  6.                 velocityTracker, mActivePointerId);  
  7.         mPopulatePending = true;  
  8.         final int width = getWidth();  
  9.         final int scrollX = getScrollX();  
  10.         final ItemInfo ii = infoForCurrentScrollPosition();  
  11.         final int currentPage = ii.position;  
  12.         final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;  
  13.         final int activePointerIndex =  
  14.                 MotionEventCompat.findPointerIndex(ev, mActivePointerId);  
  15.         final float x = MotionEventCompat.getX(ev, activePointerIndex);  
  16.         final int totalDelta = (int) (x - mInitialMotionX);  
  17.         int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,  
  18.                 totalDelta);  
  19.         setCurrentItemInternal(nextPage, true, true, initialVelocity);  
  20.                                                                
  21.         mActivePointerId = INVALID_POINTER;  
  22.         endDrag();  
  23.         needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();  
  24.     }  

可以看到setCurrentItemInternal调用了scrollToItem(item, smoothScroll, velocity, dispatchSelected);来实现手指松开后的继续平移效果。也就是说对于mFollowViewPager,如果我们也同样调用setCurrentItemInternal就可以使他也跟着移动了。照着这个思路我们改写case MotionEvent.ACTION_UP的代码段:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. case MotionEvent.ACTION_UP:  
  2.     if (mIsBeingDragged) {  
  3.         final VelocityTracker velocityTracker = mVelocityTracker;  
  4.         velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);  
  5.         int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(  
  6.                 velocityTracker, mActivePointerId);  
  7.         mPopulatePending = true;  
  8.         final int width = getWidth();  
  9.         final int scrollX = getScrollX();  
  10.         final ItemInfo ii = infoForCurrentScrollPosition();  
  11.         final int currentPage = ii.position;  
  12.         final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;  
  13.         final int activePointerIndex =  
  14.                 MotionEventCompat.findPointerIndex(ev, mActivePointerId);  
  15.         final float x = MotionEventCompat.getX(ev, activePointerIndex);  
  16.         final int totalDelta = (int) (x - mInitialMotionX);  
  17.         int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,  
  18.                 totalDelta);  
  19.         setCurrentItemInternal(nextPage, true, true, initialVelocity);  
  20.         //add by jcodecraeer  
  21.         if(mFollowViewPager!=null){  
  22.             mFollowViewPager.setCurrentItemInternal(nextPage, true, true, initialVelocity);  
  23.         }  
  24.         mActivePointerId = INVALID_POINTER;  
  25.         endDrag();  
  26.         needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();  
  27.     }  

至此,我们完成了所有的修改,其实也没改几行。

那么在activity中如何使用改造后的ViewPager让两个ViewPager联动呢?假设有一个是mViewPager,有一个是mFollowViewPager,我想让mFollowViewPager随着mViewPager动,则:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. mPager.setFollowViewPager(mFollowViewPager);  


需要注意的是在我接下来给出的demo中,我屏蔽了followViewPager的所有触摸事件,让主ViewPager覆盖在followViewPager之上,这跟我要实现的效果稳合的。如果你要让followViewPager也能反过来使主ViewPager也能跟着移动不妨反过来调用:

mFollowViewPager.setFollowViewPager(mPager);

但是我不确定这种双向调用是否会出现问题,因为我并没有很严格的考虑从mFollowViewPager变量在移动过后本应该导致的一些状态变化(比如相关的变量)。读者可以试一试,然后改进。

如何实现两个ViewPager的联动