RecyclerView改造成ViewPager思路

1.实现每个子Item的全屏显示

自定义一个全屏的Adapter,当Adapter创建根View的时候,强制设置根View的布局参数为MATCH_PARENT。并且覆盖掉


/**
 *  Adapters to set all of the child view to full screen
 *
 * @author lby 20/07/2017
 */
public abstract class FullScreenAdapter<M extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<M> {

    @Override
    public M onCreateViewHolder(ViewGroup parent, int viewType) {
        M viewHolder =  onCreateRawViewHolder(parent, viewType);
        if (viewHolder != null) {
            View rootView = viewHolder.itemView;
            if (rootView != null) {
                // Let root view display in full screen
                viewHolder.itemView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
            }
        }

        return viewHolder;
    }

    /**
     * create raw ViewHolder
     * @param parent The ViewGroup into which the new View will be added after it is bound to
     *               an adapter position.
     * @param viewType The view type of the new View.
     *
     * @return A new ViewHolder that holds a View of the given view type.
     */
    public abstract M onCreateRawViewHolder(ViewGroup parent, int viewType);
}

2.实现跟随手指的滑动而运动

滑动的过程中,当用户松手之后,RecyclerView默认会滑动,在RecyclerViewPager中覆盖掉fling方法,以达到松手后停止。
    /**
     * not fling when action_up event occurs
     *
     * @param velocityX
     * @param velocityY
     * @return
     */
    @Override
    public boolean fling(int velocityX, int velocityY) {
        return false;
    }

3.松手自动计算当前位置,并自动滑动到合适的position的页面

采用VelocityTracker+Scroller+Interpolator+回调函数computeScroll,根据用户滑动速度动态改变松手滑动的快慢
1.松手的时候计算需要滑动的距离
2.根据松手位置和滑动的速度和方向,计算要滑动的目标page
3.利用Scroller和松手滑动的算法,自然滑动到指定的page,滑动的效果和体验同ViewPager
主要的思路是View的弹性滑动:利用VelocityTracker追踪手指在滑动过程中的速度和Scroller类计算滑动的距离,不断的刷新,并在computeScroll回调中计算当前时刻,正在滚动的View应该处于的位置,以实现View的弹性滚动
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        ...
        mVelocityTracker.addMovement(e);
        ...
        switch(e){
            ...
            case MotionEvent.ACTION_UP:
                mLastMotionX = e.getX();
                mLastMotionY = e.getY();
                handleActionUpEvent(e);

                break;
        }
        ...

        return touchEvent;
    }


    private void handleActionUpEvent(MotionEvent e) {
        final VelocityTracker velocityTracker = mVelocityTracker;
        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
        int initialVelocity = (int) velocityTracker.getXVelocity();

        final int widthWithMargin = getWidthWithPageMargin();
        final int scrollX = mMyScrollX;
        final int currentPage = scrollX / widthWithMargin;
        final int deltaX = (int) (mLastMotionX - mInitialMotionX);

        int nextPage = currentPage;
        if (Math.abs(deltaX) > mFlingDistance && Math.abs(initialVelocity) > mMinimumVelocity) {
            nextPage = initialVelocity > 0 ? currentPage : currentPage + 1;
        }
        setCurrentItemInternal(nextPage, true, true, initialVelocity);
    }

    // switch to the specific page
    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
        if (getAdapter() == null || getAdapter().getItemCount() <= 0) {
            return;
        }

        mCurItem = item;

        dispatchOnPageSelected(item);

        // final int destX = (getWidth() + mPageMargin) * item;
        final int destX = (getWidthWithPageMargin()) * item;
        if (smoothScroll) {
            smoothScrollTo(destX, 0, velocity);
        } else {
            scrollTo(destX, 0);
            pageScrolled(destX);
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

     /**
     * Like android.view.View.scrollBy(int,int), but scroll smoothly instead of immediately.
     *
     * @param x        the number of pixels to scroll by on the X axis
     * @param y        the number of pixels to scroll by on the Y axis
     * @param velocity velocity the velocity associated with a fling, if applicable. (0 otherwise)
     */
    void smoothScrollTo(int x, int y, int velocity) {
        if (getChildCount() == 0) {
            // Nothing to do.
            return;
        }

        int sx = mMyScrollX;
        int sy = mMyScrollY;

        int dx = x - sx;
        int dy = y - sy;
        LayoutManager layoutManager = getLayoutManager();
        if ((layoutManager == null) && (!layoutManager.canScrollHorizontally())) {
            dx = 0;
        }
        if ((layoutManager == null) && (!layoutManager.canScrollVertically())) {
            dy = 0;
        }

        if (dx == 0 && dy == 0) {
            completeScroll(false);
            setScrollState(SCROLL_STATE_IDLE);
            return;
        }

        setScrollState(SCROLL_STATE_SETTLING);

        final int width = getWidth();
        final int halfWidth = width / 2;
        final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
        final float distance = halfWidth + halfWidth
                * distanceInfluenceForSnapDuration(distanceRatio);

        int duration;
        velocity = Math.abs(velocity);
        if (velocity > 0) {
            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
        } else {
            final float pageWidthWithMargin = getWidthWithPageMargin();
            final float pageDelta = (float) Math.abs(dx) / (pageWidthWithMargin);
            duration = (int) ((pageDelta + 1) * 100);
        }
        duration = Math.min(duration, MAX_SETTLE_DURATION);

        mIsStartScroller = true;
        // start scroll
        mScroller.startScroll(sx, sy, dx, dy, duration);
        ViewCompat.postInvalidateOnAnimation(this);
    }


    @Override
    public void computeScroll() {
        // !mScroller.isFinished() &&
        if (mScroller.computeScrollOffset()) {
            int oldX = mMyScrollX;
            int oldY = mMyScrollY;
            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 (mIsStartScroller) {
            // System.out.println("completeScroll is calling..");
            // Done with scroll, clean up state.
            completeScroll(true);
            mIsStartScroller = false;
        }
    }

4.实现OnPageChangeListener事件

在onTouchEvent和setCurrentItem中回调用户设置的OnPageChangeListener,以达到当用户设置滚动、状态改变、页面选中时候推送事件发生。

4.1 ViewPager中mOnPageChangeListener.onPageScrolled的调用过程

public void computeScroll() 和private boolean performDrag(float x)中回调
dispatchOnPageScrolled是mOnPageChangeListener.onPageScrolled的唯一调用入口

4.2 ViewPager中mOnPageChangeListener.onPageSelected的调用过程

public boolean onTouchEvent(MotionEvent ev) case MotionEvent.ACTION_UP:和public void setCurrentItem(int item)中回调
dispatchOnPageSelected是mOnPageChangeListener.onPageSelected的唯一调用入口

4.3 ViewPager中mOnPageChangeListener.onPageScrollStateChanged的调用过程

void smoothScrollTo(int x, int y, int velocity)和public boolean onTouchEvent(MotionEvent ev)中回调
dispatchOnPageSelected是mOnPageChangeListener.onPageSelected的唯一调用入口

5.设置当前选中Item

如果当前是第一次设置的话,则启动requestLayout,并且设置当前选中的item,在布局中重新布局requestLayout();  ===》使用标志位isFirstLayout来标记是否是第一次布局

6.setPagerMargin的实现

当用户设置pageMargin的时候,自定义PageMarginItemDecoration,让pageMargin的大小和ItemDecoration的mPageMarginWidth大小相等
然后整个RecyclerView控件的高度 + 上下分割线的总和,让控件高度超出屏幕的高度,在当前屏幕就看不到分割线了。滑动的时候,又可以看到分割线
/**
 * Helper ItemDecoration class for RecyclerViewViewPager to set PageMargin
 *
 *  @author lby 25/07/2017
 */
class PageMarginItemDecoration extends RecyclerView.ItemDecoration {

    private final int mPageMarginWidth;

    public PageMarginItemDecoration(int pageMarginWidth) {
        mPageMarginWidth = pageMarginWidth;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        // set pageMargin
        outRect.right = mPageMarginWidth;
    }
}

public void setPageMargin(int pageMargin) {
    mPageMargin = pageMargin;

    addItemDecoration(new PageMarginItemDecoration(mPageMargin));

    // reLayout
    requestLayout();
}

参考文献:

如何获取Android RecyclerView滑动的距离

如何获取 RecyclerView 的滑动距离?

RecyclerView

ViewPager

ViewPager源码

演示效果和源码

[实现代码地址]
[效果演示:体验和ViewPager一致]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值