Skip to content
景彬 edited this page Jan 6, 2020 · 39 revisions

概述

ByRecyclerView是一个用来处理App中列表展示的框架。它最大的优点在于,RecyclerViewAdatper两者可以分开使用、自带下拉刷新也可使用SwipeRefreshLayout、不足一屏上拉才加载更多。

特性

  • 1.支持 下拉刷新、加载更多
  • 2.可随意切换 自带下拉刷新布局 / SwipeRefreshLayout
  • 3.加载更多机制:手动上拉才执行加载更多
  • 4.可设置自定义 下拉刷新布局 和 加载更多布局
  • 5.添加/移除 HeaderView、FooterView
  • 6.设置各种状态布局 EmptyView / LoadingView / ErrorView
  • 7.添加item的点击/长按事件
  • 8.优化过的BaseAdapter (RecyclerView / ListView),减少大量代码
  • 9.Adapter结合DataBinding使用 (RecyclerView / ListView)
  • 10.可添加万能分隔线(LinearLayout / GridLayout / StaggeredGridLayout)
  • 11.默认使用AndoridX,且支持Support

1 引入及极速设置

1.1 引入

先在 build.gradle 的 repositories 添加

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

然后在dependencies添加

dependencies {
	implementation 'com.github.youlookwhat:ByRecyclerView:1.0.9'         // 默认AndroidX版本
	implementation "com.github.youlookwhat:ByRecyclerView:1.0.9-support" // support版本引入
}

1.2 极速设置

  • 加入布局
<me.jingbin.library.ByRecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:layoutManager="LinearLayoutManager"
    tools:listitem="@layout/item_home" />
  • 使用BaseRecyclerAdapter
public class OneTypeAdapter extends BaseRecyclerAdapter<String> {

    public OneTypeAdapter(List<String> data) {
        super(R.layout.item_main, data);
    }

    @Override
    protected void bindView(BaseByViewHolder<String> holder, String bean, int position) {
        holder.setText(R.id.view_bottom, bean);
    }
}

mAdapter.setNewData(list);   // 设置第一页数据
  • 加载更多监听
mRecyclerView.setOnLoadMoreListener(new ByRecyclerView.OnLoadMoreListener() {
    @Override
    public void onLoadMore() {
         mAdapter.addData(list);            // 设置及刷新数据
         mRecyclerView.loadMoreComplete();  // 加载更多完成 
         mRecyclerView.loadMoreEnd();       // 没有更多内容了
         mRecyclerView.loadMoreFail();      // 加载更多失败
    }
});

2 ByRecyclerView

集合下拉刷新、加载更多、添加/移除HeaderView或FooterView、设置EmptyView等,也可以使用自己定义的BaseAdapter

2.1 使用自带下拉刷新

默认关闭,设置监听后标明启用。也可手动再设置关闭。

mRecyclerView.setOnRefreshListener(new ByRecyclerView.OnRefreshListener() {
    @Override
    public void onRefresh() {
        mAdapter.setNewData(list);  // 设置及刷新数据
    }
});

// 需要在设置监听后设置,设置监听会开启自动刷新
mRecyclerView.setRefreshEnabled(false); 

mRecyclerView.setRefreshing(true);  // 手动启动刷新
mRecyclerView.setRefreshing(false); // 取消刷新重置参数,包括加载更多的参数

2.2 使用加载更多

刷新机制:不满一屏上拉加载更多,满一屏后触底加载更多。 设置监听即表示开启加载更多。不设置默认不开启。

// 想要使用加载更多,必须设置监听或将加载更多开关打开。
// 只打开开关不设置加载更多监听,多出现在只想在列表最后设置`没有更多数据了`的布局。
mRecyclerView.setLoadMoreEnabled(true);

mRecyclerView.setOnLoadMoreListener(new ByRecyclerView.OnLoadMoreListener() {
    @Override
    public void onLoadMore() {
         mAdapter.addData(list);            // 设置及刷新数据(对应adapter方法)
         mRecyclerView.loadMoreComplete();  // 加载更多完成 
         mRecyclerView.loadMoreEnd();       // 没有更多内容了
         mRecyclerView.loadMoreFail();      // 加载更多失败(点击或再次上拉都会再次调用加载更多接口)
    }
}, delayMillis);// delayMillis: 延迟多少毫秒调用接口

2.3 添加item点击事件

mRecyclerView.setOnItemClickListener(new ByRecyclerView.OnItemClickListener() {
    @Override
    public void onClick(View v, int position) {
        DataItemBean itemData = mAdapter.getItemData(position);// 通过adapter获取对应position的数据
    }
});

2.4 添加item长按事件

mRecyclerView.setOnItemLongClickListener(new ByRecyclerView.OnItemLongClickListener() {
    @Override
    public boolean onLongClick(View v, int position) {
        return false;
    }
});

2.5 addHeaderView(多type)

每一个headerView都将对应一个viewType,这将可以通过headerView实现锚点的效果。可传入View或对应布局layout。注意:不可频繁的add和remove

// add
recyclerView.addHeaderView(getView() / layoutId);
// remove
recyclerView.removeHeaderView(getView() / layoutId));
// removeAll
recyclerView.removeAllHeaderView();

// 不显示headerView,调用后需要刷新adapter
recyclerView.setHeaderViewEnabled(false);

2.6 addFooterView

多个footerView只是单一的viewType;可传入View或对应布局layout。

// add
recyclerView.addFooterView(getView() / layoutId));
// remove
recyclerView.removeFooterView(getView() / layoutId));
// removeAll
recyclerView.removeAllFooterView();

// 不显示footerView,调用后需要刷新adapter
recyclerView.setFootViewEnabled(false);

2.7 setStateView(空布局、加载中布局、错误布局)

loadingView / emptyView / errorView 只会存在一个,所以调用这个方法即可

recyclerView.setStateView(getView() / layoutId);

// 不显示stateView,调用后需要刷新adapter
recyclerView.setStateViewEnabled(false);

2.8 设置不满一屏不加载

// 不满一屏无法上拉加载更多
recyclerView.setNotFullScreenNoLoadMore();

2.9 设置加载更多布局底部间距

给加载更多底部增加一个高度,单位dp。这样的设计主要是为了底部如果有透明栏,加载更多布局不会被覆盖。

// 为了底部透明显示
recyclerView.setLoadingMoreBottomHeight(50);

2.10 自定义下拉刷新布局

使用者可以根据项目需求自定义布局,需要继承BaseRefreshHeader

// 设置下拉刷新布局
recyclerView.setRefreshHeaderView(new NeteaseRefreshHeaderView(this));

仿网易云音乐Ios版下拉刷新布局示例:NeteaseRefreshHeaderView

2.11 自定义加载更多布局

自定义加载中,无内容,失败的布局,继承 BaseLoadMore

// 设置加载更多布局
recyclerView.setLoadingMoreView(new NeteaseLoadMoreView(this));

仿网易云音乐Ios版加载更多布局示例:NeteaseLoadMoreView

2.12 添加子View的点击事件

// 首先需要在ViewHolder里添加点击事件
holder.addOnClickListener(R.id.tv_text);

// 然后给recyclerView设置点击监听
recyclerView.setOnItemChildClickListener(new ByRecyclerView.OnItemChildClickListener() {
    @Override
    public void onItemChildClick(View view, int position) {
        switch (view.getId()) {
            case R.id.tv_text:
                break;
            default:
                break;
        }
    }
});

2.13 添加子View的长按事件

// 首先需要在ViewHolder里添加从长按事件
holder.addOnLongClickListener(R.id.tv_text);

// 然后给recyclerView设置长按监听
recyclerView.setOnItemChildLongClickListener(new ByRecyclerView.OnItemChildLongClickListener() {
    @Override
    public void onItemChildLongClick(View view, int position) {
        switch (view.getId()) {
            case R.id.tv_text:
                break;
            default:
                break;
        }
    }
});

3 BaseAdapter

png_adapter_uml.png

目前有三种Adapter,两种ViewHolder:

  • BaseByRecyclerViewAdapter(所有adapter超类)
  • BaseRecyclerAdapter (单类型极简adapter)
  • BaseBindingAdapter (使用了databinding的单类型极简adapter)
  • BaseByViewHolder (所有ViewHolder超类)
  • BaseBindingHolder (使用了databinding的ViewHolder)

简单点说:

  • 多类型展示使用BaseByRecyclerViewAdapter,其中使用的ViewHolder可以是BaseByViewHolderBaseBindingHolder
  • 单类型展示使用BaseRecyclerAdapterBaseBindingAdapter,区别是后者使用了databinding

注意:

  • 因为databinding是在编译时生成对应xml文件的类,所以需要使用者拷贝项目中的binding文件夹里的类。

3.1 单类型列表极简实现

public class OneTypeAdapter extends BaseRecyclerAdapter<DataItemBean> {

    public OneTypeAdapter(List<DataItemBean> data) {
        super(R.layout.item_main, data);
    }

    @Override
    protected void bindView(BaseByViewHolder<DataItemBean> holder, DataItemBean bean, int position) {
        holder.setText(R.id.tv_text, bean.getTitle())
              .addOnClickListener(R.id.tv_text)       // 子view点击事件
              .addOnLongClickListener(R.id.tv_text);  // 子view长按事件
    }
}

3.2 单类型列表(databinding)

public class DataAdapter extends BaseBindingAdapter<DataItemBean, ItemHomeBinding> {

    public DataAdapter() {
        super(R.layout.item_home);
    }

    public DataAdapter(List<DataItemBean> data) {
        super(R.layout.item_home, data);
    }

    @Override
    protected void bindView(BaseBindingHolder holder, ItemHomeBinding binding, DataItemBean bean, int position) {
        binding.tvText.setText(bean.getTitle() + ": " + position);
    }
}

3.3 多类型列表实现

使用 BaseByRecyclerViewAdapter 结合 BaseBindingHolder 或 BaseByViewHolder

public class MultiAdapter extends BaseByRecyclerViewAdapter<DataItemBean, BaseByViewHolder<DataItemBean>> {

    public MultiAdapter(List<DataItemBean> data) {
        super(data);
    }

    @Override
    public int getItemViewType(int position) {
        DataItemBean itemData = getItemData(position);
        if ("title".equals(itemData.getType())) {
            return 1;
        } else {
            return 2;
        }
    }

    @NonNull
    @Override
    public BaseByViewHolder<DataItemBean> onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (1 == viewType) {
            return new TitleHolder(parent, R.layout.item_multi_title);
        } else {
            return new ViewHolder(parent, R.layout.item_home);
        }
    }

    private class TitleHolder extends BaseByViewHolder<DataItemBean> {
        TitleHolder(ViewGroup viewGroup, int layoutId) {
            super(viewGroup, layoutId);
        }

        @Override
        protected void onBaseBindView(BaseByViewHolder<DataItemBean> holder, DataItemBean bean, int position) {
            holder.setText(R.id.tv_title, bean.getDes());
        }
    }

    private class ViewHolder extends BaseBindingHolder<DataItemBean, ItemHomeBinding> {
        ViewHolder(ViewGroup viewGroup, int layoutId) {
            super(viewGroup, layoutId);
        }

        @Override
        protected void onBindingView(BaseBindingHolder holder, DataItemBean bean, int position) {
            binding.tvText.setText(bean.getDes());
        }
    }
}

4 ItemDecoration

4.1 给LinearLayout设置分割线

可设置drawable,也可以直接设置颜色,高度,左右间距。并可以设置头部和尾部不显示item的个数,例如添加了header,不想在此header下添加分割线,则可以通过setHeaderNoShowDivider()方法处理。具体实现类:SpacesItemDecoration

4.1.1 设置drawable

// 第二个参数表示 是纵向还是横向
SpacesItemDecoration itemDecoration = new SpacesItemDecoration(this, SpacesItemDecoration.VERTICAL)
        .setNoShowDivider(1, 1)  // 第一个参数:头部不显示分割线的个数,第二个参数:尾部不显示分割线的个数,默认为1
        .setDrawable(R.drawable.shape_line);// 设置drawable文件

recyclerView.addItemDecoration(itemDecoration);

4.1.2 设置颜色、高度、间距等

也可设置横向或纵向

SpacesItemDecoration itemDecoration = new SpacesItemDecoration(this, SpacesItemDecoration.VERTICAL)
        .setNoShowDivider(1, 1)
        // 颜色,分割线间距,左边距(单位dp),右边距(单位dp)
        .setParam(R.color.colorBlue, 10, 70, 70);

recyclerView.addItemDecoration(itemDecoration);

4.2 给宫格/瀑布流设置分割线

可以设置两种风格的分割线,1.四周没有间距,2.四周有间距。也可以设置头部和尾部不显示item的个数,具体实现类:GridSpaceItemDecoration

GridSpaceItemDecoration itemDecoration = new GridSpaceItemDecoration(3, 10, true)
        .setNoShowSpace(1, 1);

recyclerView.addItemDecoration(itemDecoration);
/**
 * @param spanCount   item 每行个数
 * @param spacing     item 间距
 * @param includeEdge item 距屏幕周围是否也有间距
 */
public GridSpaceItemDecoration(int spanCount, int spacing, boolean includeEdge)

/**
 * 设置从哪个位置 结束设置间距
 *
 * @param startFromSize 一般为HeaderView的个数 + 刷新布局(不一定设置)
 * @param endFromSize   默认为1,一般为FooterView的个数 + 加载更多布局(不一定设置)
 */
public GridSpaceItemDecoration setNoShowSpace(int startFromSize, int endFromSize)