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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值