自定义ViewPager实现轮播效果

您也可以点此浏览,阅读体验更佳

前言

这种轮播效果多应用于展示电影海报,但是效果并不好,有些没有手势动画,更加没有fling效果。我将ViewPager的源码拷贝出来,做了修改,实现了这两个效果。

效果图

此处输入图片的描述

原理

  1. 一屏显示三个page,让中间一个page居中突出显示
    首先我们可以设置一个page宽为ViewPager宽的一定比例,比如 0.7。然后,ViewPager在两个page之间切换是通过设置scrollX实现的(你可以简单把ViewPager想象为一个HorizontalScrollView
    )。但是ViewPager原本的逻辑是让当前的page靠ViewPager的左边缘显示的。所以我们需要把修改这部分的逻辑,把scrollX加上一个负偏移,让中间一个page居中。见第一个代码段。
  2. 手势动画
    手势动画是通过监听滑动状态,并改变相关视图的变换矩阵实现的。因为你的page不再充满ViewPager,也不再靠ViewPager左边缘显示,所以处理起来比较麻烦,要分段处理。见第二个代码段。
  3. fling效果
    ViewPager原本的逻辑是手指抬起的时候,根据移动距离和速度去判断是否翻页或退页,没有fling。我的做法在手指抬起时,判断速度,如果速度小于一定阈值按ViewPager原先的处理方式处理,否则,使用Scroller工具类进行fling,在fling结束后调整位置,选中特定的page。见第三个代码段。

Demo

Github

源码(我修改的ViewPager部分)

private void scrollToItem(int item, boolean smoothScroll, int velocity,
                              boolean dispatchSelected) {
        final ItemInfo curInfo = infoForPosition(item);
        int destX = 0;
        if (curInfo != null) {
            final int width = getClientWidth();
            destX = (int) (width * Math.max(mFirstOffset,
                    Math.min(curInfo.offset, mLastOffset)));
        }
        //我的修改
        if (item != 0 && item != mAdapter.getCount() - 2 && item != mAdapter.getCount() - 1) {
            destX = destX - itemScrollOffset;
        }
/*
        if (item == mAdapter.getCount() - 1) {
            destX = destX + itemScrollOffset;
        } else {
            destX = destX - itemScrollOffset;
        }
*/
        if (smoothScroll) {
            smoothScrollTo(destX, 0, velocity);
            if (dispatchSelected) {
                dispatchOnPageSelected(item);
            }
        } else {
            if (dispatchSelected) {
                dispatchOnPageSelected(item);
            }
            completeScroll(false);
            scrollTo(destX, 0);
            pageScrolled(destX);
        }
    }
        //这个逻辑比较复杂,总的来说是通过第一个page的相对窗口的偏移去计算当前可见的三个page的缩放比例,这个计算过程比较复杂
        //这跟你的page占屏幕宽度的比例,后台page缩放多少有关。
        viewPager.addOnPageChangeListener(new MultiCardViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//                Log.e("", position + "  " + positionOffset);
                if (position == 0 && positionOffset == 0) {
                    return;
                }

                View leftPage = viewPager.findViewWithTag(position);
                View middlePage = viewPager.findViewWithTag(position + 1);
                View rightPage = viewPager.findViewWithTag(position + 2);

                if (position == 0) {
                    if (middlePage != null) {
                        float scale = 1 - positionOffset * frontPageLeftOffset * frontBackScaleDelta;
                        setScale(middlePage, scale);
                        setDim(middlePage, scale);
                    }
                    if (rightPage != null) {
                        float scale = backgroundPageScale + positionOffset * frontPageLeftOffset * frontBackScaleDelta;
                        setScale(rightPage, scale);
                        setDim(rightPage, scale);
                    }
                    return;
                }

                if (leftPage != null) {
                    if (positionOffset < pageGoToBackgroundOffsetThreshold) {
                        //become smaller
                        float scale = 1 - (positionOffset + frontPageLeftOffset) * frontBackScaleDelta;
                        setScale(leftPage, scale);
                        setDim(leftPage, scale);
                    } else {
                        //stay still
                        setScale(leftPage, backgroundPageScale);
                        setDim(leftPage, backgroundPageScale);
                    }
                }

                if (middlePage != null) {
                    if (positionOffset < pageGoToBackgroundOffsetThreshold) {
                        //become bigger
                        float scale = backgroundPageScale + (positionOffset + frontPageLeftOffset) * frontBackScaleDelta;
                        setScale(middlePage, scale);
                        setDim(middlePage, scale);
                    } else {
                        //become smaller
                        float scale = 1 - (positionOffset - pageGoToBackgroundOffsetThreshold) * frontBackScaleDelta;
                        setScale(middlePage, scale);
                        setDim(middlePage, scale);
                    }
                }

                if (rightPage != null) {
                    if (positionOffset < pageGoToBackgroundOffsetThreshold) {
                        //stay still
                        setScale(rightPage, backgroundPageScale);
                        setDim(rightPage, backgroundPageScale);
                    } else {
                        //become bigger
                        float scale = backgroundPageScale + (positionOffset - pageGoToBackgroundOffsetThreshold) * frontBackScaleDelta;
                        setScale(rightPage, scale);
                        setDim(rightPage, scale);
                    }
                }

            }

            @Override
            public void onPageSelected(int position) {
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mFakeDragging) {
            // A fake drag is in progress already, ignore this real one
            // but still eat the touch events.
            // (It is likely that the user is multi-touching the screen.)
            return true;
        }

        if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
            // Don't handle edge touches immediately -- they may actually belong to one of our
            // descendants.
            return false;
        }

        if (mAdapter == null || mAdapter.getCount() == 0) {
            // Nothing to present or scroll; nothing to touch.
            return false;
        }

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        final int action = ev.getAction();
        boolean needsInvalidate = false;

        switch (action & MotionEventCompat.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: {
                mScroller.abortAnimation();
                mPopulatePending = false;
                populate();

                // Remember where the motion event started
                mLastMotionX = mInitialMotionX = ev.getX();
                mLastMotionY = mInitialMotionY = ev.getY();
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                break;
            }
            case MotionEvent.ACTION_MOVE:
                if (!mIsBeingDragged) {
                    final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                    final float x = MotionEventCompat.getX(ev, pointerIndex);
                    final float xDiff = Math.abs(x - mLastMotionX);
                    final float y = MotionEventCompat.getY(ev, pointerIndex);
                    final float yDiff = Math.abs(y - mLastMotionY);
                    if (DEBUG)
                        Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
                    if (xDiff > mTouchSlop && xDiff > yDiff) {
                        if (DEBUG) Log.v(TAG, "Starting drag!");
                        mIsBeingDragged = true;
                        requestParentDisallowInterceptTouchEvent(true);
                        mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
                                mInitialMotionX - mTouchSlop;
                        mLastMotionY = y;
                        setScrollState(SCROLL_STATE_DRAGGING);
                        setScrollingCacheEnabled(true);

                        // Disallow Parent Intercept, just in case
                        ViewParent parent = getParent();
                        if (parent != null) {
                            parent.requestDisallowInterceptTouchEvent(true);
                        }
                    }
                }
                // Not else! Note that mIsBeingDragged can be set above.
                if (mIsBeingDragged) {
                    // Scroll to follow the motion event
                    final int activePointerIndex = MotionEventCompat.findPointerIndex(
                            ev, mActivePointerId);
                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
                    needsInvalidate |= performDrag(x);
                }
                break;
            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 = getClientWidth();
                    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);

                    //我的修改
                    if (Math.abs(initialVelocity) < 4000) {
                        int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
                                totalDelta);
                        setCurrentItemInternal(nextPage, true, true, initialVelocity);
                    } else {
                        fling = true;
//                        Log.e("Velocity", initialVelocity + "");
                        mScroller.fling(getScrollX(), getScrollY(), -initialVelocity, 0, minScrollX, maxScrollX, getScrollY(), getScrollY());
                    }

                    mActivePointerId = INVALID_POINTER;
                    endDrag();
                    needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                if (mIsBeingDragged) {
                    scrollToItem(mCurItem, true, 0, false);
                    mActivePointerId = INVALID_POINTER;
                    endDrag();
                    needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
                }
                break;
            case MotionEventCompat.ACTION_POINTER_DOWN: {
                final int index = MotionEventCompat.getActionIndex(ev);
                final float x = MotionEventCompat.getX(ev, index);
                mLastMotionX = x;
                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
                break;
            }
            case MotionEventCompat.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                mLastMotionX = MotionEventCompat.getX(ev,
                        MotionEventCompat.findPointerIndex(ev, mActivePointerId));
                break;
        }
        if (needsInvalidate) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
        return true;
    }
@Override
    public void computeScroll() {
        if (!myScroller.isFinished() && myScroller.computeScrollOffset()) {
            int oldX = getScrollX();
            int oldY = getScrollY();
            int x = myScroller.getCurrX();
            int y = myScroller.getCurrY();

            if (oldX != x || oldY != y) {
                scrollTo(x, y);
                if (!pageScrolled(x)) {
                    myScroller.abortAnimation();
                    scrollTo(0, y);
                }
            }

            // Keep on drawing until the animation has finished.
            ViewCompat.postInvalidateOnAnimation(this);
            return;
        }

        if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
            int oldX = getScrollX();
            int oldY = getScrollY();
            int x = mScroller.getCurrX();
            int y = mScroller.getCurrY();

            if (oldX != x || oldY != y) {
                scrollTo(x, y);
                if (!pageScrolled(x)) {
                    mScroller.abortAnimation();
                    scrollTo(0, y);
                }
            }

            // Keep on drawing until the animation has finished.
            ViewCompat.postInvalidateOnAnimation(this);
            return;
        }

        //我的修改
        if (fling) {
            ItemInfo curInfo = infoForCurrentScrollPosition();
            if (getScrollX() > (curInfo.offset + 0.225) * getWidth()) {
                if (curInfo.position + 1 < mAdapter.getCount()) {
                    curInfo = infoForPosition(curInfo.position + 1);
                }
            }
//        final ItemInfo curInfo = infoForPosition(item);
            int destX = 0;

            if (curInfo != null) {
                final int width = getClientWidth();
                destX = (int) (width * Math.max(mFirstOffset,
                        Math.min(curInfo.offset, mLastOffset)));
            }
            if (curInfo.position != 0 && curInfo.position != mAdapter.getCount() - 2 && curInfo.position != mAdapter.getCount() - 1) {
                destX = destX - itemScrollOffset;
            }
//            smoothScrollTo(destX, 0, 100);
            mScroller.startScroll(getScrollX(), getScrollY(), destX - getScrollX(), 0, 600);
            ViewCompat.postInvalidateOnAnimation(this);
            dispatchOnPageSelected(curInfo.position);
            fling = false;
            return;
        }


//                scrollToItem(itemInfo.position, true, 100, true);
//                setCurrentItemInternal(itemInfo.position, false, true, 100);
//                setCurrentItem(itemInfo.position, true);
//        fling = false;

        // Done with scroll, clean up state.
        completeScroll(true);
    }

猜你喜欢

转载自blog.csdn.net/fly1183989782/article/details/48086405