使用recycleview 實現viewpager 功能,並帶有指示器。(仿高德交通路線規劃實現)

最近項目中遇到了這麼個需求,媽的竟然和高德地圖實現一模一樣的功能。因爲保密性原則,我就直接上高德地圖的截圖了。


首先這麼一步操作,輸入起始點,

之後呢,就進入這個界面






看到這裏,大家應該清楚我說的需求吧,好吧,不講邏輯,單單講一下這個界面 實現。因爲數據都是動態生成的,每次搜索的結果對應的list的大小和內容都是不一樣的,所以呢,我們做這個界面的時候,也需要遵循數據源定的遊戲規則,我們界面上也要做成動態的。並且是可複用的,這樣不論是在操作還是性能上,都會很好。

這個佈局主要分兩大部分,

  1,頭部的水平recycle +指示器

  2,頭部以下的分級列表

 

好,分析完大的結構,然後我們再來看一下他們的關係,頭部的滑動,會影響到下面列表內容的展示,實施更新對應的數據。

所以我們需要對recycle 的滑動做監聽,目的是有兩個:1,及時更新指示器 ; 2,及時更新分級列表內容。



下面分享一下我個人這個界面的實現過程。將Recycleview 設置成水平滑動模式,很簡單,就這麼一句話。

hLinearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(hLinearLayoutManager);
恩,這就實現了水平滑動,但是單單實現水平滑動是不夠的,滑動的距離要是一個item 的寬度才行,這樣才能像高仿的viewpager。那麼很關鍵的東西出現了。Scroller ,處理recycle滑動分頁的工具類。我們自定義的Scorller 繼承於recycleview 的onscroollIstener。

最開始是看到這位兄臺的博客得到的靈感:一行代碼讓RecyclerView變身ViewPager

因爲他沒有實現 指示器的功能,所以我在他的基礎上,把指示器添加了進來。下面是我自己的Scroller:

public class PagingScrollHelper {

    RecyclerView mRecyclerView = null;

    private RouteTopAdapter myAdapter = null;

    private MyOnScrollListener mOnScrollListener = new MyOnScrollListener();

    private MyOnFlingListener mOnFlingListener = new MyOnFlingListener();
    private int offsetY = 0;
    private int offsetX = 0;

    int startY = 0;
    int startX = 0;

    private PageIndicatorView mIndicatorView = null;

    private int index = 0;

    private int totalPage = 0;


    enum ORIENTATION {
        HORIZONTAL, VERTICAL, NULL
    }

    ORIENTATION mOrientation = ORIENTATION.HORIZONTAL;

    public void setUpRecycleView(RecyclerView recycleView) {
        if (recycleView == null) {
            throw new IllegalArgumentException("recycleView must be not null");
        }
        mRecyclerView = recycleView;
        //處理滑動
        recycleView.setOnFlingListener(mOnFlingListener);
        //設置滾動監聽,記錄滾動的狀態,和總的偏移量
        recycleView.setOnScrollListener(mOnScrollListener);
        //記錄滾動開始的位置
        recycleView.setOnTouchListener(mOnTouchListener);
        //獲取滾動的方向
        updateLayoutManger();
    }

    public void updateLayoutManger() {
        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
        if (layoutManager != null) {
            if (layoutManager.canScrollVertically()) {
                mOrientation = ORIENTATION.VERTICAL;
            } else if (layoutManager.canScrollHorizontally()) {
                mOrientation = ORIENTATION.HORIZONTAL;
            } else {
                mOrientation = ORIENTATION.NULL;
            }
            if (mAnimator != null) {
                mAnimator.cancel();
            }
            startX = 0;
            startY = 0;
            offsetX = 0;
            offsetY = 0;

        }

    }

    ValueAnimator mAnimator = null;

    public class MyOnFlingListener extends RecyclerView.OnFlingListener {

        @Override
        public boolean onFling(int velocityX, int velocityY) {
            if (mOrientation == ORIENTATION.NULL) {
                return false;
            }
            //獲取開始滾動時所在頁面的index
            int p = getStartPageIndex();

            //記錄滾動開始和結束的位置
            int endPoint = 0;
            int startPoint = 0;

            //如果是垂直方向
            if (mOrientation == ORIENTATION.VERTICAL) {
                startPoint = offsetY;

                if (velocityY < 0) {
                        p--;
                } else if (velocityY > 0) {
                        p++;

                }
                //更具不同的速度判斷需要滾動的方向
                //注意,此處有一個技巧,就是當速度爲0的時候就滾動會開始的頁面,即實現頁面復位
                endPoint = p * mRecyclerView.getHeight();

            } else {
                startPoint = offsetX;
                if (velocityX < 0) {
                        p--;
                } else if (velocityX > 0) {
                        p++;
                }
                endPoint = p * mRecyclerView.getWidth();

            }
            if (endPoint < 0) {
                endPoint = 0;
            }

            //使用動畫處理滾動
            if (mAnimator == null) {
                mAnimator = new ValueAnimator().ofInt(startPoint, endPoint);

                mAnimator.setDuration(300);
                mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int nowPoint = (int) animation.getAnimatedValue();

                        if (mOrientation == ORIENTATION.VERTICAL) {
                            int dy = nowPoint - offsetY;
                            //這裏通過RecyclerView的scrollBy方法實現滾動。
                            mRecyclerView.scrollBy(0, dy);
                        } else {
                            int dx = nowPoint - offsetX;
                            mRecyclerView.scrollBy(dx, 0);
                        }
                    }
                });
                mAnimator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        //回調監聽
                        if (null != mOnPageChangeListener) {
                            mOnPageChangeListener.onPageChange(getPageIndex());
                        }
                    }
                });
            } else {
                mAnimator.cancel();
                mAnimator.setIntValues(startPoint, endPoint);
            }

            mAnimator.start();

            return true;
        }
    }

    public class MyOnScrollListener extends RecyclerView.OnScrollListener {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            //newState==0表示滾動停止,此時需要處理回滾
            if (newState == 0 && mOrientation != ORIENTATION.NULL) {
                boolean move;
                int vX = 0, vY = 0;
                if (mOrientation == ORIENTATION.VERTICAL) {
                    int absY = Math.abs(offsetY - startY);
                    //如果滑動的距離超過屏幕的一半表示需要滑動到下一頁
                    move = absY > recyclerView.getHeight() / 2;
                    vY = 0;

                    if (move) {
                        vY = offsetY - startY < 0 ? -1000 : 1000;
                    }

                } else {
                    int absX = Math.abs(offsetX - startX);
                    move = absX > recyclerView.getWidth() / 2;
                    if (move) {
                        vX = offsetX - startX < 0 ? -1000 : 1000;
                    }

                }

                mOnFlingListener.onFling(vX, vY);

            }

        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            //滾動結束記錄滾動的偏移量
            offsetY += dy;
            offsetX += dx;
        }
    }

    private MyOnTouchListener mOnTouchListener = new MyOnTouchListener();


    public class MyOnTouchListener implements View.OnTouchListener {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            //手指按下的時候記錄開始滾動的座標
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                startY = offsetY;
                startX = offsetX;
            }
            return false;
        }

    }

    private int getPageIndex() {
        int p = 0;
        if (mOrientation == ORIENTATION.VERTICAL) {
            p = offsetY / mRecyclerView.getHeight();
        } else {
            p = offsetX / mRecyclerView.getWidth();
        }
        mIndicatorView.setSelectedPage(p);

        return p;
    }

    private int getStartPageIndex() {
        int p = 0;
        if (mOrientation == ORIENTATION.VERTICAL) {
            p = startY / mRecyclerView.getHeight();
        } else {
            p = startX / mRecyclerView.getWidth();
        }
        return p;
    }

    onPageChangeListener mOnPageChangeListener;

    public void setOnPageChangeListener(onPageChangeListener listener) {
        mOnPageChangeListener = listener;
    }

    public interface onPageChangeListener {
        void onPageChange(int index);
    }

    public void setIndicator(PageIndicatorView indicatorView) {
        this.mIndicatorView = indicatorView;
    }

    public void setAdapter(RecyclerView.Adapter adapter) {
        this.myAdapter = (RouteTopAdapter) adapter;
        update();
    }



    // 更新頁碼指示器和相關數據
    private void update() {
        int temp = myAdapter.getItemCount();
        if (temp != totalPage) {
            mIndicatorView.initIndicator(temp);
            if (temp < totalPage && index == totalPage) {
                index = temp;

            }
            mIndicatorView.setSelectedPage(index);
            totalPage = temp;
        }
    }


}
上面有詳細的註釋,這個類如果你想用的話,可以直接去copy。

這個是Indicator類的代碼

public class PageIndicatorView extends LinearLayout {

    private Context mContext = null;
    private int dotSize = 15; // 指示器的大小(dp)
    private int margins = 4; // 指示器間距(dp)
    private List<View> indicatorViews = null; // 存放指示器

    public PageIndicatorView(Context context) {
        this(context, null);
    }

    public PageIndicatorView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PageIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        this.mContext = context;

        setGravity(Gravity.CENTER);
        setOrientation(HORIZONTAL);

        dotSize = dip2px(context, dotSize);
        margins = dip2px(context, margins);
    }

    // 初始化指示器,默認選中第一頁

    public void initIndicator(int count) {

        if (indicatorViews == null) {
            indicatorViews = new ArrayList<>();
        } else {
            indicatorViews.clear();
            removeAllViews();
        }
        View view;
        LayoutParams params = new LayoutParams(dotSize, dotSize);
        params.setMargins(margins, margins, margins, margins);
        for (int i = 0; i < count; i++) {
            view = new View(mContext);
            view.setBackgroundResource(android.R.drawable.presence_invisible);
            addView(view, params);
            indicatorViews.add(view);
        }
        if (indicatorViews.size() > 0) {
            indicatorViews.get(0).setBackgroundResource(android.R.drawable.presence_offline);
        }
    }

    //設置選中頁
    public void setSelectedPage(int selected) {
        for (int i = 0; i < indicatorViews.size(); i++) {
            if (i == selected) {
                indicatorViews.get(i).setBackgroundResource(android.R.drawable.presence_offline);
            } else {
                indicatorViews.get(i).setBackgroundResource(android.R.drawable.presence_invisible);
            }
        }
    }

}

這兩個關鍵類已經給出。其他的就是自己來實例化Adapter  ,在XML佈局中添加 PagerIndicatorView控件。

綁定adapter 和 scroller。即可。


不明白的可以諮詢我。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章