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>