1. 程式人生 > >RecyclerView改造成ViewPager思路

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();
}

參考文獻:

演示效果和原始碼

[實現程式碼地址]
[效果演示:體驗和ViewPager一致]