GroupedRecyclerViewAdapter

Introduction: GroupedRecyclerViewAdapter 可以很方便的实现 RecyclerView 的分组显示,并且每个组都可以包含组头、组尾和子项;可以方便实现多种 Type 类型的列表,可以实现如 QQ 联系人的列表一样的列表展开收起功能,还可以实现头部悬浮吸顶功能等。
More: Author   ReportBugs   
Tags:

GroupedRecyclerViewAdapter 可以很方便的实现 RecyclerView 的分组显示,并且每个组都可以包含组头、组尾和子项;可以方便实现多种 Type 类型的列表,可以实现如 QQ 联系人的列表一样的列表展开收起功能,还可以实现头部悬浮吸顶功能等。下面先让我们看一下它所能够实现的一些效果:

分组的列表 不带组尾的列表 不带组头的列表

子项为 Grid 的列表 子项为 Grid 的列表(各组子项的 Span 不同).jpg) 头、尾和子项都支持多种类型的列表

多种子项类型的列表

还可以很容易的实时列表的展开收起效果:

可展开收起的列表

还可以轻松实现头部悬浮吸顶的效果:

头部吸顶的列表

以上展示的只是 GroupedRecyclerViewAdapter 能实现的一些常用效果,其实使用 GroupedRecyclerViewAdapter 还可以很容易的实现一些更加复杂的列表效果。在我的 GroupedRecyclerViewAdapter 项目的 Demo 中给出了上面几种效果的实现例子,并且有详细的注释说明,有兴趣的同学可以到我的 GitHub 下载源码。下面直接讲解 GroupedRecyclerViewAdapter 的使用。

1、引入依赖

在 Project 的 build.gradle 在添加以下代码

    allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

在 Module 的 build.gradle 在添加以下代码

    implementation 'com.github.donkingliang:GroupedRecyclerViewAdapter:1.3.2'

2、继承 GroupedRecyclerViewAdapter

public class GroupedListAdapter extends GroupedRecyclerViewAdapter {
}

3、实现 GroupedRecyclerViewAdapter 里的方法

GroupedRecyclerViewAdapter 是一个抽象类,它提供了一系列需要子类去实现的方法。

    //返回组的数量
    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);

还可是重写 GroupedRecyclerViewAdapter 方法实现头、尾和子项的多种类型 item。效果就像上面的第 6 张图一样。

    //返回头部的 viewType。
    public int getHeaderViewType(int groupPosition);

    //返回尾部的 viewType。
    public int getFooterViewType(int groupPosition) ;

    //返回子项的 viewType。
    public int getChildViewType(int groupPosition, int childPosition) ;

4、设置点击事件的监听

GroupedRecyclerViewAdapter 提供了对列表的点击事件的监听方法。

    //设置组头点击事件
    public void setOnHeaderClickListener(OnHeaderClickListener listener) {
        mOnHeaderClickListener = listener;
    }

    //设置组尾点击事件
    public void setOnFooterClickListener(OnFooterClickListener listener) {
        mOnFooterClickListener = listener;
    }

    // 设置子项点击事件
    public void setOnChildClickListener(OnChildClickListener listener) {
        mOnChildClickListener = listener;
    }

注意事项:

1、对方法重写的注意。

如果我们直接继承 RecyclerView.Adapter 去实现自己的 Adapter 时,一般会重写 Adapter 中的以下几个方法:

public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);

public void onBindViewHolder(RecyclerView.ViewHolder holder, int position);

public int getItemCount();

public int getItemViewType(int position);

但如果是使用 GroupedRecyclerViewAdapter,就一定不能去重写这几个方法,因为在 GroupedRecyclerViewAdapter 中已经对这几个方法做了实现,而且是对实现列表分组至关重要的,如果子类重写了这几个方法,可能会破坏 GroupedRecyclerViewAdapter 的功能。 从前面给出的 GroupedRecyclerViewAdapter 的方法我们可以看到,这些方法其实就是对应 RecyclerView.Adapter 的这 4 个方法的,所以我们直接使用 GroupedRecyclerViewAdapter 提供的方法即可。 RecyclerView.Adapter 中的

    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);

对应 GroupedRecyclerViewAdapter 中的

    //返回头部的布局 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);

这里之所以返回的是布局 id 而不是 ViewHolder ,是因为在 GroupedRecyclerViewAdapter 项目中已经提供了一个通用的 ViewHolder:BaseViewHolder。所以使用者只需要提供布局的 id 即可,不需要自己去实现 ViewHolder。

    @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;
    }

RecyclerView.Adapter 中的

    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position);

对应 GroupedRecyclerViewAdapter 中的

    //绑定头部布局数据。(如果 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);

RecyclerView.Adapter 中的

    public int getItemCount();

对应 GroupedRecyclerViewAdapter 中的

    //返回组的数量
    public abstract int getGroupCount();

    //返回当前组的子项数量
    public abstract int getChildrenCount(int groupPosition);

RecyclerView.Adapter 中的

    public int getItemViewType(int position);

对应 GroupedRecyclerViewAdapter 中的

    //返回头部的 viewType。
    public int getHeaderViewType(int groupPosition);

    //返回尾部的 viewType。
    public int getFooterViewType(int groupPosition) ;

    //返回子项的 viewType。
    public int getChildViewType(int groupPosition, int childPosition) ;

2、对列表操作的注意

RecyclerView.Adapter 提供了一系列对列表进行操作的方法。如:

//更新操作
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);

在 GroupedRecyclerViewAdapter 不建议使用 RecyclerView.Adapter 的任何对列表的操作方法,因为这些方法都是基于列表的操作,它的 position 是相对于整个列表而言的,而 GroupedRecyclerViewAdapter 是分组的列表,它对列表的操作应该是基于组的。同时 GroupedRecyclerViewAdapter 使用了组结构来维护整个列表的结构,使我们可以对列表进行组的操作,在列表发生变化时 GroupedRecyclerViewAdapter 需要及时对组结构进行调整,如果使用了 RecyclerView.Adapter 中的方法对列表进行更新,GroupedRecyclerViewAdapter 可能因为无法及时调整组结构而发生异常。所以在使用中应该避免使用这些方法。GroupedRecyclerViewAdapter 同样提供了一系列对列表进行操作的方法,我们应该使用 GroupedRecyclerViewAdapter 所提供的方法。

     //****** 刷新操作 *****//

    //通知数据列表刷新。对应 notifyDataSetChanged();
    public void notifyDataChanged();

    //通知一组数据刷新,包括组头,组尾和子项
    public void notifyGroupChanged(int groupPosition);

    //通知多组数据刷新,包括组头,组尾和子项
    public void notifyGroupRangeChanged(int groupPosition, int count);

    // 通知组头刷新
    public void notifyHeaderChanged(int groupPosition);

    // 通知组尾刷新
    public void notifyFooterChanged(int groupPosition);

    // 通知一组里的某个子项刷新
    public void notifyChildChanged(int groupPosition, int childPosition);

    // 通知一组里的多个子项刷新
    public void notifyChildRangeChanged(int groupPosition, int childPosition, int count);

    // 通知一组里的所有子项刷新
    public void notifyChildrenChanged(int groupPosition);

    //****** 删除操作 *****//
    // 通知所有数据删除
    public void notifyDataRemoved();

    // 通知一组数据删除,包括组头,组尾和子项
    public void notifyGroupRemoved(int groupPosition);

    // 通知多组数据删除,包括组头,组尾和子项
    public void notifyGroupRangeRemoved(int groupPosition, int count);

    // 通知组头删除
    public void notifyHeaderRemoved(int groupPosition);

    // 通知组尾删除
    public void notifyFooterRemoved(int groupPosition);

    // 通知一组里的某个子项删除
    public void notifyChildRemoved(int groupPosition, int childPosition);

    // 通知一组里的多个子项删除
    public void notifyChildRangeRemoved(int groupPosition, int childPosition, int count);

    // 通知一组里的所有子项删除
    public void notifyChildrenRemoved(int groupPosition);

    //****** 插入操作 *****//
    // 通知一组数据插入
    public void notifyGroupInserted(int groupPosition);

    // 通知多组数据插入
    public void notifyGroupRangeInserted(int groupPosition, int count);

    // 通知组头插入
    public void notifyHeaderInserted(int groupPosition);

    // 通知组尾插入
    public void notifyFooterInserted(int groupPosition);

    // 通知一个子项到组里插入
    public void notifyChildInserted(int groupPosition, int childPosition);

    // 通知一组里的多个子项插入
    public void notifyChildRangeInserted(int groupPosition, int childPosition, int count);

    // 通知一组里的所有子项插入
    public void notifyChildrenInserted(int groupPosition);

3、使用 GridLayoutManager 的注意

如果要使用 GridLayoutManager,一定要使用项目中所提供的 GroupedGridLayoutManager。因为分组列表如果要使用 GridLayoutManager 实现网格布局,就要保证组的头部和尾部是要单独占用一行的。否则组的头、尾可能会跟子项混着一起,造成布局混乱。同时 GroupedGridLayoutManager 提供了对子项的 SpanSize 的修改方法,使用 GroupedGridLayoutManager 可以实现更多的复杂列表布局。

    //直接使用 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);

4、BaseViewHolder 的使用

项目中提供了一个通用的 ViewHolder:BaseViewHolder。提供了根据 viewId 获取 View 的方法和对 View、TextView、ImageView 的常用设置方法。

//根据 id 获取 View
TextView  textView = holder.get(R.id.tv_header);

//View、TextView、ImageView 的常用设置方法。并且支持方法连缀调用
holder.setText(R.id.tv_header, "内容")
                .setImageResource(R.id.iv_image, 资源 id)
                .setBackgroundRes(R.id.view,资源 id);

BaseViewHolder 是可以通用的,在普通的 Adapter 中也可以使用,可以省去每次都要创建 ViewHolder 的麻烦。

5、头部悬浮吸顶功能

应一些朋友的反馈,我在 1.2.0 版本中新加了列表的头部悬浮吸顶功能。使用起来非常的简单,只需要用框架里提供的 StickyHeaderLayout 包裹一下你的 RecyclerView 就可以了。当然,你需要使用 GroupedRecyclerViewAdapter 才能看到效果。

    <!-- 用 StickyHeaderLayout 包裹 RecyclerView -->
    <com.donkingliang.groupedadapter.widget.StickyHeaderLayout
        android:id="@+id/sticky_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </com.donkingliang.groupedadapter.widget.StickyHeaderLayout>

StickyHeaderLayout 提供了一个设置是否显示悬浮吸顶的方法。

    //是否吸顶,默认为 true。
    stickyLayout.setSticky(true);

6、使用 DataBinding

GroupedRecyclerViewAdapter 在 1.3.0 版本加入了对 DataBinding 的支持。要想在 Adapter 中使用 DataBinding,只需要在 GroupedRecyclerViewAdapter 的构造函数的 useBinding 参数传 true 即可。

public class BindingAdapter extends GroupedRecyclerViewAdapter {

    public BindingAdapter(Context context) {
        //只要在这里传 true,Adapter 就会用 DataBinding 的方式加载列表的 item 布局。默认为 false。
        super(context, true);
    }
}

然后同过 BaseViewHolder 的 getBinding()就可以获取到 item 对应的 ViewDataBinding 对象。

    @Override
    public void onBindHeaderViewHolder(BaseViewHolder holder, int groupPosition) {
        //获取 ViewDataBinding 对象。
        AdapterBindingHeaderBinding binding = holder.getBinding();
    }

    @Override
    public void onBindFooterViewHolder(BaseViewHolder holder, int groupPosition) {
        //获取 ViewDataBinding 对象。
        AdapterBindingFooterBinding binding = holder.getBinding();
    }

    @Override
    public void onBindChildViewHolder(BaseViewHolder holder, int groupPosition, int childPosition) {
        //获取 ViewDataBinding 对象。
        AdapterBindingChildBinding binding = holder.getBinding();
    }
Support Me
Apps
About Me
Google+: Trinea trinea
GitHub: Trinea