RecycleView多种布局显示

RecycleView多种布局显示


1.前言

  • 我们知道ListView多种布局显示用到两个方法一个getItemViewType和getViewTypeCount方法。
    • getitemViewType方法告诉ListView我在第几个position展示哪种布局
    • getViewTypeCount方法告诉ListView我有多少种布局
  • 那么RecycleView如何实现多布局显示呢?其实这个和ListView原理一样,我们待会说,先来了解下实现RecycleView.Adapter会有哪几个方法要重写
    • getItemCount()告诉RecycleView我有多少个item数量
    • onCreateViewHolder()创建自己的holder并返回
    • onBindViewHolder()绑定holder
    • 创建自己的holder只需继承成RecycleView.ViewHolder即可(这一句不是重写adapter的方法,而是在继承RecycleView.Adapter的时候需要指定泛型,而这个泛型就是你的ViewHolder)。

2.多布局显示

RecycleView.Adapter也提供了getItemViewType方法,此方法和ListView加载多布局一样。我们只要在该方法中判断某个位置下返回某一种类型的布局即可。比如这样给RecycleView加头布局和脚布局(以下代码是伪代码):

  • 你得告诉RecycleView你要加载的item数量

      public int  getItemCount() {
          return mDatas.size()+2;
      }
    
  • 在getItemViewType中根据不同位置的position返回不同布局类型

    if(position==0){
    return  头布局类型;
    }else if(position==getitemCount()-1){
    return  脚布局类型;
    }else {
    return  默认布局类型;
    }
  • 在onCreateViewHolder(ViewGroup parent, intviewType)根据不同类型的type来返回不同的view给自己的ViewHolder。FooterView和SwitchView如何来的,其实是自己的adapter提供两个方法setHeaderView()和setFooterView通过外界传递进来的。

    public ListHolder onCreateViewHolder(ViewGroup parent, intviewType) {
       View root =null;
       if(viewType ==头布局类型) {
       root =mHeaderView;
       }else if(viewType==脚布局类型){
        root=mFooterView;
       }else{
        root = View.inflate(context,R.layout.list_item,null);
      }
       return newListHolder(root,viewType);
    }
    
  • 在自己的ViewHolder中进行处理如果是头布局或者脚布局直接返回

    public static class ListHolderextends RecyclerView.ViewHolder{
       TextView tv;
       publicListHolder(View root, intviewType) {
             super(root);
         if(viewType==头布局类型){
           return ;
       }
         if(viewType==脚布局类型){
         return ;
       }
       tv= (TextView)root.findViewById(R.id.item);
    }
    
  • 在onBindViewHolder(ListHolder holder, intposition)方法中绑定数据

    • 绑定View,这里是根据返回的这个position的类型,从而进行绑定的,HeaderView和FooterView就不绑定了
        public void  onBindViewHolder(ListHolder holder,  intposition) {
                int  itemViewType = getItemViewType(position);
                if(itemViewType ==头布局类型) {
                    return;
               }else if(itemViewType ==脚布局类型) {
                  return;
               }else{
               //这里注意因为加了一个头布局position-1才是正确的数据
               holder.tv.setTag(position-1);
               holder.tv.setText(mDatas.get(position -1));
               }
           }

原理讲完了,那么接下来就应该讲讲实际的东西了。

3.需求

  • 一般情况下,RecycleView加载多布局就是头部一个轮播图脚部一个加载更多,那么今天带给大家是RecycleView四种布局加载,为什么是四种布局呢,其实和以上三种布局原理是一样的,只是为了多说一下,recycleView切换视图列数。
      请自动忽略图丑
      请自动忽略图丑
  • 我们分析下上面给出的两张图

    • 蓝色背景是headerView
    • 红色背景用来切换列表我们这里就叫做swichView
    • 绿色部分就是我们的数据布局了
    • 蓝色部分为FooterView
  • 要达到这种布局,那么首先你得在你的adapter中getitemCount中返回数据长度+3

  • 在getItemViewType方法中根据position返回不同类型布局 ......和上面的三种布局一致这里就不多说了。
    主要讲解如何监听RecycleView滑动到底部自定加载数据呢?如何切换item的列数呢?

4.滑动监听

  • 如何监听RecycleView滑动到底部自定加载数据?
    • RecycleView有个addOnScrollListener方法,此方法接受一个OnScrollListener的子类并重写onScrolled,当RecycleView滑动的时 候会回调onScrolled方法
    • onScrolled(RecyclerView recyclerView, int dx, int dy) 参数二:水平滚动距离,参数三:竖直滚动距离
  • 那我们如何实现呢?
    • 自定义一个类EndLessOnScrollListener 继承RecyclerView.OnScrollListener重写onScrolled方法。完整代码如下:
           public abstract class EndLessOnScrollListener extends  RecyclerView.OnScrollListener{
                public static final String TAG =EndLessOnScrollListener.class.getName();
                private GridLayoutManager gridLayoutManager;

                //已经加载出来的Item的数量
                private int totalItemCount;

                //主要用来存储上一个totalItemCount
                 private int previousTotal = 0;

                //在屏幕上可见的item数量
                private int visibleItemCount;

                //在屏幕可见的Item中的第一个
                private int firstVisibleItem;

                //是否正在上拉数据
                private boolean loading = true;

                //当前页,从1开始
                private int currentPage =1;
            public EndLessOnScrollListener(GridLayoutManager gridLayoutManager) {
                this.gridLayoutManager = gridLayoutManager;
            }

              @Override
              public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
              super.onScrolled(recyclerView, dx, dy);
              visibleItemCount = recyclerView.getChildCount();
              totalItemCount = gridLayoutManager.getItemCount();
               firstVisibleItem = gridLayoutManager.findFirstVisibleItemPosition();
              if(loading){
                  if(totalItemCount > previousTotal){
                    //说明数据已经加载结束
                    loading = false;
                    previousTotal = totalItemCount;
                }
            }
            /**
             *      当不是正在加载时并且
             *    totalItemCount-visibleItemCount得到的值表示已经滚出和未滑入的总数,
             *    firstVisibleItem 也可以表示已经滚出屏幕的item个数
             *    其实也可以写成这样
             *     int i = totalItemCount - visibleItemCount; i就是滚出和未滚入的和
             *       i-firstVisibleItem得到的是未滚入的  如果未滚入=0或者小于0说明已经滚动到底部了
             *       if(!loading&&i-firstVisibleItem<=0){
             *        currentPage ++;
             *        onLoadMore(currentPage);
             *        loading = true;
             *     }
             */
            if (!loading && totalItemCount-visibleItemCount <= firstVisibleItem){
                currentPage ++;
                onLoadMore(currentPage);
                loading = true;
                }
            }
                /**
                 * 提供一个抽象方法,在Activity中监听到这个EndLessOnScrollListener
                 * 并且实现这个方法
                 */
                public abstract void onLoadMore(int currentPage);

                }
}
  • 如何使用呢?

      rec.addOnScrollListener(new EndLessOnScrollListener(gridLayoutManager) {
              @Override
         public void onLoadMore(int currentPage) {
                  footerViewInTextView.setText("正在加载请稍后...");
                  getData(false);
              }
        }); 
    

因为要模拟网络请求,这里用的是rxjava timer操作符来模拟耗时,count用来模拟数据加载完毕

    private void getData(final boolean falg) {
    if(refreshSubscribe!=null&&!refreshSubscribe.isUnsubscribed()){
        refreshSubscribe.unsubscribe();
    }
    if(count==3){
        footerViewInTextView.setText("没有更多数据了");
        return ;
    }
    if(falg){
        swRefresh.setRefreshing(true);
    }
    refreshSubscribe = Observable.timer(3, TimeUnit.SECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<Long>() {
        @Override
        public void onCompleted() {
            for (int x = 30*count; x < 30*(1+count); x++) {
                 list.add("item" + x);
            }
            if(falg){
                swRefresh.setRefreshing(false);
            }
            count++;
            notifyData();
        }

        @Override
        public void onError(Throwable e) {
        }
        @Override
        public void onNext(Long aLong) {

        }
    });
}

5.更改布局列数

写之前先来看一下动图:

实现原理很简单:

  • adapter定义两种类型

    • 一列类型
    • 三列类型
  • 默认加载三列类型那么可以提供一个变量来默认加载三列类型,提供外界一个可以设置类型的方法。用来更改列数

  • 在getItemViewType中在返回默认布局的时候再次判断下是三列还是一列

    public int getItemViewType(int position) {
      if (position == 0){
      //第一个item应该加载Header
      return TYPE_HEADER;
      //第二个选择切换布局
      }else if(position==1){
        return TYPE_SWITCHER;
      }if (position == getItemCount()-1){
       //最后一个,应该加载Footer
      return TYPE_FOOTER;
      }else {
         //如果是三列 
         if(showOneOrThree==TYPE_SHOW_THREE){
             return  TYPE_SHOW_THREE;
         }else {
         //一列    
             return  TYPE_SHOW_ONE;
         }
      }
    }
    
  • 同样在onBindViewHolder中判断类型,测试点击的是哪一种列数

      public void onBindViewHolder(ListHolder holder,  int position) {
      int itemViewType = getItemViewType(position);
      if (itemViewType == TYPE_HEADER) {
          return;
      } else if (itemViewType == TYPE_SWITCHER) {
          return;
      } else if (itemViewType == TYPE_FOOTER) {
          return;
      } else {
          holder.tv.setTag(position-2);
          holder.tv.setText(mDatas.get(position - 2));
          if(itemViewType==TYPE_SHOW_ONE){
              holder.tv.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      Integer tag = (Integer) v.getTag();
                      Toast.makeText(context,"一行单列:"+mDatas.get(tag),Toast.LENGTH_SHORT).show();
                  }
              });
          }else {
              holder.tv.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      Integer tag = (Integer) v.getTag();
                      Toast.makeText(context,"一行三列:"+mDatas.get(tag),Toast.LENGTH_SHORT).show();
                      }
                  });
              }
          }
      }
    
  • 外界更改并刷新

    //三列
    to_gridview.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (myAdapter != null) {
                int spanSize = myAdapter.getShowOneOrThree();
                //一列变三列,三列就不管
                if (spanSize == RECYCLEVIEW_SHOW_ONE) {
                    gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                        @Override
                        public int getSpanSize(int position) {
                            if (position == 0) {
                                return 3;
                            } else if (position == 1) {
                                return 3;
                            } else if (myAdapter.getItemCount() - 1 == position) {
                                return 3;
                            } else {
                                return 1;
                            }
                        }
                    });
                    //更改状态
                    myAdapter.setShowOneOrThree(RECYCLEVIEW_SHOW_THREE);
                    //刷新视图
                    myAdapter.notifyItemRangeChanged(3, myAdapter.getItemCount());
                }
            }
        }
    });
    //一列
    to_listview.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (myAdapter != null) {
                int spanSize = myAdapter.getShowOneOrThree();
                //三列变一列,一列就不管
                if (spanSize == RECYCLEVIEW_SHOW_THREE) {
                    gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                        @Override
                        public int getSpanSize(int position) {
                            if (position == 0) {
                                return 3;
                            } else if (position == 1) {
                                return 3;
                            } else if (myAdapter.getItemCount() - 1 == position) {
                                return 3;
                            } else {
                                return 3;
                            }
                        }
                    });
                    //更改状态
                    myAdapter.setShowOneOrThree(RECYCLEVIEW_SHOW_ONE);
                    //刷新视图
                    myAdapter.notifyItemRangeChanged(3, myAdapter.getItemCount());
                }
            }
        }
    });

这里要说下gridLayoutManager.setSpanSizeLookup方法,此方法的作用是用来确定一个item占用几列。因为我们默认用的是gridViewManager
来加载三列的,所以原始视图是三列。

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

推荐阅读更多精彩内容