Android 可分组的 RecyclerViewAdapter
airongbin
8年前
<p>今天给大家介绍的是一个可以实现数据分组显示的RecyclerViewAdapter: GroupedRecyclerViewAdapter 。它可以很方便的实现RecyclerView的分组显示,并且每个组都可以包含组头、组尾和子项;可以方便实现多种Type类型的列表,可以实现如QQ联系人的列表一样的列表展开收起功能等。下面先让我们看一下它所能够实现的一些效果:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/434b4e4c78497c7ea8afcbfe13bb883c.jpg"></p> <p style="text-align:center">分组的列表</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/2a1f1686b0801bf6ada1b0cf31b33b92.jpg"></p> <p style="text-align:center">不带组尾的列表</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/d3fe2c339f1a716de46a103c2607519a.jpg"></p> <p style="text-align:center">不带组头的列表</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/9c2445b0cbabdf127175e19a6f369c41.jpg"></p> <p style="text-align:center">子项为Grid的列表</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/7783726746647bab23a6ea69271a8dc1.jpg"></p> <p style="text-align:center">子项为Grid的列表(各组子项的Span不同)</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/07719bf567ac0174dca44d41e4dab9c1.jpg"></p> <p style="text-align:center">头、尾和子项都支持多种类型的列表</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/6badb6d39ae546ba00514738cb632280.jpg"></p> <p style="text-align:center">子项为Grid的列表</p> <p>还可以很容易的实时列表的展开收起效果:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3ecddd8d4006916a0347a501d49504a9.gif"></p> <p style="text-align:center">可展开收起的列表</p> <p>以上展示的只是GroupedRecyclerViewAdapter能实现的一些常用效果,其实使用GroupedRecyclerViewAdapter还可以很容易的实现一些更加复杂的列表效果。在我的GroupedRecyclerViewAdapter项目给出的Demo中给出了上面几种效果的实现例子,并且有详细的注释说明,有兴趣的同学可以到我的GitHub下载源码。下面直接讲解GroupedRecyclerViewAdapter的使用。</p> <p>1、引入依赖</p> <p>在Project的build.gradle在添加以下代码</p> <pre> <code class="language-java">allprojects { repositories { ... maven { url 'https://jitpack.io' } } }</code></pre> <p>在Module的build.gradle在添加以下代码</p> <pre> <code class="language-java">compile 'com.github.donkingliang:GroupedRecyclerViewAdapter:1.0.1'</code></pre> <p>2、继承GroupedRecyclerViewAdapter</p> <pre> <code class="language-java">public class GroupedListAdapter extends GroupedRecyclerViewAdapter { }</code></pre> <p>3、实现GroupedRecyclerViewAdapter里的方法</p> <p>GroupedRecyclerViewAdapter是一个抽象类,它提供了一系列需要子类去实现的方法。</p> <pre> <code class="language-java">//返回组的数量 public abstract int getGroupCount(); //返回当前组的子项数量 public abstract int getChildrenCount(int groupPosition); //当前组是否有头部 public abstract boolean hasHeader(int groupPosition); //当前组是否有尾部 public abstract boolean hasFooter(int groupPosition); //返回头部的布局id。(如果hasHeader返回false,这个方法不会执行) public abstract int getHeaderLayout(int viewType); //返回尾部的布局id。(如果hasFooter返回false,这个方法不会执行) public abstract int getFooterLayout(int viewType); //返回子项的布局id。 public abstract int getChildLayout(int viewType); //绑定头部布局数据。(如果hasHeader返回false,这个方法不会执行) public abstract void onBindHeaderViewHolder(BaseViewHolder holder, int groupPosition); //绑定尾部布局数据。(如果hasFooter返回false,这个方法不会执行) public abstract void onBindFooterViewHolder(BaseViewHolder holder, int groupPosition); //绑定子项布局数据。 public abstract void onBindChildViewHolder(BaseViewHolder holder, int groupPosition, int childPosition);</code></pre> <p>还可是重写GroupedRecyclerViewAdapter方法实现头、尾和子项的多种类型item。效果就像上面的第6张图一样。</p> <pre> <code class="language-java">//返回头部的viewType。 public int getHeaderViewType(int groupPosition); //返回尾部的viewType。 public int getFooterViewType(int groupPosition) ; //返回子项的viewType。 public int getChildViewType(int groupPosition, int childPosition) ;</code></pre> <p>4、设置点击事件的监听</p> <p>GroupedRecyclerViewAdapter提供了对列表的点击事件的监听方法。</p> <pre> <code class="language-java">//设置组头点击事件 public void setOnHeaderClickListener(OnHeaderClickListener listener) { mOnHeaderClickListener = listener; } //设置组尾点击事件 public void setOnFooterClickListener(OnFooterClickListener listener) { mOnFooterClickListener = listener; } // 设置子项点击事件 public void setOnChildClickListener(OnChildClickListener listener) { mOnChildClickListener = listener; }</code></pre> <p>注意事项:</p> <p>1、对方法重写的注意。</p> <p>如果我们直接继承RecyclerView.Adapter去实现自己的Adapter时,一般会重写Adapter中的以下几个方法:</p> <pre> <code class="language-java">public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType); public void onBindViewHolder(RecyclerView.ViewHolder holder, int position); public int getItemCount(); public int getItemViewType(int position);</code></pre> <p>但如果是使用GroupedRecyclerViewAdapter,就一定不能去重写这几个方法,因为在GroupedRecyclerViewAdapter中已经对这几个方法做了实现,而且是对实现列表分组至关重要的,如果子类重写了这几个方法,可能会破坏GroupedRecyclerViewAdapter的功能。</p> <p>从前面给出的GroupedRecyclerViewAdapter的方法我们可以看到,这些方法其实就是对应RecyclerView.Adapter的这4个方法的,所以我们直接使用GroupedRecyclerViewAdapter提供的方法即可。</p> <p>RecyclerView.Adapter中的</p> <pre> <code class="language-java">public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);</code></pre> <p>对应GroupedRecyclerViewAdapter中的</p> <pre> <code class="language-java">//返回头部的布局id。(如果hasHeader返回false,这个方法不会执行) public abstract int getHeaderLayout(int viewType); //返回尾部的布局id。(如果hasFooter返回false,这个方法不会执行) public abstract int getFooterLayout(int viewType); //返回子项的布局id。 public abstract int getChildLayout(int viewType);</code></pre> <p>这里之所以返回的是布局id而不是ViewHolder ,是因为在GroupedRecyclerViewAdapter项目中已经提供了一个通用的ViewHolder:BaseViewHolder。所以使用者只需要提供布局的id即可,不需要自己去实现ViewHolder。</p> <pre> <code class="language-java">@Override public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(mContext).inflate(getLayoutId(mTempPosition, viewType), parent, false); return new BaseViewHolder(view); } private int getLayoutId(int position, int viewType) { int type = judgeType(position); if (type == TYPE_HEADER) { return getHeaderLayout(viewType); } else if (type == TYPE_FOOTER) { return getFooterLayout(viewType); } else if (type == TYPE_CHILD) { return getChildLayout(viewType); } return 0; }</code></pre> <p>RecyclerView.Adapter中的</p> <pre> <code class="language-java">public void onBindViewHolder(RecyclerView.ViewHolder holder, int position);</code></pre> <p>对应GroupedRecyclerViewAdapter中的</p> <pre> <code class="language-java">//绑定头部布局数据。(如果hasHeader返回false,这个方法不会执行) public abstract void onBindHeaderViewHolder(BaseViewHolder holder, int groupPosition); //绑定尾部布局数据。(如果hasFooter返回false,这个方法不会执行) public abstract void onBindFooterViewHolder(BaseViewHolder holder, int groupPosition); //绑定子项布局数据。 public abstract void onBindChildViewHolder(BaseViewHolder holder, int groupPosition, int childPosition);</code></pre> <p>RecyclerView.Adapter中的</p> <pre> <code class="language-java">public int getItemCount();</code></pre> <p>对应GroupedRecyclerViewAdapter中的</p> <pre> <code class="language-java">//返回组的数量 public abstract int getGroupCount(); //返回当前组的子项数量 public abstract int getChildrenCount(int groupPosition);</code></pre> <p>RecyclerView.Adapter中的</p> <pre> <code class="language-java">public int getItemViewType(int position);</code></pre> <p>对应GroupedRecyclerViewAdapter中的</p> <pre> <code class="language-java">//返回头部的viewType。 public int getHeaderViewType(int groupPosition); //返回尾部的viewType。 public int getFooterViewType(int groupPosition) ; //返回子项的viewType。 public int getChildViewType(int groupPosition, int childPosition) ;</code></pre> <p>2、对应列表操作的注意</p> <p>RecyclerView.Adapter提供了一系列对列表进行操作的方法。如:</p> <pre> <code class="language-java">//更新操作 public final void notifyDataSetChanged(); public final void notifyItemChanged(int position); public final void notifyItemChanged(int position, Object payload); public final void notifyItemRangeChanged(int positionStart, int itemCount); public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload); //插入操作 public final void notifyItemInserted(int position); public final void notifyItemRangeInserted(int positionStart, int itemCount); //删除操作 public final void notifyItemRemoved(int position) public final void notifyItemRangeRemoved(int positionStart, int itemCount);</code></pre> <p>在GroupedRecyclerViewAdapter不建议使用RecyclerView.Adapter的任何对列表的操作方法,因为这些方法都是基于列表的操作,它的position是相对于整个列表而言的,而GroupedRecyclerViewAdapter是分组的列表,它对列表的操作应该是基于组的。同时GroupedRecyclerViewAdapter使用了组结构来维护整个列表的结构,使我们可以对列表进行组的操作,在列表发生变化时GroupedRecyclerViewAdapter需要及时对组结构进行调整,如果使用了RecyclerView.Adapter中的方法对列表进行更新,GroupedRecyclerViewAdapter可能因为无法及时调整组结构而方式异常。所以在使用中应该避免使用这些方法。GroupedRecyclerViewAdapter同样提供了一系列对列表进行操作的方法,我们应该使用GroupedRecyclerViewAdapter所提供的方法。</p> <pre> <code class="language-java">//****** 刷新操作 *****// //刷新数据列表。对应 notifyDataSetChanged(); public void changeDataSet(); //刷新一组数据,包括组头,组尾和子项 public void changeGroup(int groupPosition); //刷新多组数据,包括组头,组尾和子项 public void changeRangeGroup(int groupPosition, int count); // 刷新组头 public void changeHeader(int groupPosition); /刷新组尾 public void changeFooter(int groupPosition); // 刷新一组里的某个子项 public void changeChild(int groupPosition, int childPosition); //刷新一组里的多个子项 public void changeRangeChild(int groupPosition, int childPosition, int count); // 刷新一组里的所有子项 public void changeChildren(int groupPosition); //****** 删除操作 *****// // 删除所有数据 public void removeAll(); //删除一组数据,包括组头,组尾和子项 public void removeGroup(int groupPosition); // 删除多组数据,包括组头,组尾和子项 public void removeRangeGroup(int groupPosition, int count); // 删除组头 public void removeHeader(int groupPosition); // 删除组尾 public void removeFooter(int groupPosition); //删除一组里的某个子项 public void removeChild(int groupPosition, int childPosition); // 删除一组里的多个子项 public void removeRangeChild(int groupPosition, int childPosition, int count); //删除一组里的所有子项 public void removeChildren(int groupPosition); //****** 插入操作 *****// // 插入一组数据 public void insertGroup(int groupPosition); //插入一组数据 public void insertRangeGroup(int groupPosition, int count); //插入组头 public void insertHeader(int groupPosition); // 插入组尾 public void insertFooter(int groupPosition); //插入一个子项到组里 public void insertChild(int groupPosition, int childPosition); // 插入一组里的多个子项 public void insertRangeChild(int groupPosition, int childPosition, int count); //插入一组里的所有子项 public void insertChildren(int groupPosition);</code></pre> <p>3、使用GridLayoutManager的注意</p> <p>如果有使用GridLayoutManager,一定要使用项目中所提供的GroupedGridLayoutManager。因为分组列表如果要使用GridLayoutManager实现网格布局。要保证组的头部和尾部是要单独占用一行的。否则组的头、尾可能会跟子项混着一起,造成布局混乱。而且GroupedGridLayoutManager提供了对子项的SpanSize的修改方法,使用GroupedGridLayoutManager可以实现更多的复杂列表布局。</p> <pre> <code class="language-java">//直接使用GroupedGridLayoutManager实现子项的Grid效果 GroupedGridLayoutManager gridLayoutManager = new GroupedGridLayoutManager(this, 2, adapter); rvList.setLayoutManager(gridLayoutManager); GroupedGridLayoutManager gridLayoutManager = new GroupedGridLayoutManager(this, 4, adapter){ //重写这个方法 改变子项的SpanSize。 //这个跟重写SpanSizeLookup的getSpanSize方法的使用是一样的。 @Override public int getChildSpanSize(int groupPosition, int childPosition) { if(groupPosition % 2 == 1){ return 2; } return super.getChildSpanSize(groupPosition, childPosition); } }; rvList.setLayoutManager(gridLayoutManager);</code></pre> <p>下面看一个简单的使用列子:</p> <pre> <code class="language-java">public class GroupedListAdapter extends GroupedRecyclerViewAdapter { private ArrayList<GroupEntity> mGroups; public GroupedListAdapter(Context context, ArrayList<GroupEntity> groups) { super(context); mGroups = groups; } @Override public int getGroupCount() { return mGroups == null ? 0 : mGroups.size(); } @Override public int getChildrenCount(int groupPosition) { ArrayList<ChildEntity> children = mGroups.get(groupPosition).getChildren(); return children == null ? 0 : children.size(); } @Override public boolean hasHeader(int groupPosition) { return true; } @Override public boolean hasFooter(int groupPosition) { return true; } @Override public int getHeaderLayout(int viewType) { return R.layout.adapter_header; } @Override public int getFooterLayout(int viewType) { return R.layout.adapter_footer; } @Override public int getChildLayout(int viewType) { return R.layout.adapter_child; } @Override public void onBindHeaderViewHolder(BaseViewHolder holder, int groupPosition) { GroupEntity entity = mGroups.get(groupPosition); holder.setText(R.id.tv_header, entity.getHeader()); } @Override public void onBindFooterViewHolder(BaseViewHolder holder, int groupPosition) { GroupEntity entity = mGroups.get(groupPosition); holder.setText(R.id.tv_footer, entity.getFooter()); } @Override public void onBindChildViewHolder(BaseViewHolder holder, int groupPosition, int childPosition) { ChildEntity entity = mGroups.get(groupPosition).getChildren().get(childPosition); holder.setText(R.id.tv_child, entity.getChild()); } }</code></pre> <pre> <code class="language-java">public class GroupedListActivity extends AppCompatActivity { private TextView tvTitle; private RecyclerView rvList; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_group_list); tvTitle = (TextView) findViewById(R.id.tv_title); rvList = (RecyclerView) findViewById(R.id.rv_list); tvTitle.setText(R.string.group_list); rvList.setLayoutManager(new LinearLayoutManager(this)); GroupedListAdapter adapter = new GroupedListAdapter(this, GroupModel.getGroups(10, 5)); adapter.setOnHeaderClickListener(new GroupedRecyclerViewAdapter.OnHeaderClickListener() { @Override public void onHeaderClick(GroupedRecyclerViewAdapter adapter, BaseViewHolder holder, int groupPosition) { Toast.makeText(GroupedListActivity.this, "组头:groupPosition = " + groupPosition, Toast.LENGTH_LONG).show(); } }); adapter.setOnFooterClickListener(new GroupedRecyclerViewAdapter.OnFooterClickListener() { @Override public void onFooterClick(GroupedRecyclerViewAdapter adapter, BaseViewHolder holder, int groupPosition) { Toast.makeText(GroupedListActivity.this, "组尾:groupPosition = " + groupPosition, Toast.LENGTH_LONG).show(); } }); adapter.setOnChildClickListener(new GroupedRecyclerViewAdapter.OnChildClickListener() { @Override public void onChildClick(GroupedRecyclerViewAdapter adapter, BaseViewHolder holder, int groupPosition, int childPosition) { Toast.makeText(GroupedListActivity.this, "子项:groupPosition = " + groupPosition + ", childPosition = " + childPosition, Toast.LENGTH_LONG).show(); } }); rvList.setAdapter(adapter); } }</code></pre> <p> </p> <p> </p> <p>来自:https://juejin.im/post/58d3dc96b123db3f6b5f5642</p> <p> </p>