使用recycleview 实现viewpager 功能,并带有指示器。(仿高德交通路线规划实现)

最近项目中遇到了这么个需求,妈的竟然和高德地图实现一模一样的功能。因为保密性原则,我就直接上高德地图的截图了。


首先这么一步操作,输入起始点,使用recycleview 实现viewpager 功能,并带有指示器。(仿高德交通路线规划实现)

之后呢,就进入这个界面




使用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。即可。


不明白的可以咨询我。