When the interview if you will ListView components, instead of using RecyclerView words, there may be little the company to send you offer, if you will RecyclerView use and development of general adapter, then do not understand open source framework BaseRecyclerViewAdapterHelper RecyclerView of (BRVAH) use , it is also a loss, at least the working efficiency will be much slower! Let's look at what this can do open-source framework (content translated from the official website)?
The following is a reprint content, layout slightly changed a bit, to facilitate their reading! Continue to update their code examples to put some of it!
- Adapter optimization codes, and the adapter relative to the original, reduced by 70% of the code.
- Add Item events
Item click event
Item long press event
Item child control click event
Item child controls of a long press event - Add a list loading animation: a line of code to easily switch 5 kinds of default animation.
- Add header and footer: a line of code to get feeling back ListView era.
- Automatic load: the load without having to listen for Raja slip event, you can customize the layout of loading, display abnormal prompt, prompt custom exception. Supports drop-down load.
- Layout packet: the packet header defined heart.
- Multi-layout: a simple configuration, without the need to rewrite an additional method.
- Set an empty layout: even works better than the setEmptyView Listview.
- Add drag, swipe to delete: open, listening to, is that simple.
- Tree list: more powerful than ExpandableListView, multi-level support.
- Custom ViewHolder: support custom ViewHolder, allowing developers to arbitrary.
- Extension Framework: a combination of third-party frameworks, needs more easily customized.
So fast hardware frame looked really Brief Encounter ah, alone, automatically loads the pull-down refresh, group layout, click encapsulates a variety of events, we believe that most developers will be able to capture the hearts and minds of! (The above mentioned features, this article also catalog: https://www.jianshu.com/p/b343fcff51b0 )
The new beta version 3.x has been released for use kotlin
and AndroidX
write in support of java
use. Addresses many of the remaining issues, enhanced support for DataBinding, and "multiple layout" more flexible. Of course, you can also continue to use the 2.x version.
Framework introduced
First in build.gradle: repositories (Project XXXX) is added:
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
Then build.gradle (Module: app) of the dependencies added:
dependencies {
compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30'
}
Update Description: https://github.com/CymChad/BaseRecyclerViewAdapterHelper/releases
note
版本:2.9.28
Change the method setVisible --> setGone
The new method setVisible
setVisible:Set a view visibility to VISIBLE (true) or INVISIBLE (false).
setGone: Set a view visibility to VISIBLE (true) or GONE (false).
Note: Once the case load failure occurs, only two cases:
- Configuration is not configured
Configuration is not configured, there are several situations:
1. Configure up the Dependencies
2. configuration repositories, but the location was wrong, build.gradle (Project: XXXX) repositories in the file there are two, one is buildscript below , one is allprojects below, to configure the following allprojects is right.
3. The version number in front of a multi-v, this is my pot (of words), all versions prior to 2.1.2 with v, followed by (2.1.2 included) do not need to bring v. - The reason the network (this is not explained)
BaseRecyclerViewAdapterHelper adapter to use:
First need to inherit BaseQuickAdapter
, and then add the generic parameter BaseQuickAdapter<Status, BaseViewHolder>
of a generic Status
data entity type, the second BaseViewHolder
is ViewHolder, its purpose is to support the expansion ViewHolder.
Assignment
You can use direct viewHolder
target point. "" Correlation methods by passing viewId and data to support chained method calls. If the network is loaded picture or custom view can viewHolder.getView(viewId)
get that control.
Common method
- viewHolder.getLayoutPosition () Gets the current item of position
Using the code
public class HomeAdapter extends BaseQuickAdapter<HomeItem, BaseViewHolder> {
public HomeAdapter(int layoutResId, List data) {
super(layoutResId, data);
}
@Override
protected void convert(BaseViewHolder helper, HomeItem item) {
helper.setText(R.id.text, item.getTitle());
helper.setImageResource(R.id.icon, item.getImageResource());
// 加载网络图片
Glide.with(mContext).load(item.getUserAvatar()).crossFade().into((ImageView) helper.getView(R.id.iv));
}
}
Note: If you want to understand the principles can view RecyclerView.Adapter optimized yet?
common problem
The problem is not the use of the library, but often asked these questions, so he wrote it to help developers follow-up encounter the following problems.
Why not show the data?
Please check your RecyclerView whether a LayoutManager.
Why are there 10 data show only one?
Please check the item layout outermost Layout is not layout_height
set match_parent
.
Data status confusion
this issue or whether it is RecyclerView ListView will not deal with the problem, this is essentially due to the layout of the mechanisms leading to reuse, the solution is to control the state of the state control over the data, be sure to set the state no matter what state, if
and else
is ultimately, the following code:
if(entity.isCheck){
checkBox.isChecked(true);
} else {
checkBox.isChecked(false);
}
Cache problem solving cases:
- Cache deal with the problem of text boxes and radio : https://github.com/Pyuming/RecyclerViewEditTextDemo
- Achieve three telescopic and address check box reuse : https://github.com/Sky0202/About_BRVAH
Add Item event
Item click event
adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
Log.d(TAG, "onItemClick: ");
Toast.makeText(ItemClickActivity.this, "onItemClick" + position, Toast.LENGTH_SHORT).show();
}
});
Item long press event
adapter.setOnItemLongClickListener(new BaseQuickAdapter.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position) {
Log.d(TAG, "onItemLongClick: ");
Toast.makeText(ItemClickActivity.this, "onItemLongClick" + position, Toast.LENGTH_SHORT).show();
return false;
}
});
Note: Nested recycleView case you need to use adapter setOnItemClickListener to set the click event, if recycleView.addOnItemTouchListener will add a total of.
Click event Item child controls (two steps)
① first on the inside of the adapter convert method by viewHolder.addOnClickListener
binding controls id
@Override
protected void convert(BaseViewHolder viewHolder, Status item) {
viewHolder.setText(R.id.tweetName, item.getUserName())
.setText(R.id.tweetText, item.getText())
.setText(R.id.tweetDate, item.getCreatedAt())
.setVisible(R.id.tweetRT, item.isRetweet())
//添加子控件的点击事件
.addOnClickListener(R.id.tweetAvatar)
.addOnClickListener(R.id.tweetName)
.linkify(R.id.tweetText);
}
② then set
adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public boolean onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
Log.d(TAG, "onItemChildClick: ");
Toast.makeText(ItemClickActivity.this, "onItemChildClick" + position, Toast.LENGTH_SHORT).show();
return false;
}
});
Long Item child control by event (two steps)
steps above using a different method.
①adapter bound method will addOnClickListener
change addOnLongClickListener
.
② set up click event method setOnItemChildClickListener
changedsetOnItemChildLongClickListener
Note: Set the child control events, if not bound adapter, click the event could not be effective, because they can not find the control you need to set.
如果需要在点击事件中获取其他子控件可以使用方法:
getViewByPosition(RecyclerView recyclerView, int position, @IdRes int viewId)
注意:如果有header的话需要处理一下position加上 headerlayoutcount。
添加列表加载动画
开启动画(默认为渐显效果)adapter.openLoadAnimation();
默认提供5种动画类型(渐显、缩放、从下到上,从左到右、从右到左)
public static final int ALPHAIN = 0x00000001;
/**
* Use with {@link #openLoadAnimation}
*/
public static final int SCALEIN = 0x00000002;
/**
* Use with {@link #openLoadAnimation}
*/
public static final int SLIDEIN_BOTTOM = 0x00000003;
/**
* Use with {@link #openLoadAnimation}
*/
public static final int SLIDEIN_LEFT = 0x00000004;
/**
* Use with {@link #openLoadAnimation}
*/
public static final int SLIDEIN_RIGHT = 0x00000005;
切换动画类型:
quickAdapter.openLoadAnimation(BaseQuickAdapter.ALPHAIN);
自定义动画:
quickAdapter.openLoadAnimation(new BaseAnimation() {
@Override
public Animator[] getAnimators(View view) {
return new Animator[]{
ObjectAnimator.ofFloat(view, "scaleY", 1, 1.1f, 1),
ObjectAnimator.ofFloat(view, "scaleX", 1, 1.1f, 1)
};
}
});
动画默认只执行一次,如果想重复执行可设置
mQuickAdapter.isFirstOnly(false);
注:如果想深入了解的原理可以查看BaseRecyclerAdapter之添加动画(策略模式)
设置不显示动画数量
adapter.setNotDoAnimationCount(count);
首次到界面的item都依次执行加载动画
由于进入界面的item都是很快的速度进来的所以不会出现滑动显示的依次执行动画效果,这个时候会一起执行动画,如果觉得这样的效果不好,可以使用
setNotDoAnimationCount
设置第一屏item不执行动画,但是如果需要依次执行动画可以重写startAnim
让第一个屏幕的item动画延迟执行即可。
@Override
protected void startAnim(Animator anim, int index) {
super.startAnim(anim, index);
if (index < count)
anim.setStartDelay(index * 150);
}
添加头部、尾部
添加头部、尾部:一行代码搞定,感觉又回到ListView时代
mQuickAdapter.addHeaderView(getView());
mQuickAdapter.addFooterView(getView());
注:如果想深入了解的原理可以查看BaseRecyclerAdapter之添加不同布局(头部尾部)
删除指定view
mQuickAdapter.removeHeaderView(getView);
mQuickAdapter.removeFooterView(getView);
删除所有
mQuickAdapter.removeAllHeaderView();
mQuickAdapter.removeAllFooterView();
默认出现了头部就不会显示Empty,和尾部,配置以下方法也支持同时显示:
setHeaderAndEmpty
setHeaderFooterEmpty
默认头部尾部都是占满一行,如果需要不占满可以配置:
setHeaderViewAsFlow
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());
自定义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>
下拉加载(符合聊天软件下拉历史数据需求)
设置开启开关
mAdapter.setUpFetchEnable(true);
设置监听
mAdapter.setUpFetchListener(new BaseQuickAdapter.UpFetchListener() {
@Override
public void onUpFetch() {
startUpFetch();
}
});
private void startUpFetch() {
count++;
/**
* set fetching on when start network request.
*/
mAdapter.setUpFetching(true);
/**
* get data from internet.
*/
mRecyclerView.postDelayed(new Runnable() {
@Override
public void run() {
mAdapter.addData(0, getData());
/**
* set fetching off when network request ends.
*/
mAdapter.setUpFetching(false);
/**
* set fetch enable false when you don't need anymore.
*/
if (count > 5) {
mAdapter.setUpFetchEnable(false);
}
}
}, 300);
}
开始加载的位置
mAdapter.setStartUpFetchPosition(2);
分组布局
实体类必须继承SectionEntity
public class MySection extends SectionEntity<Video> {
private boolean isMore;
public MySection(boolean isHeader, String header) {
super(isHeader, header);
}
public MySection(Video t) {
super(t);
}
}
adapter构造方法需要传入两个布局id,第一个是item的,第二个是head的,在convert
方法里面加载item数据,在convertHead
方法里面加载head数据
public class SectionAdapter extends BaseSectionQuickAdapter<MySection> {
//构造方法需要传入两个布局id,第一个是item的,第二个是head的
public SectionAdapter(int layoutResId, int sectionHeadResId, List data) {
super(layoutResId, sectionHeadResId, data);
}
//在convert方法里面加载item数据
@Override
protected void convert(BaseViewHolder helper, MySection item) {
helper.setImageUrl(R.id.iv, (String) item.t);
}
//在convertHead方法里面加载head数据
@Override
protected void convertHead(BaseViewHolder helper,final MySection item) {
helper.setText(R.id.header, item.header);
helper.setOnClickListener(R.id.more, new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context,item.header+"more..",Toast.LENGTH_LONG).show();
}
});
}
多布局
实体类必须实现MultiItemEntity
,在设置数据的时候,需要给每一个数据设置itemType
public class MultipleItem implements MultiItemEntity {
public static final int TEXT = 1;
public static final int IMG = 2;
private int itemType;
public MultipleItem(int itemType) {
this.itemType = itemType;
}
@Override
public int getItemType() {
return itemType;
}
}
在构造里面addItemType
绑定type和layout的关系
public class MultipleItemQuickAdapter extends BaseMultiItemQuickAdapter<MultipleItem, BaseViewHolder> {
//在构造里面addItemType绑定type和layout的关系
public MultipleItemQuickAdapter(List data) {
super(data);
addItemType(MultipleItem.TEXT, R.layout.text_view);
addItemType(MultipleItem.IMG, R.layout.image_view);
}
@Override
protected void convert(BaseViewHolder helper, MultipleItem item) {
switch (helper.getItemViewType()) {
case MultipleItem.TEXT:
helper.setImageUrl(R.id.tv, item.getContent());
break;
case MultipleItem.IMG:
helper.setImageUrl(R.id.iv, item.getContent());
break;
}
}
}
注:如果想深入了解的原理可以查看BaseRecyclerAdapter之添加不同布局(优化篇)
如果考虑到在GridLayoutManager
复用item问题可以配置:
multipleItemAdapter.setSpanSizeLookup(new BaseQuickAdapter.SpanSizeLookup() {
@Override
public int getSpanSize(GridLayoutManager gridLayoutManager, int position) {
return data.get(position).getSpanSize();
}
});
如果大家觉得这种多布局方式有点由于耦合了实体类,还有支持另外一种多布局方式具体可查看更便捷的多布局, 为 BaseQuickAdapter 设置代理.
更便捷的多布局, 为 BaseQuickAdapter
设置代理
public class MultiDelegateAdapter extends BaseQuickAdapter<Entity, BaseViewHolder> {
public MultiDelegateAdapter() {
super(null);
//Step.1
setMultiTypeDelegate(new MultiTypeDelegate<Entity>() {
@Override
protected int getItemType(Entity entity) {
//根据你的实体类来判断布局类型
return entity.type;
}
});
//Step.2
getMultiTypeDelegate()
.registerItemType(Entity.TEXT, R.layout.item_text_view)
.registerItemType(Entity.IMG, R.layout.item_image_view);
}
@Override
protected void convert(BaseViewHolder helper, Entity entity) {
//Step.3
switch (helper.getItemViewType()) {
case Entity.TEXT:
// do something
break;
case Entity.IMG:
// do something
break;
}
}
}
多布局代理很简单,在任何继承与BaseQuickAdapter
的 adapter 上都可以实现,分为三步,如下:
Step.1
setMultiTypeDelegate()
, 并重写getItemType()
方法
需要说明的是Entity 并不需要实现任何接口 ,只需要能够判断出该实体对应的是哪个布局类型即可
Step.2
getMultiTypeDelegate().registerItemType()
设置每种type对应的布局
Step.3
- 分 type 进行
convert()
操作
OK, 多布局就可以顺利的运行了
如果使用多布局出现这个NotFoundException异常,有可能是addItemType()两个参数写反了。
// 没有数据的时候默认显示该布局
mQuickAdapter.setEmptyView(getView());
如果用网格布局的话,设置空布局就不能给全屏,可以使用瀑布流布局。
添加拖拽、滑动删除
拖拽和滑动删除的回调方法
OnItemDragListener onItemDragListener = new OnItemDragListener() {
@Override
public void onItemDragStart(RecyclerView.ViewHolder viewHolder, int pos){}
@Override
public void onItemDragMoving(RecyclerView.ViewHolder source, int from, RecyclerView.ViewHolder target, int to) {}
@Override
public void onItemDragEnd(RecyclerView.ViewHolder viewHolder, int pos) {}
}
OnItemSwipeListener onItemSwipeListener = new OnItemSwipeListener() {
@Override
public void onItemSwipeStart(RecyclerView.ViewHolder viewHolder, int pos) {}
@Override
public void clearView(RecyclerView.ViewHolder viewHolder, int pos) {}
@Override
public void onItemSwiped(RecyclerView.ViewHolder viewHolder, int pos) {}
};
adapter需要继承BaseItemDraggableAdapter
public class ItemDragAdapter extends BaseItemDraggableAdapter<String, BaseViewHolder> {
public ItemDragAdapter(List data) {
super(R.layout.item_draggable_view, data);
}
@Override
protected void convert(BaseViewHolder helper, String item) {
helper.setText(R.id.tv, item);
}
}
Activity使用代码
mAdapter = new ItemDragAdapter(mData);
ItemDragAndSwipeCallback itemDragAndSwipeCallback = new ItemDragAndSwipeCallback(mAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemDragAndSwipeCallback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);
// 开启拖拽
mAdapter.enableDragItem(itemTouchHelper, R.id.textView, true);
mAdapter.setOnItemDragListener(onItemDragListener);
// 开启滑动删除
mAdapter.enableSwipeItem();
mAdapter.setOnItemSwipeListener(onItemSwipeListener);
默认不支持多个不同的 ViewType 之间进行拖拽,如果开发者有所需求:
重写
ItemDragAndSwipeCallback
里的onMove()
方法,return true
即可
树形列表
例子:三级菜单
// if you don't want to extent a class, you can also use the interface IExpandable.
// AbstractExpandableItem is just a helper class.
public class Level0Item extends AbstractExpandableItem<Level1Item> {...}
public class Level1Item extends AbstractExpandableItem<Person> {...}
public class Person {...}
adapter需要继承BaseMultiItemQuickAdapter
public class ExpandableItemAdapter extends BaseMultiItemQuickAdapter<MultiItemEntity, BaseViewHolder> {
public ExpandableItemAdapter(List<MultiItemEntity> data) {
super(data);
addItemType(TYPE_LEVEL_0, R.layout.item_expandable_lv0);
addItemType(TYPE_LEVEL_1, R.layout.item_expandable_lv1);
addItemType(TYPE_PERSON, R.layout.item_text_view);
}
@Override
protected void convert(final BaseViewHolder holder, final MultiItemEntity item) {
switch (holder.getItemViewType()) {
case TYPE_LEVEL_0:
....
//set view content
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getAdapterPosition();
if (lv0.isExpanded()) {
collapse(pos);
} else {
expand(pos);
}
}});
break;
case TYPE_LEVEL_1:
// similar with level 0
break;
case TYPE_PERSON:
//just set the content
break;
}
}
开启所有菜单:
adapter.expandAll();
删除某一个item(添加和修改的思路是一样的)
// 获取当前父级位置
int cp = getParentPosition(person);
// 通过父级位置找到当前list,删除指定下级
((Level1Item)getData().get(cp)).removeSubItem(person);
// 列表层删除相关位置的数据
getData().remove(holder.getLayoutPosition());
// 更新视图
notifyDataSetChanged();
自定义ViewHolder
需要继承BaseViewHolder
public class MovieViewHolder extends BaseViewHolder
然后修改adapter的第二个泛型为自定义的ViewHolder
public class DataBindingUseAdapter extends BaseQuickAdapter<Movie, DataBindingUseAdapter.MovieViewHolder>
注意:需要单独建一个外部类继承BaseViewHolder,否则部分机型会出现ClassCastException,如果是内部类的构造方法要是public,定义的那个类也最好是public。
混淆
-keep class com.chad.library.adapter.** {
*;
}
-keep public class * extends com.chad.library.adapter.base.BaseQuickAdapter
-keep public class * extends com.chad.library.adapter.base.BaseViewHolder
-keepclassmembers class **$** extends com.chad.library.adapter.base.BaseViewHolder {
<init>(...);
}
扩展框架
由于adapter本身能力有限,我们又不想耦合view层所以有些需求是现实不了,于是合作了一些优秀开源库,为开发者提供更多可能性。以下扩展框架都是有结合BRVAH的demo。
- PinnedSectionItemDecoration:一个强大的粘性标签库
- EasyRefreshLayout:这个库让你轻松实现下拉刷新和上拉更多
- EasySwipeMenuLayout:独立的侧滑删除
本文章由于持续更新,建议点赞收藏,便于查看。
官方网站:www.recyclerview.org
Demo下载地址:http://fir.im/s91g
如果有问题:提问,请先看这个!
两个很不错的地址:
官网:https://github.com/CymChad/BaseRecyclerViewAdapterHelper
简书:https://www.jianshu.com/p/b343fcff51b0