首页 > 代码库 > 如何实现两个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方法):
- 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中,我屏蔽了
的所有触摸事件,让主ViewPager覆盖在followViewPager
followViewPager之上,这跟我要实现的效果稳合的。如果你要让
也能反过来使主ViewPager也能跟着移动不妨反过来调用:followViewPager
mFollowViewPager.setFollowViewPager(mPager);
但是我不确定这种双向调用是否会出现问题,因为我并没有很严格的考虑从mFollowViewPager
变量在移动过后本应该导致的一些状态变化(比如相关的变量)。读者可以试一试,然后改进。
如何实现两个ViewPager的联动