Skip to content
景彬 edited this page May 17, 2021 · 39 revisions

概述

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

特性

  • 1.支持 下拉刷新、加载更多
  • 2.可随意切换 自带下拉刷新布局 / SwipeRefreshLayout
  • 3.加载更多机制:手动上拉才执行加载更多
  • 4.可设置自定义 下拉刷新布局 和 加载更多布局
  • 5.添加/移除 HeaderView、FooterView
  • 6.设置各种状态布局 EmptyView / LoadingView / ErrorView
  • 7.添加item的点击/长按事件(防止重复点击)
  • 8.优化过的BaseAdapter (RV/LV),减少大量代码
  • 9.Adapter结合DataBinding使用 (RV/LV)
  • 10.可添加万能分隔线(线性/宫格/瀑布流)
  • 11.可设置粘性header,StickyView
  • 12.可配置Skeleton骨架图

1 引入及极速设置

1.1 引入 jitpack

先在 build.gradle 的 repositories 添加

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

然后在dependencies添加

dependencies {
	implementation 'com.github.youlookwhat:ByRecyclerView:1.1.6'
	implementation "com.github.youlookwhat:ByRecyclerView:1.0.18-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的数据
    }
});

// 防止重复点击
mRecyclerView.setOnItemClickListener(new OnItemFilterClickListener() {
    @Override
    protected void onSingleClick(View v, int 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);

// 获取view对应databinding,注意:recyclerView.getParent()
LayoutHeaderViewBinding headerBinding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.layout_header_view, (ViewGroup) binding.recyclerView.getParent(), 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设置子view点击监听
recyclerView.setOnItemChildClickListener(new ByRecyclerView.OnItemChildClickListener() {
    @Override
    public void onItemChildClick(View view, int position) {
        switch (view.getId()) {
            case R.id.tv_text:
                break;
            default:
                break;
        }
    }
});

// 设置防重复的点击事件
recyclerView.setOnItemChildClickListener(new OnItemChildFilterClickListener() {
    @Override
    public void onSingleClick(View view, int position) {
        
    }
});

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 Adapter

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(10, true)
        .setNoShowSpace(1, 1);

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

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

5 设置StickyView

1、使用StickyLinearLayoutManager,传入adapter

StickyLinearLayoutManager layoutManager = new StickyLinearLayoutManager(getContext(), mAdapter);

2、在adapter里,将悬浮的item的ItemViewType设置为StickyHeaderHandler.TYPE_STICKY_VIEW

@Override
public int getItemViewType(int position) {
    if ("title".equals(getItemData(position).getType())) {
        return StickyHeaderHandler.TYPE_STICKY_VIEW;
    } else {
        return 2;
    }
}

也可以使用StickyGridLayoutManager,只需将最后一个参数传入 adapter 即可

注意:使用置顶item时,不能使用自带的下拉刷新。

6 设置Skeleton骨架图

骨架图处理,在设置完 ByRV 的一切配置后执行,可设置 list、grid、View、HeaderView 具体请见项目示例代码

6.1 设置item骨架图

通过额外setAdapter实现 【在之前 不能 setAdapter()】 示例代码:

// 显示
skeletonScreen = BySkeleton
        .bindItem(binding.recyclerView)
        .adapter(mAdapter)// 必须设置adapter,且在此之前不要设置adapter
        .shimmer(false)// 是否有动画
        .load(R.layout.layout_by_default_item_skeleton)// item骨架图
        .angle(30)// 微光角度
        .frozen(false) // 是否不可滑动
        .color(R.color.colorWhite)// 动画的颜色
        .duration(1500)// 微光一次显示时间
        .count(10)// item个数
        .show();

// 隐藏
skeletonScreen.hide();

6.2 设置view骨架图

通过setStateView实现 【在之前 需要 setAdapter()】 示例代码:

// 显示
skeletonScreen = BySkeleton
        .bindView(binding.recyclerView)
        .load(R.layout.layout_skeleton_view)// view骨架图
        .shimmer(true)// 是否有动画
        .angle(20)// 微光角度
        .color(R.color.colorWhite)// 动画的颜色
        .duration(1500)// 微光一次显示时间
        .show();

// 隐藏
skeletonScreen.hide();