RecyclerView实现分组展示信息

extends:http://blog.csdn.net/wzlyd1/article/details/52292548

前言

一直在鸿洋大神的安卓群里水群,渐渐的loader和安卓弟等人都成长了起来,还记得当初他们清纯的模样。小L在群里不水了,安卓弟成长为CTO了,只有我依然默默无闻,于是决定再写博客了,之前不写,一是因为工作比较忙,二是因为我水平有限,简单的不想写,因为写了也没用,网上demo很多,难的自己也没多高的造诣,写也写不出来,所以一直都是处于“半荒废状态”,当然说到底其实还是因为懒,于是今天我再次执笔,将学到的东西全部记录下来。

效果

先上效果图给大家看看,好有一个整体的认识

效果就是这样的,但是不仅仅局限于这种布局,事实上只要是三段式布局,都可以通过该demo的学习来实现,什么是三段式布局呢,就是有header -content-footer类型的布局,画一个图来解释

 
比如下面这个图就可以 

可以看到,用途还是很广泛的,所以很需要我们去学习一下

怎么去实现

gitbub上有一个很牛逼的类,但是貌似知道的人很少,名字叫做SectionedRecyclerViewAdapter ,但是今天我们不去研究她是怎么实现的,我们来研究他怎么用就行了

  1. 继承SectionedRecyclerViewAdapter
/**
 * Created by lyd10892 on 2016/8/23.
 */

public class HotelEntityAdapter extends SectionedRecyclerViewAdapter<HeaderHolder, DescHolder, RecyclerView.ViewHolder> {

    public ArrayList<HotelEntity.TagsEntity> allTagList;
    private Context mContext;
    private LayoutInflater mInflater;
    private SparseBooleanArray mBooleanMap;//记录下哪个section是被打开的

    public HotelEntityAdapter(Context context) {
        mContext = context;
        mInflater = LayoutInflater.from(context);
        mBooleanMap = new SparseBooleanArray();
    }

    public void setData(ArrayList<HotelEntity.TagsEntity> allTagList) {
        this.allTagList = allTagList;
        notifyDataSetChanged();
    }

    @Override
    protected int getSectionCount() {
        return HotelUtils.isEmpty(allTagList) ? 0 : allTagList.size();
    }

    @Override
    protected int getItemCountForSection(int section) {
        int count = allTagList.get(section).tagInfoList.size();
        if (count >= 8 && !mBooleanMap.get(section)) {
            count = 8;
        }

        return HotelUtils.isEmpty(allTagList.get(section).tagInfoList) ? 0 : count;
    }

    //是否有footer布局
    @Override
    protected boolean hasFooterInSection(int section) {
        return false;
    }

    @Override
    protected HeaderHolder onCreateSectionHeaderViewHolder(ViewGroup parent, int viewType) {
        return new HeaderHolder(mInflater.inflate(R.layout.hotel_title_item, parent, false));
    }

    @Override
    protected RecyclerView.ViewHolder onCreateSectionFooterViewHolder(ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    protected DescHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
        return new DescHolder(mInflater.inflate(R.layout.hotel_desc_item, parent, false));
    }

    @Override
    protected void onBindSectionHeaderViewHolder(final HeaderHolder holder, final int section) {
        holder.openView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                boolean isOpen = mBooleanMap.get(section);
                String text = isOpen ? "展开" : "关闭";
                mBooleanMap.put(section, !isOpen);
                holder.openView.setText(text);
                notifyDataSetChanged();
            }
        });

        holder.titleView.setText(allTagList.get(section).tagsName);
        holder.openView.setText(mBooleanMap.get(section) ? "关闭" : "展开");

    }

    @Override
    protected void onBindSectionFooterViewHolder(RecyclerView.ViewHolder holder, int section) {

    }

    @Override
    protected void onBindItemViewHolder(DescHolder holder, int section, int position) {
        holder.descView.setText(allTagList.get(section).tagInfoList.get(position).tagName);

    }
}

这里面有几个很重要的方法也是需要我们必须重写的,是我们实现效果的关键

    protected abstract int getSectionCount();

    protected abstract int getItemCountForSection(int section);

    protected abstract boolean hasFooterInSection(int section);

    protected abstract H  onCreateSectionHeaderViewHolder(ViewGroup parent, int viewType);

    protected abstract F  onCreateSectionFooterViewHolder(ViewGroup parent, int viewType);

    protected abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType);

    protected abstract void onBindSectionHeaderViewHolder(H holder, int section);

    protected abstract void onBindSectionFooterViewHolder(F holder, int section);

   protected abstract void onBindItemViewHolder(VH holder, int section, int position); 

接下来我们详细分析这几个方法存在的具体意义 
不过在说之前我们需要看一下我们的数据结构,这个也很重要

public class HotelEntity {
    /**
     * 要注意这个类的数据结构,很重要,直接决定了我们能不能实现分组展示
     */

    public ArrayList<TagsEntity> allTagsList;

    public class TagsEntity {
        public String tagsName;
        public ArrayList<TagInfo> tagInfoList;

        public class TagInfo {
            public String tagName;
        }
    }

}

这个方法主要是用来计算我们一共有多少个section需要展示,返回值是我们最外称list的大小,在我们的示例图中,对应的为热门品牌—商业区—热门景点 等,对应的数据是我们的allTagList

protected abstract int getSectionCount();

这个方法是用来展示content内容区域,返回值是我们需要展示多少内容,在本例中,我们超过8条数据只展示8条,点击展开后就会展示全部数据,逻辑就在这里控制。 对应数据结构为tagInfoList

protected abstract int getItemCountForSection(int section);

判断是否需要底部footer布局,在该例中,我们并不需要显示footer,所以默认返回false就可以,如果你对应的section需要展示footer布局,那么就在对应的section返回true就行了

protected abstract boolean hasFooterInSection(int section);

我们要单独说一下这个方法,这里有一个section和position ,有些人可能会弄混 
section是区域,也就是我们最外层的index,position是每个section对应的内容数据的position

@Override
    protected void onBindItemViewHolder(DescHolder holder, int section, int position) {
        holder.descView.setText(allTagList.get(section).tagInfoList.get(position).tagName);

    } 

至于下面的onCreateViewHolder ,onBindViewHolder不多做解释了,如果你用过recyclerView,使用方法是一样的,无非是渲染布局,绑定数据

  • 展示数据 
    基本上,如果上面的adapter逻辑写完,我们的布局算是完成了,首页代码如下
public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private HotelEntityAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        mAdapter = new HotelEntityAdapter(this);
        GridLayoutManager manager = new GridLayoutManager(this,4);//我们需要网格式的布局
        //设置header占据的空间
        manager.setSpanSizeLookup(new SectionedSpanSizeLookup(mAdapter,manager));
        mRecyclerView.setLayoutManager(manager);
        mRecyclerView.setAdapter(mAdapter);
        HotelEntity entity = JsonUtils.analysisJsonFile(this,"json");
        mAdapter.setData(entity.allTagsList);
    }
}

代码里有一段很重要的注释,设置header占据的空间,没错,因为我们要仿造header的效果,我们设置的manager是GridLayoutManager,设置的每一行item数量是4,如果不重写该方法,那么header显示就会出错,核心代码如下:

/**
 * A SpanSizeLookup to draw section headers or footer spanning the whole width of the RecyclerView
 * when using a GridLayoutManager
 *
 * 这个类是用来自定义每个item需要占据的空间
 *
 *
 */
public class SectionedSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {

    protected SectionedRecyclerViewAdapter<?, ?, ?> adapter = null;
    protected GridLayoutManager layoutManager = null;

    public SectionedSpanSizeLookup(SectionedRecyclerViewAdapter<?, ?, ?> adapter, GridLayoutManager layoutManager) {
        this.adapter = adapter;
        this.layoutManager = layoutManager;
    }

    @Override
    public int getSpanSize(int position) {

        //header和footer占据的是全屏
        if(adapter.isSectionHeaderPosition(position) || adapter.isSectionFooterPosition(position)){
            return layoutManager.getSpanCount();
        }else{
            return 1;//其他默认1
        }

    }
} 

最重要的是getSpanSize方法,只要是header或者是footer就返回我们设置的网格数,也就是4,代表header和footer占据4个网格的空间,其他占据1个 
这样,我们就可以完美的展示我们需要的布局了 
当前我们的demo是网格布局的,你也可以设置流式布局,只需要设置不同的layoutmanager就可以了 
比如下图的效果我们也可以实现 

核心代码已经解释完毕,当然最核心的是SectionedRecyclerViewAdapter这个类,这个类好好学习一下,会学到很多,也会实现很多app常见的布局效果,比如设置不同的viewType展现更复杂的布局 
最后,看一下代码结构: 
 
最后啰嗦一句,写博客比写代码难多了。 
demo已经上传到github了,欢迎fork 
https://github.com/nbwzlyd/SectionRecyclerViewDemo

时间: 2024-10-09 16:52:20

RecyclerView实现分组展示信息的相关文章

6.数据分组-统计信息

---数据分组-统计信息----- --select 字段列表from 表列表 where 对数据源进行数据筛选group by 分组字段列表Order by 排序字段列表 --1.得到所有学员总人数 select COUNT(*) from Student --2.得到男女生的人数 select COUNT(*) from Student where Sex='男' select COUNT(*) from Student where Sex='女' --使用分组统计 select COUNT

messages中的展示信息呢?

中实现可重载的MessageSource简明教程原创 2017年09月28日引言: 在Spring Boot中messages中定义的信息,如果发生变更,则需要重启应用.那该如何实现才可以不重启应用的情况下替换messages中的展示信息呢?本文将给出一个简要的教程. 的资源配置与读取在之前的文章中,已经介绍过了如何在Spring Boot中进行资源的配置和读取以及相应的测试代码,感兴趣的读者,可以参照Spring Boot中支持i18n简明教程. 2 可重载的在Spring中定义了Reload

IOS第七天(5:UiTableView 汽车品牌,复杂模型分组展示,A-Z索要列表) (2015-08-05 14:03)

复杂模型分组展示 #import "HMViewController.h" #import "HMCarGroup.h" #import "HMCar.h" @interface HMViewController () <UITableViewDataSource> @property (nonatomic, strong) NSArray *carGroups; @property (nonatomic, strong) UITab

03 展示信息-show

展示数据库.表信息 - show show databases; //展示可用数据库 select database(); //展示当前使用的数据库 show tables; //展示当前数据库可用表格 show columns from 表名; //查看指定表的列信息,和下面两个语句等价 desc 表名; describe 表名; show create table 表名; //查看建表语句 展示其他信息 show status; //用于展示广泛的服务器状态信息 show grants; /

项目在App Store的展示信息

一.首部1.图标作用:一个软件的logo.修改:每次提交新版本时可以修改.要求:1>1024*1024像素 2>72dpi.RGB.平展.不透明.没有圆角 3>高品质的JPEG或PNG图像文件格式. 2.项目名称作用:显示在App Store中的App本地化名称.修改:App提交之后,只能在下一次上传新版App版本时,才能更改其名称.要求:名称长度不能少于2个字符,不能超过50个字符. 3.17+作用:评级,17+代表17岁以上人员适合使用.修改:每次提交新版本时可以修改.要求:真实选择

Linq Group By 获取分组内信息

var q = from u in dt_data.AsEnumerable() group u by u.Field<string>("Code"); //分组信息 foreach (var gp in q) { sb.AppendLine(gp.Key); //每个分组内的信息 foreach (var item in gp) { sb.AppendLine(item.Field<string>("Code") + " &quo

9.商品在展示信息

<html> <head> <title>bookStore 商城-商品信息展示</title> </head> <body> <!-- head 一行两列的表格--> <div id="head"> <table width="100%"> <tr> <td> <img src="images/case1/logo.p

oracle按每个10分钟进行分组展示数据

例如 有这么一张表 XATXDAY_FLIGHT(航班飞航表), 有这么一个字段 STD_LOCAL(起飞时间), 要求:统计一天24小时之内每隔10分钟,这10分钟之内有几架飞机起飞. 比如:XATXDAY_FLIGHT 表 STD_LOCAL . . .(其他字段省略) 12:00 12:05 12:10 12:20 12:25 . . . (其他时间省略) 也就是说,结果应该是这样的:表示12:00~12:10这个时间段内有2架飞机起飞 12:00~12:10  2 12:10~12:20

非表格展示信息(布局)

<title></title> <link href="css/css1.css" rel="stylesheet" /> 导入css样式表 </head> <body> <form id="form1" runat="server"> <div id="top"></div> <div id="