横向滑动分页的recycleview

为了实现下图中的效果


描述一下需求:

1.根据后台返回的菜单数量,每页展示10条  不够10条也要占用一页  比如20条是两页,21条是3页

2.可以左右滑动,下方指示器跟着左右动

下面开始表演

1.首先自定义一个Helper,用于分页

public class PagingScrollHelper {

    RecyclerView mRecyclerView= 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;

    enum ORIENTATION {

        HORIZONTAL, VERTICAL, NULL

    }

    private 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;

}

}

    /**

* 获取总共的页数

*/

    public int getPageCount() {

        if (mRecyclerView!= null) {

            if (mOrientation== ORIENTATION.NULL) {

                return 0;

}

            if (mOrientation== ORIENTATION.VERTICAL && mRecyclerView.computeVerticalScrollExtent() != 0) {

                return mRecyclerView.computeVerticalScrollRange() / mRecyclerView.computeVerticalScrollExtent();

            } else if (mRecyclerView.computeHorizontalScrollExtent() != 0) {

                return mRecyclerView.computeHorizontalScrollRange() / mRecyclerView.computeHorizontalScrollExtent();

}

}

        return 0;

}

    ValueAnimator mAnimator= null;

    public void scrollToPosition(int position) {

        if (mAnimator== null) {

            mOnFlingListener.onFling(0, 0);

}

        if (mAnimator!= null) {

            int startPoint= mOrientation== ORIENTATION.VERTICAL ? offsetY: offsetX, endPoint= 0;

            if (mOrientation== ORIENTATION.VERTICAL) {

                endPoint= mRecyclerView.getHeight() * position;

            } else {

                endPoint= mRecyclerView.getWidth() * position;

}

            if (startPoint!= endPoint) {

                mAnimator.setIntValues(startPoint, endPoint);

                mAnimator.start();

}

}

}

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

}

                        //修复双击item bug

                        mRecyclerView.stopScroll();

                        startY= offsetY;

                        startX= offsetX;

}

});

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

    private boolean firstTouch= true;

    public class MyOnTouchListener implements View.OnTouchListener {

        @Override

        public boolean onTouch(View v, MotionEvent event) {

            //手指按下的时候记录开始滚动的坐标

            if (firstTouch) {

                //第一次touch可能是ACTION_MOVE或ACTION_DOWN,所以使用这种方式判断

                firstTouch= false;

                startY= offsetY;

                startX= offsetX;

}

            if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {

                firstTouch= true;

}

            return false;

}

}

    private int getPageIndex() {

        int p= 0;

        if (mRecyclerView.getHeight() == 0 || mRecyclerView.getWidth() == 0) {

            return p;

}

        if (mOrientation== ORIENTATION.VERTICAL) {

            p= offsetY/ mRecyclerView.getHeight();

        } else {

            p= offsetX/ mRecyclerView.getWidth();

}

        return p;

}

    private int getStartPageIndex() {

        int p= 0;

        if (mRecyclerView.getHeight() == 0 || mRecyclerView.getWidth() == 0) {

            //没有宽高无法处理

            return p;

}

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

}

}

2.定义一个接口(用于我们自定义LayoutMAnager)

public interface PageDecorationLastJudge {

    boolean isLastRow(int position);

    boolean isLastColumn(int position);

    boolean isPageLast(int position);

}

3.自定义LayoutManager

public class HorizontalPageLayoutManager extends RecyclerView.LayoutManager implements PageDecorationLastJudge {

    @Override

    public RecyclerView.LayoutParams generateDefaultLayoutParams() {

        return null;

}

    int totalHeight= 0;

    int totalWidth= 0;

    int offsetY= 0;

    int offsetX= 0;

    public HorizontalPageLayoutManager(int rows, int columns) {

        this.rows= rows;

        this.columns= columns;

        this.onePageSize= rows * columns;

}

    @Override

    public boolean canScrollHorizontally() {

        return true;

}

    @Override

    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {

        detachAndScrapAttachedViews(recycler);

        int newX= offsetX+ dx;

        int result= dx;

        if (newX> totalWidth) {

            result= totalWidth- offsetX;

        } else if (newX< 0) {

            result= 0 - offsetX;

}

        offsetX+= result;

        offsetChildrenHorizontal(-result);

        recycleAndFillItems(recycler, state);

        return result;

}

    private SparseArray<Rect> allItemFrames= new SparseArray<>();

    private int getUsableWidth() {

        return getWidth() - getPaddingLeft() - getPaddingRight();

}

    private int getUsableHeight() {

        return getHeight() - getPaddingTop() - getPaddingBottom();

}

    int rows= 0;

    int columns= 0;

    int pageSize= 0;

    int itemWidth= 0;

    int itemHeight= 0;

    int onePageSize= 0;

    int itemWidthUsed;

    int itemHeightUsed;

    @Override

    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {

        if (getItemCount() == 0) {

            removeAndRecycleAllViews(recycler);

            return;

}

        if (state.isPreLayout()) {

            return;

}

        //获取每个Item的平均宽高

        itemWidth= getUsableWidth() / columns;

        itemHeight= getUsableHeight() / rows;

        //计算宽高已经使用的量,主要用于后期测量

        itemWidthUsed= (columns- 1) * itemWidth;

        itemHeightUsed= (rows- 1) * itemHeight;

        //计算总的页数

//        pageSize = state.getItemCount() / onePageSize + (state.getItemCount() % onePageSize == 0 ? 0 : 1);

        computePageSize(state);

        Log.i("zzz", "itemCount=" + getItemCount() + " state itemCount=" + state.getItemCount() + " pageSize=" + pageSize);

        //计算可以横向滚动的最大值

        totalWidth= (pageSize- 1) * getWidth();

        //分离view

        detachAndScrapAttachedViews(recycler);

        int count= getItemCount();

        for (int p= 0; p< pageSize; p++) {

            for (int r= 0; r< rows; r++) {

                for (int c= 0; c< columns; c++) {

                    int index= p* onePageSize+ r* columns+ c;

                    if (index== count) {

                        //跳出多重循环

                        c= columns;

                        r= rows;

                        p= pageSize;

                        break;

}

                    View view= recycler.getViewForPosition(index);

                    addView(view);

                    //测量item

                    measureChildWithMargins(view, itemWidthUsed, itemHeightUsed);

                    int width= getDecoratedMeasuredWidth(view);

                    int height= getDecoratedMeasuredHeight(view);

                    //记录显示范围

                    Rect rect= allItemFrames.get(index);

                    if (rect== null) {

                        rect= new Rect();

}

                    int x= p* getUsableWidth() + c* itemWidth;

                    int y= r* itemHeight;

                    rect.set(x, y, width+ x, height+ y);

                    allItemFrames.put(index, rect);

}

}

            //每一页循环以后就回收一页的View用于下一页的使用

            removeAndRecycleAllViews(recycler);

}

        recycleAndFillItems(recycler, state);

}

    private void computePageSize(RecyclerView.State state) {

        pageSize= state.getItemCount() / onePageSize+ (state.getItemCount() % onePageSize== 0 ? 0 : 1);

}

    @Override

    public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {

        super.onDetachedFromWindow(view, recycler);

        offsetX= 0;

        offsetY= 0;

}

    private void recycleAndFillItems(RecyclerView.Recycler recycler, RecyclerView.State state) {

        if (state.isPreLayout()) {

            return;

}

        Rect displayRect= new Rect(getPaddingLeft() + offsetX, getPaddingTop(), getWidth() - getPaddingLeft() - getPaddingRight() + offsetX, getHeight() - getPaddingTop() - getPaddingBottom());

        Rect childRect= new Rect();

        for (int i= 0; i< getChildCount(); i++) {

            View child= getChildAt(i);

            childRect.left= getDecoratedLeft(child);

            childRect.top= getDecoratedTop(child);

            childRect.right= getDecoratedRight(child);

            childRect.bottom= getDecoratedBottom(child);

            if (!Rect.intersects(displayRect, childRect)) {

                removeAndRecycleView(child, recycler);

}

}

        for (int i= 0; i< getItemCount(); i++) {

            if (Rect.intersects(displayRect, allItemFrames.get(i))) {

                View view= recycler.getViewForPosition(i);

                addView(view);

                measureChildWithMargins(view, itemWidthUsed, itemHeightUsed);

                Rect rect= allItemFrames.get(i);

                layoutDecorated(view, rect.left- offsetX, rect.top, rect.right- offsetX, rect.bottom);

}

}

}

    @Override

    public boolean isLastRow(int index) {

        if (index >= 0 && index < getItemCount()) {

            int indexOfPage= index % onePageSize;

            indexOfPage++;

            if (indexOfPage> (rows- 1) * columns&& indexOfPage<= onePageSize) {

                return true;

}

}

        return false;

}

    @Override

    public boolean isLastColumn(int position) {

        if (position >= 0 && position < getItemCount()) {

            position++;

            if (position % columns== 0) {

                return true;

}

}

        return false;

}

    @Override

    public boolean isPageLast(int position) {

        position++;

        return position % onePageSize== 0;

}

    @Override

    public int computeHorizontalScrollRange(RecyclerView.State state) {

        computePageSize(state);

        return pageSize* getWidth();

}

    @Override

    public int computeHorizontalScrollOffset(RecyclerView.State state) {

        return offsetX;

}

    @Override

    public int computeHorizontalScrollExtent(RecyclerView.State state) {

        return getWidth();

}

}

4.下面开始调用

4.1首先是布局  

//RecyclerView  要定义高度

<androidx.recyclerview.widget.RecyclerView

    android:id="@+id/rv_menu"

    android:layout_marginLeft="@dimen/dp_12"

    android:layout_width="match_parent"

    android:layout_height="@dimen/dp_150"

    android:layout_marginTop="@dimen/dp_12" />

//指示器

<LinearLayout

    android:id="@+id/indicator_layout"

    android:layout_marginTop="@dimen/dp_8"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:layout_marginHorizontal="5dp"

    android:layout_marginBottom="@dimen/dp_10"

    android:gravity="center_horizontal"

    android:orientation="horizontal" />

4.2然后造数据

mItemBeans= new ArrayList<>();

for (int i= 0; i< 28; i++) {

    if (i== 28) {

        MenuBean.ItemBean itemBean= new MenuBean.ItemBean();

        itemBean.setTitle("全部");

        mItemBeans.add(itemBean);

    } else {

        MenuBean.ItemBean itemBean= new MenuBean.ItemBean();

        itemBean.setTitle("菜单" + i);

        mItemBeans.add(itemBean);

}

}

4.3 调用

PagingScrollHelper scrollHelper= new PagingScrollHelper();//初始化横向管理器

HorizontalPageLayoutManager horizontalPageLayoutManager

= new HorizontalPageLayoutManager(2, 5);//这里两个参数是行列,这里实现的是三行四列

scrollHelper.setUpRecycleView(rvMenu);//将横向布局管理器和recycler view绑定到一起

scrollHelper.setOnPageChangeListener(this);//设置滑动监听

rvMenu.setLayoutManager(horizontalPageLayoutManager);//设置为横向

scrollHelper.updateLayoutManger();

scrollHelper.scrollToPosition(0);//默认滑动到第一页

rvMenu.setHorizontalScrollBarEnabled(true);

HorizontalScrollItemAdapter itemAdapter= new HorizontalScrollItemAdapter(context, R.layout.item_menu);

itemAdapter.setNewData(mItemBeans);

rvMenu.setAdapter(itemAdapter);

if (mItemBeans.size() % 10 > 0) {

    //一共的页数等于 总数/每页数量,并取整。

    page= (int) Math.ceil(mItemBeans.size() * 1.0 / 10);

} else if (mItemBeans.size() % 10 == 0){

    page= mItemBeans.size() % 10;

}

setIndicatorLayout(page);

4.4 指示器

/*

page 是页数 需要自己算 比如我是三行四列 就是每页12个item

用我们数据的size%12 如不不等于0 就是 (size/12)+1

如果等于0 就是 size/12

*/

private void setIndicatorLayout(int page) {

    //生成相应数量的导航小圆点

    LinearLayout.LayoutParams params= new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,

            LinearLayout.LayoutParams.WRAP_CONTENT);

    //设置小圆点左右之间的间隔

    params.setMargins(10, 0, 10, 0);

    //得到页面个数

    dotViews= new ImageView[page];//此处传进来的6是页面数,通常要遍历得到页面的数量来创建图片集合

    for (int i= 0; i< page; i++) {//这里也是循环页面数量

        ImageView imageView= new ImageView(context);

        imageView.setLayoutParams(params);

        imageView.setImageResource(R.drawable.shape_circle);//设置默认没有选中时所有图片为灰色

        if (i== 0) {

            //默认启动时,选中第一个小圆点

            imageView.setSelected(true);

        } else {

            imageView.setSelected(false);

}

        //得到每个小圆点的引用,用于滑动页面时,更改它们的状态。

        dotViews[i] = imageView;

        dotViews[0].setImageResource(R.drawable.shape_sel_circle);//设置第一个页面选择为嗨丝

//添加到布局里面显示

        indicatorLayout.addView(imageView);

}

}

4.5指示器样式

//未选中状态

<shape xmlns:android="http://schemas.android.com/apk/res/android"

    android:shape="oval">

    <solid android:color="#e5e5e5" />

    <size

        android:width="@dimen/dp_3"

        android:height="@dimen/dp_3" />

</shape>

//选中状态

<shape xmlns:android="http://schemas.android.com/apk/res/android"

    android:shape="rectangle">

    <solid android:color="#EA3721" />

    <corners android:radius="@dimen/dp_3"/>

    <size

        android:width="@dimen/dp_10"

        android:height="@dimen/dp_3" />

</shape>

5.表演结束  谢谢大家

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容