RecyclerView 万能适配器

一、框架引入

  • 先在项目的 build.gradle(Project:XXXX) 的 repositories 添加:
    allprojects {
        repositories {
            ...
            maven { url "https://jitpack.io" }
        }
    }
  • 然后在Module的 build.gradle(Module:app) 的 dependencies 添加:
    dependencies {
            ......
            compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.22'
    }

2.2 最基本使用示例Demo

  • 要是刚接触Android的朋友要是还是不知道怎么用,那我这里提供一个很简单的demo:

  • 比如说我想要实现的效果大致如下:

     

  • 第一步:在布局文件中引入RecyclerView
    activity_main.xml

  • <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </RelativeLayout>
    
  • 第二步:编写条目布局文件
    item_rv.xml(省略)
     
  • 第三步:编写数据实体类型
    Model.java
public class Model {
    private String title;
    private String content;
    private String imgUrl;

    //生成set、get方法
    ......
}
  • 第四步:编写适配器
    MyAdapter.java
public class MyAdapter extends BaseQuickAdapter<Model, BaseViewHolder> {

    public MyAdapter(@LayoutRes int layoutResId, @Nullable List<Model> data) {
        super(layoutResId, data);
    }

    @Override
    protected void convert(BaseViewHolder helper, Model item) {
        //可链式调用赋值
        helper.setText(R.id.tv_title, item.getTitle())
                .setText(R.id.tv_content, item.getContent())
                .setImageResource(R.id.iv_img, R.mipmap.ic_launcher);

        //获取当前条目position
        //int position = helper.getLayoutPosition();
    }
}
  • 最后一步:在Activity中使用该适配器
    MainActivity.java
public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private List<Model> datas;
    private MyAdapter adapter;

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

        //初始化RecyclerView
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        //模拟的数据(实际开发中一般是从网络获取的)
        datas = new ArrayList<>();
        Model model;
        for (int i = 0; i < 15; i++) {
            model = new Model();
            model.setTitle("我是第" + i + "条标题");
            model.setContent("第" + i + "条内容");
            datas.add(model);
        }

        //创建布局管理
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);

        //创建适配器
        adapter = new MyAdapter(R.layout.item_rv, datas);

        //给RecyclerView设置适配器
        recyclerView.setAdapter(adapter);
    }
}
  • OK,运行,得到的效果就是上述的效果。

三、点击事件

  • 上文中描述的就是使用BaseRecyclerViewAdapterHelper最基本的用法,因为怕刚接触Android的兄弟们不明朗或不相信这么简单的用法,所以做了上节简单的demo示例用法。

  • 那么使用列表当然少不了点击事件,不论是整个条目的点击事件还是条目中子控件的点击事件,该适配器对点击事件也是做了及简化的处理:

3.1 条目事件

Item的点击事件

        //条目点击事件
        adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(BaseQuickAdapter adapter, View view, int position) {

                Toast.makeText(MainActivity.this, "点击了第" + (position + 1) + "条条目", Toast.LENGTH_SHORT).show();
            }
        });

Item的长按事件

        //条目长按事件
        adapter.setOnItemLongClickListener(new BaseQuickAdapter.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position) {

                Toast.makeText(MainActivity.this, "长按了第" + (position + 1) + "条条目", Toast.LENGTH_SHORT).show();
                return false;
            }
        });

注意事项

  • 在嵌套recycleView的情况下需要使用你使用 adapter. setOnItemClickListener 来设置点击事件,如果使用recycleView.addOnItemTouchListener会累计添加的。

3.2 条目子控件事件

Item子控件的点击事件

  • 首先:在adapter的convert方法里面通过 helper.addOnClickListener 绑定一下子控件的控件id
    @Override
    protected void convert(BaseViewHolder helper, Model item) {
        //可链式调用赋值
        helper.setText(R.id.tv_title, item.getTitle())
                .setText(R.id.tv_content, item.getContent())
                .addOnClickListener(R.id.iv_img)    //给图标添加点击事件
                .setImageResource(R.id.iv_img, R.mipmap.ic_launcher);
        //获取当前条目position
        //int position = helper.getLayoutPosition();
    }
  • 然后:我们设置
        //条目子控件点击事件
        adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
            @Override
            public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {

                Toast.makeText(MainActivity.this, "点击了第" + (position + 1) + "条条目的图片", Toast.LENGTH_SHORT).show();
            }
        });

Item子控件的长按事件

  • 这里和子控件点击事件是一样的,只是将 点击 变成 长按 就可以了

  • 首先:在adapter的convert方法里面通过 helper.addOnLongClickListener 绑定一下子控件的控件id

    @Override
    protected void convert(BaseViewHolder helper, Model item) {
        //可链式调用赋值
        helper.setText(R.id.tv_title, item.getTitle())
                .setText(R.id.tv_content, item.getContent())
                //.addOnClickListener(R.id.iv_img)    //给图标添加点击事件
                .addOnLongClickListener(R.id.iv_img)//给图片添加长按事件
                .setImageResource(R.id.iv_img, R.mipmap.ic_launcher);
        //获取当前条目position
        //int position = helper.getLayoutPosition();
    }
  • 然后:我们设置
        //条目子控件长按事件
        adapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() {
            @Override
            public boolean onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) {

                Toast.makeText(MainActivity.this, "长按了第" + (position + 1) + "条条目的图片", Toast.LENGTH_SHORT).show();
                return false;
            }
        });

注意事项

  • 设置子控件的事件,如果不在adapter中绑定,点击事件无法生效,因为无法找到你需要设置的控件。

多个Item子控件事件

  • 官方文档上没说,但是 其实这里可以用很常规的方法处理,就是通过判断ID来判定是否是我要的控件?,从而处理不同的事件

  • 比如我这里给 图片和标题 都加点击事件处理不同的逻辑

  • 首先:当然是在适配器中给图片和标题都添加点击事件

 @Override
    protected void convert(BaseViewHolder helper, Model item) {
        //可链式调用赋值
        helper.setText(R.id.tv_title, item.getTitle())
                .setText(R.id.tv_content, item.getContent())
                .addOnClickListener(R.id.iv_img)    //给图标添加 点击事件
                .addOnClickListener(R.id.tv_title)  //给标题也添加 点击事件
                .setImageResource(R.id.iv_img, R.mipmap.ic_launcher);
        //获取当前条目position
        //int position = helper.getLayoutPosition();
    }
  • 其次:
        //条目子控件点击事件
        adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
            @Override
            public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {

                //判断id
                if (view.getId() == R.id.iv_img) {
                    Log.i("tag", "点击了第" + position + "条条目的 图片");
                } else if (view.getId() == R.id.tv_title) {
                    Log.i("tag", "点击了第" + position + "条条目的 标题");
                }
            }
        });
  • 那么如果是长按事件呢?当然也是相同的处理方法。

如果需要在子控件事件中获取其他子控件可以使用:

getViewByPosition(RecyclerView recyclerView, int position, @IdRes int viewId)

比如:

        //条目子控件点击事件
        adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
            @Override
            public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {

                Toast.makeText(MainActivity.this, "点击了第" + (position + 1) + "条条目的图片", Toast.LENGTH_SHORT).show();

                TextView tv_title = (TextView) adapter.getViewByPosition(recyclerView, position, R.id.tv_title);
                Log.i("tag", "当前图片对应的 title=" + tv_title.getText());
            }
        });

        //条目子控件长按事件
        adapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() {
            @Override
            public boolean onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) {

                Toast.makeText(MainActivity.this, "长按了第" + position + "条条目的图片", Toast.LENGTH_SHORT).show();
                TextView tv_title = (TextView) adapter.getViewByPosition(recyclerView, position, R.id.tv_title);
                Log.i("tag", "长按的图片对应的title=" + tv_title.getText());

                return false;
            }
        });

注意:如果有header的话需要处理一下position加上 headerlayoutcount。

  • 最后,到这里,点击事件基本就完了

五、添加头部、尾部

  • 添加
    (可以添加多个头部或尾部)
mQuickAdapter.addHeaderView(headerView);
mQuickAdapter.addFooterView(footerView);
  • 删除指定view
mQuickAdapter.removeHeaderView(headerView);
mQuickAdapter.removeFooterView(footerView);
  • 删除所有
mQuickAdapter.removeAllHeaderView();
mQuickAdapter.removeAllFooterView();
  • 默认出现了头部就不会显示Empty,和尾部,配置以下方法也支持同时显示:
mQuickAdapter.setHeaderAndEmpty()
mQuickAdapter.setHeaderFooterEmpty();
  • 默认头部尾部都是占满一行,如果需要不占满可以配置:
mQuickAdapter.setHeaderViewAsFlow();
mQuickAdapter.setFooterViewAsFlow();

六、上拉加载

  • 没错,该适配器居然还实现了加载更多的功能,真心佩服作者

  • 设置上拉加载

// 滑动最后一个Item的时候回调onLoadMoreRequested方法
setOnLoadMoreListener(RequestLoadMoreListener);
  • 默认第一次加载会进入回调,如果不需要可以配置:
mQuickAdapter.disableLoadMoreIfNotFullPage();
  • 回调处理代码
//上拉加载(设置这个监听就表示有上拉加载功能了)
mQuickAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
            @Override public void onLoadMoreRequested() {
                mRecyclerView.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (mCurrentCounter >= TOTAL_COUNTER) {
                            //数据全部加载完毕
                            mQuickAdapter.loadMoreEnd();
                        } else {
                            if (isErr) {
                                //成功获取更多数据(可以直接往适配器添加数据)
                                mQuickAdapter.addData(DataServer.getSampleData(PAGE_SIZE));
                                mCurrentCounter = mQuickAdapter.getData().size();
                                //主动调用加载完成,停止加载
                                mQuickAdapter.loadMoreComplete();
                            } else {
                                //获取更多数据失败
                                isErr = true;
                                Toast.makeText(PullToRefreshUseActivity.this, R.string.network_err, Toast.LENGTH_LONG).show();
                                //同理,加载失败也要主动调用加载失败来停止加载(而且该方法会提示加载失败)
                                mQuickAdapter.loadMoreFail();

                            }
                        }
                    }

                }, delayMillis);
            }
        }, mReyclerView);
  • 加载完成(注意不是加载结束,而是本次数据加载结束并且还有下页数据)
mQuickAdapter.loadMoreComplete();
  • 加载失败
mQuickAdapter.loadMoreFail();
  • 加载结束
mQuickAdapter.loadMoreEnd();

注意:如果上拉结束后,下拉刷新需要再次开启上拉监听,需要使用setNewData方法填充数据。

  • 打开或关闭加载(一般用于下拉的时候做处理,因为上拉下拉不能同时操作)
mQuickAdapter.setEnableLoadMore(boolean);
  • 预加载(这个功能屌炸天)
// 当列表滑动到倒数第N个Item的时候(默认是1)回调onLoadMoreRequested方法
mQuickAdapter.setPreLoadNumber(int);

设置自定义加载布局

mQuickAdapter.setLoadMoreView(new CustomLoadMoreView());
public final class CustomLoadMoreView extends LoadMoreView {

    @Override 
    public int getLayoutId() {
        return R.layout.view_load_more;
    }

    /**
     * 如果返回true,数据全部加载完毕后会隐藏加载更多
     * 如果返回false,数据全部加载完毕后会显示getLoadEndViewId()布局
     */
    @Override 
    public boolean isLoadEndGone() {
        return true;
    }

    @Override 
    protected int getLoadingViewId() {
        return R.id.load_more_loading_view;
    }

    @Override 
    protected int getLoadFailViewId() {
        return R.id.load_more_load_fail_view;
    }

    /**
     * isLoadEndGone()为true,可以返回0
     * isLoadEndGone()为false,不能返回0
     */
    @Override 
    protected int getLoadEndViewId() {
        return 0;
    }
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/dp_40">

    <LinearLayout
        android:id="@+id/load_more_loading_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/loading_progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_marginRight="@dimen/dp_4"
            android:indeterminateDrawable="@drawable/sample_footer_loading_progress"/>

        <TextView
            android:id="@+id/loading_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="@dimen/dp_4"
            android:text="@string/loading"
            android:textColor="#0dddb8"
            android:textSize="@dimen/sp_14"/>
    </LinearLayout>

    <FrameLayout
        android:id="@+id/load_more_load_fail_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone">


        <TextView
            android:id="@+id/tv_prompt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:textColor="#0dddb8"
            android:text="@string/load_failed"/>

    </FrameLayout>

</FrameLayout>

九、多布局

  • 多种类型条目的列表是我们日常开发中非常常见的功能,当然该适配器也给我们提供了相应的方法。

  • 使用该适配器设置不同类型条目有两种方式,一种 耦合了实体类,一种是设置代理,这里两种方式我都会演示一遍。

Demo实例演示

  • 从上述文档中我们是可以看出来两点

1.我们的数据不直接传给适配器,而是通过MultiItemEntity来传递
2.适配器构造里必须绑定type和layout的关系

  • 废话不多说,演示一个小demo(这里Item布局我就不贴出来了)

第一步:创建MultiItemEntity类

public class MyMultipleItem implements MultiItemEntity {
    public static final int FIRST_TYPE = 1;
    public static final int SECOND_TYPE = 2;
    public static final int NORMAL_TYPE = 3;

    private int itemType;
    private Model data;

    public MyMultipleItem(int itemType, Model data) {
        this.itemType = itemType;
        this.data = data;
    }

    @Override
    public int getItemType() {
        return itemType;
    }

    public Model getData(){
        return data;
    }
}

第二步:创建适配器

public class MultipleItemAdapter extends BaseMultiItemQuickAdapter<MyMultipleItem, BaseViewHolder> {
    
    public MultipleItemAdapter(List data) {
        super(data);
        //必须绑定type和layout的关系
        addItemType(MyMultipleItem.FIRST_TYPE, R.layout.first_type_layout);
        addItemType(MyMultipleItem.SECOND_TYPE, R.layout.second_type_layout);
        addItemType(MyMultipleItem.NORMAL_TYPE, R.layout.item_rv);

    }

    @Override
    protected void convert(BaseViewHolder helper, MyMultipleItem item) {
        switch (helper.getItemViewType()) {
            case MyMultipleItem.FIRST_TYPE:
                Log.i("tag","FIRST_TYPE==============="+helper.getLayoutPosition());
                break;
            case MyMultipleItem.SECOND_TYPE:
                Log.i("tag","SECOND_TYPE==============="+helper.getLayoutPosition());
                break;
            case MyMultipleItem.NORMAL_TYPE:
                helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
                        .setText(R.id.tv_title, item.getData().getTitle())
                        .setText(R.id.tv_content, item.getData().getContent());
                break;
        }
    }
}

第三步:最后当然是在我们的Activity里编写代码

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private List<Model> datas01;
    private List<MyMultipleItem> datas02;
    private MultipleItemAdapter adapter;

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

        //初始化RecyclerView
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        //模拟的假数据(实际开发中当然是从网络获取数据)
        datas01 = new ArrayList<>();
        Model model;
        for (int i = 0; i < 30; i++) {
            model = new Model();
            model.setTitle("我是第" + i + "条标题");
            model.setContent("第" + i + "条内容");
            datas01.add(model);
        }

        datas02 = new ArrayList<>();
        //这里我是随机给某一条目加载不同的布局
        for (int i = 0; i < 30; i++) {
            if (i % 3 == 0) {
                datas02.add(new MyMultipleItem(MyMultipleItem.FIRST_TYPE, null));
            } else if (i % 7 == 0) {
                datas02.add(new MyMultipleItem(MyMultipleItem.SECOND_TYPE, null));
            } else {
                datas02.add(new MyMultipleItem(MyMultipleItem.NORMAL_TYPE, datas01.get(i)));
            }

        }

        //创建布局管理
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);

        //创建适配器
        adapter = new MultipleItemAdapter(datas02);

        //给RecyclerView设置适配器
        recyclerView.setAdapter(adapter);
    }
}



作者:猜火车_
链接:https://www.jianshu.com/p/1e20f301272e
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

抄录整理:鹿人_甲

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值