ImitatioRrecyclers 一个简单通用的recycleView

一个简单通用的recycleView

突然想对recycleView进行一次整体的封装,放入到项目中来使用,首先为什么要对它进行封装呢,因为做app用到最多的控件是什么?那就是列表,无论在什么公司列表的使用都不可避免,一个app多到几十上百个列表页也是正常的,但是每个需求的列表可能存在一些差别比如
  • 需要添加个头部,常见添加轮播图等
  • 需要下拉刷新,加载更多
  • 需要分组
  • 分组的列表页也需要刷新和加载更多并添加头部
  • 分组的group点击可展开收缩
  • 像ios那样分组的列表头部悬停到顶端
  • 列表item的展示也是各种各样
如果每个列表的ui都要按需求重写一遍,也不是没有问题,但是实在是太麻烦了,也浪费时间,如果团队开发 每个人使用的实现方式又不一样维护起来很繁琐

在5.0之前有listview gridView 如果分组列表有ExpandableListView,对应着Baseadapter 和 BaseExpandableAdapter,但是在5.0之后recycleView可以代替list到现在列表的展现功能基本都用它了吧

之前公司项目需要重构团队开发组里一位同学封装下recycleView和适配器用起来感觉还不错,这里感谢shizhao同学,但是有些bug功能也不完善 在加载更多是如果数据不满一屏的情况下GridLayoutManager和StaggeredGridLayoutManager处理存在问题,二级分组的列表的需求都无法实现,我在这个基础上又深造了下,这样可以满足大部分列表需求的开发。

先看下一级列表的实现

<com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullSwipeRefresh
        android:id="@+id/pullSwipeRefresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
        <com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullRecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            >
        com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullRecyclerView>

    com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullSwipeRefresh>

其中MyPullSwipeRefresh包含着MyPullRecyclerView,MyPullRecyclerView
就是列表控件,看看MyPullSwipeRefresh里做什么

class MyPullSwipeRefresh extends SwipeRefreshLayout

MyPullSwipeRefresh继承了官方的刷新控件

       // 记录viewPager是否拖拽的标记
       case MotionEvent.ACTION_MOVE:
                // 如果viewpager正在拖拽中,那么不拦截它的事件,直接return false;
                if(mIsVpDragger) {
                    return false;
                }

                // 获取当前手指位置
                float endY = ev.getY();
                float endX = ev.getX();
                float distanceX = Math.abs(endX - startX);
                float distanceY = Math.abs(endY - startY);
                // 如果X轴位移大于Y轴位移,那么将事件交给viewPager处理。
                if(distanceX > mTouchSlop && distanceX > distanceY) {
                    mIsVpDragger = true;
                    return false;
                }

这里解决了头部是viewpager轮播图时下拉刷新和滑动轮播的图的手势冲突

看看MyRecyclerView

 MyPullRecyclerView extends MyRecyclerView
 MyRecyclerView extends RecyclerView

        dividerHeight = attr.getInteger(R.styleable.MyRecyclerView_divider_decoration_height, 2);
        dividerWidth = attr.getInteger(R.styleable.MyRecyclerView_divider_decoration_width, 0);
        dividerColor = attr.getInteger(R.styleable.MyRecyclerView_divider_decoration_color, ContextCompat.getColor(context, R.color.line_d));
        dividerDrawable = attr.getInteger(R.styleable.MyRecyclerView_divider_decoration_drawable, 0);

         layoutManagerType = attr.getInt   (R.styleable.MyRecyclerView_layoutManagerType, 0);

在MyRecyclerView里指定了一些属性 包括分割线的样式,默认有条灰色的分割线,又判断了RecyclerView得ManagerType

  app:divider_decoration_width="0"
  app:divider_decoration_height="0"
  app:layoutManagerType="linear_vertical"

divider_decoration_height和divider_decoration_width指定为0就看不见分割线了
layoutManagerType 可以不写 在代码中也不用创建layoutManager MyRecyclerView 默认是线性垂直的

看看主要的MyPullRecyclerView

  private OnAddMoreListener addMoreListener;

   @Override
   public void onScrolled(int dx, int dy)

   addMoreListener.addMoreListener();
   myAdapter.setLoading(true);
   myAdapter.getSwipeRefresh().setEnabled(false);

一个加载更多的接口回调,重写onScrolled判断是否加载更多,在加载更多时候调用了这行代码myAdapter.getSwipeRefresh().setEnabled(false); 这是拿到了下拉刷新控件并把他设置为不可用,这是因为在加载更多数据时 不能下拉刷新避免出现操作同一数据源的错误。

继续实现

 private MyPullSwipeRefresh pullSwipeRefresh;
 private MyPullRecyclerView recyclerView;
 private PullRefreshAdapter adapter;
 private ArrayList arrayList =new ArrayList<>();
 private Demo1Delegate delegate;

 pullSwipeRefresh = (MyPullSwipeRefresh) findViewById(R.id.pullSwipeRefresh);
 recyclerView = (MyPullRecyclerView) findViewById(R.id.recyclerView);
 **重点内容**
 delegate = new Demo1Delegate();
 adapter = new PullRefreshAdapter(this,arrayList,1,delegate);
 recyclerView.setAdapter(adapter);
 adapter.setSwipeRefresh(pullSwipeRefresh);

adapter.setSwipeRefresh(pullSwipeRefresh);这个行代码是把下拉刷新控件传过去以至于在加载更多的时候设置为不可用

这里有一个PullRefreshAdapter和Demo1Delegate
adapter = new PullRefreshAdapter(this,arrayList,1,delegate); 创建适配器参数分别为

  • 上下文对象
  • 数据集合 这时这个集合是空的集合,但是不会影响后面的数据的显示,泛型你指定的实体类
  • 1 是header即添加头部,没有头部可不写这个参数也可以写0
  • delegate 这个是你自己创建的绑定数据显示数据都是靠它 后面在解释,这个就好比自己创建的list适配器 只是这个名不好理解,我刚开始想把这个去掉换成还是创建adapter但项目里已经用了,要改好多所以没动。
注意PullRefreshAdapte的泛型要是你自己指定的实体对象

看看构造方法

 public PullRefreshAdapter(Context context, List strings, int headerCount, BaseDelegate baseDelegate)

之前说数据集合是空的 那么什么时候添加数据什么时候显示

private  void initData(){
        ArrayList initList = new ArrayList<>();
        for (int i = 0; i <20 ; i++) {
            Demo1 demo1 = new Demo1();
            demo1.setName("第"+i+"条");
            initList.add(demo1);
        }
        adapter.setTotalPage(2);
        adapter.setPullData(initList);
    }

这里又创建了新的集合,其实这很符合从服务器拿到数据在显示的流程
adapter.setTotalPage(2); 这是设置总页数这个必须指定 默认为0 设置为2就是可以加载两页,你可以设置成服务器返回的总页数
看看adapter.setPullData(initList);内部的实现

  if (DEFAULT_PAGE == pageIndex) {
            resetData(data);
            if (mySwipeRefresh != null) {
                mySwipeRefresh.setRefreshing(false);
            }
        } else {
            addData(data);
            isLoading = false;
            mySwipeRefresh.setEnabled(true);
        }

根据页数判断 如果是第一页数据 就重置数据 如果不是就添加新数据,因为下拉刷新和加载更多 拿到数据后都会调用

adapter.setTotalPage(2);
adapter.setPullData(initList);

这两行代码
那这个页数是怎么管理的呢

 recyclerView.setOnAddMoreListener(new MyPullRecyclerView.OnAddMoreListener()
 adapter.addPageIndex();

在加载更多的监听事件中 首先调用addPageIndex()方法

public void addPageIndex() {
   this.pageIndex++;
 }

pageIndex自动加1 ,那么你在请求服务器传递的页数时就调用下面代码获得当前页数

adapter.getPageIndex();

如果不需要加载更多功能可以设置adapter.setTotalPage(0);或者直接用MyRecyclerView而不是MyPullRecyclerView

下拉刷新时


pullSwipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
   @Override
    public void onRefresh() {
     adapter.resetPageIndex();

先调用adapter.resetPageIndex();把页数设置为0

最后看看Demo1Delegate

public class Demo1Delegate extends BaseDelegate<Demo1> {

    public Demo1Delegate() {
        super(R.layout.header, R.layout.item);
    }
    @Override
    public void initHeaderView(BaseViewHolder holder) {
        TextView header = holder.findViewById(R.id.header);
        header.setText("我是header");
        super.initHeaderView(holder);
    }
    @Override
    public void initCustomView(BaseViewHolder holder, List data, int position) {
        TextView str =  holder.findViewById(R.id.str);
        str.setText(data.get(position).getName());
        super.initCustomView(holder, data, position);
    }
}

R.layout.header, R.layout.item 是头部不布局和 列表item布局
initHeaderView initCustomView方法相当于listview 的 getView和recycleView的bindView方法

注意PullRefreshDelegate泛型是你自己指定的实体类对象 initCustomView方法第二参数会返回数据集合,看下怎么实现的
class BaseRecyclerAdapter<T> extends RecyclerView.Adapter<BaseViewHolder>
 @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return BaseViewHolder.creatViewHolder(mContext, parent, this.baseDelegate.getLayoutId(viewType));
    }

 @Override
    public void onBindViewHolder(final BaseViewHolder holder, final int position) {

        this.baseDelegate.initView(holder, mData, position - headerCount, getItemViewType(position));

这俩方法大家都不陌生

baseDelegate.initView方法里:

 if (viewType == BaseRecyclerAdapter.RECYCLE_TYPE_HEADER) {
            initHeaderView(holder);
        } else if (viewType == BaseRecyclerAdapter.RECYCLE_TYPE_FOOTER) {
            initFooterView(holder, position);
        } else {
            initCustomView(holder, obj, position);
        }

明白了吧 所以继承baseDelegate 实现initXXX方法
最后说下点击事件调用

adapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener()

ImitatioRrecyclers 一个简单通用的recycleView_第1张图片

二级列表的实现

recycleView实现分组效果网上很多资料直接用getItemViewType方式 甚至可以直接根据数据源操作layou的隐藏和显示,用过ExpandableListView都知道这种效果很简单,使用ExpandableListView 的数据源就是一个group对应一个集合的child,我认为这总思路实现起来很好理解 数据源处理上也很清晰,现在就像使用ExpandableListView一样使用recycleView实现分组列表

 <com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullSwipeRefresh
        android:id="@+id/pullSwipeRefresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
        <com.myrecyclers.tcy.imitationrecyclerslibrary.ExpandablePullRecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:divider_decoration_width="0"
            app:divider_decoration_height="0"
            >
        com.myrecyclers.tcy.imitationrecyclerslibrary.ExpandablePullRecyclerView>

    com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullSwipeRefresh>

跟之前一样但这里要改成ExpandablePullRecyclerView

 delegate = new Demo2Delegate(this);
 adapter = new ExpandablePullRefreshAdapter(this,demo2Groups,1,delegate);
 recyclerView.setAdapter(adapter);
 adapter.setSwipeRefresh(pullSwipeRefresh);
 adapter.setClickGroup(true);

同样这里创建适配器的时候指定 1个header
adapter.setClickGroup(true);方法默认为true 是否可以点击头部展开收缩

public class Demo2Delegate extends ExpandableBaseDelegate
    @Override
    public int getGroupCount()
     @Override
    public int getChildCount(int groupPosition)

    @Override
    public Object getGroup(int groupPosition)
     @Override
    public Object getChild(int groupPosition, int childPosition)

创建delegate 继承ExpandableBaseDelegate
注意:这里的泛型同样是你自己定义好的实体

看看上面4个方法是不是跟BaseExpandableAdapter里的方法很相像,再看看我的实体类

public class Demo2Group {

     private String name;
     private ArrayList list;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public ArrayList getList() {
        return list;
    }

    public void setList(ArrayList list) {
        this.list = list;
    }
}

这样的数据源一看就非常的清晰

 @Override
    public void initHeaderView(ExpandableBaseViewHolder holder)
     @Override
    public void initGroupView(ExpandableBaseViewHolder holder, ExpandableBaseRecyclerAdapter.GroupModel groupModel, Object groupObject, int position)
    public void initChildView(ExpandableBaseViewHolder holder, Object groupObject, Object childObject, int position)
    **重点内容**
    public void setArrayList(ArrayList arrayList) {
        this.arrayList = arrayList;
    }

上面有三个方法时绑定显示数据的方法

  • initHeaderView 绑定头部 和一级列表的一样
  • initGroupView 看名字就是绑group数据 里面的参数返回 groupModel是可以获取组的状态可以最常用的是可以 groupModel.isOpen();此组是否展开,因为有的需求是展开后有个箭头的ui变化。 groupObject是组的实体类对象,可以强转成你的group实体 position是group的索引
  • initChildView groupObject 是child对应的group对象一般不常用,如果操作child的时候group有变化的需求才会用到 childObject是是child实体对象同意强转使用 position是索引
  • setArrayList方法 这个方法一定要写 一定要写 一定要写说三遍, 在每次获得数据的时候无论是加载更多还是刷新 还是初始化数据都要调用这个方法 这个方试为了获得数据源让getGroupCount这样的方法有值返回好在ExpandableBaseRecyclerAdapter里进行数据分组

看看groupModel

 public class GroupModel {
        boolean isOpen;
        boolean isShow;
        Object mObject;
        int groupPostion;
        List childList;

这个GroupModel 我也参考了网上别人的信息 分别为是否展开 是否显示 group的实体对象 group的索引 child集合 有用的基本都全了

得到数据后

 **重点内容**
  delegate.setArrayList(groups);
  adapter.setTotalPage(2);
  adapter.setPullData(groups);

在获得数据后的调用除了要写setArrayList(groups) 同样要设置总页数和设置数据

看看ExpandableBaseRecyclerAdapter:

 class ExpandableBaseRecyclerAdapter<T> extends RecyclerView.Adapter<ExpandableBaseViewHolder> {
 public static final int RECYCLE_TYPE_ITEM_GROUP = 1003; //item group类型
 public  static final int RECYCLE_TYPE_ITEM_CHILD = 1000; //item child 类型
 public static final int RECYCLE_TYPE_HEADER = 1001; //头部类型
 public  static final int RECYCLE_TYPE_FOOTER = 1002;//底部类型

这里多两个类型group child 同样在getItemViewType里要判断好类型

  public void resetData(List newData) {
        if (this.mData != null && newData != null) {
            this.mData.clear();
            ArrayList dataList = new ArrayList();
            for (int i = 0; i < baseDelegate.getGroupCount(); i++) {
                GroupModel groupModel = new GroupModel(true, true,baseDelegate.getGroup(i),i);
                dataList.add(groupModel);
                for (int j = 0; j < baseDelegate.getChildCount(i); j++) {
                    groupModel.childList.add(baseDelegate.getChild(i, j));
                }
            }
            this.mData.addAll(dataList);
            notifyDataSetChanged();
        }
    }

在你设置数据后 这里又用GroupModel 给你包了一层 然后在onBindViewHolder方法里给你返回对应的groupModel

点击事件:

        adapter.setOnItemClickListener(new ExpandableBaseRecyclerAdapter.OnItemClickListener() {
            @Override
            public void onGroupItemClick(View view, int groupPosition) {

            }

            @Override
            public void onChildItemClick(View view, int groupPosition, int childPosition) {

            }
        });

这里的groupPosition childPosition用过ExpandableListView一看就明白了

有的页面需求会有分组列表悬停吸顶效果,网上实现这种效果基本上有两种方式,第一种就是RecyclerView.ItemDecoration的方式,绘制分组,但是我这种分组逻辑应该不在适应这种方式,所以我用第二种更简单的方式实现

 <com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullSwipeRefresh
        android:id="@+id/pullSwipeRefresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
        "match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <com.myrecyclers.tcy.imitationrecyclerslibrary.ExpandablePullRecyclerView
                android:id="@+id/recyclerView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:divider_decoration_height="0"
                app:divider_decoration_width="0"
                >

            com.myrecyclers.tcy.imitationrecyclerslibrary.ExpandablePullRecyclerView>
            **重点内容**    
            "match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">
                "@layout/header">
                "@layout/item_group">
            

        
    com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullSwipeRefresh>

添加一个FrameLayout 如果有头部就把头部布局和group布局都放在一个线性布局里,如果没有头部就直接引用 group布局

   header_ly = (RelativeLayout) findViewById(R.id.header_ly);
   header_ly.setVisibility(View.INVISIBLE);
   group_ly = (LinearLayout) findViewById(R.id.group_ly);
   group_ly.setVisibility(View.INVISIBLE);
   str = (TextView) findViewById(R.id.str);
**重点内容**
   recyclerView.setHeaderView(header_ly);
   recyclerView.setStickyheaderView(group_ly);
   recyclerView.setStickyheaderText(str);

   adapter.setClickGroup(false);

如果有头部 把头部布局和group布局设置为INVISIBLE 并设置三个方法 如果没有头部不需要设置setVisibility 不调用setHeaderView方法

设置setClickGroup方法使group不可点击,因为悬停了点击功能就没有必要了,你如果又要点击收缩效果又要悬停效果的话,自己要写group_ly的点击事件因为悬停在最上面的group是个临时的 不是真正的 item group

在你的initGroupView initChildView方法里设置

  **重点内容**  
  holder.itemView.setContentDescription(demo2Group.getName());

注意:分组的列表只试用于LinearLayoutManager类型

github:https://github.com/tangchaoyu/ImitatioRecyclers

你可能感兴趣的:(ImitatioRrecyclers 一个简单通用的recycleView)