注: 本篇文章内容有点多,要文章跟demo一块儿看才相得益彰。但愿你们能耐心看完,以后,你会以为该控件真心赞,帮你项目开发更加精简,建议收藏java
将BaseRecyclerViewAdapterHelper与MVVM模式完美结合起来,实现更加方便,快捷的列表加载,帮您省去大量的时间写各类复杂的适配器,使项目的代码结构更加简洁优雅。android
曾今有句Android界的谶言:Android UI开发只要会用列表就顶半边天。咱们平常开发中,用到的列表是何其多,大量的列表,就意味着大量的适配器,更意味着更大量的bean类、item布局和复杂繁多的逻辑,因此,有一套方便,快捷的列表模板是何其重要。如今都已经9020年了,相信你们已经从MVC模式,过渡MVP模式,到MVVM模式了吧,若是还没用MVVM模式的,你就out了!(这段是废话,能够省略不看)git
由于我公司项目是MVVM模式的,而后之前一个老Android同事写过一个控件,就是能很方便地将列表跟viewModel绑定在一块儿,使得代码精简不少。不过也有一些不足,例如不支持多布局的绑定,而后我这边就站在巨人的肩膀上,将控件完善一下。github
使用本控件的前提,项目使用MVVM开发模式,列表适配器是使用BRVAH的网络
BaseRecyclerViewAdapterHelper万能适配器(本控件支持非使用AndroidX和使用AndroidX的条件,贼贴心)。app
###目录
一、项目地址
二、首页调用展现
三、如何依赖
四、调用BRVAH自带的动画,及line模式
五、自定义动画调用设置,及grid模式
六、多布局实现,item实现MultipleItem。
七、多布局实现,item不实现MultipleItem的调用方式
八、如何用databinding模式添加多个头部和脚部,而且有各自事件
九、空布局及下拉刷新
十、侧滑删除
十一、长按拖动
十二、侧滑删除和长按拖动相结合
1三、ExpandableItem,可扩展的多布局使用
1四、下拉刷新,上拉加载
1五、仿聊天界面,从下到上加载数据
1六、双列表使用,仿外卖(甚至能够更多列表)
1七、支持使用本身的适配器来调用
1八、支持列表侧滑mvvm
###地址
废话很少说,先上Github的demo。从demo中,就能够看到项目结构精简maven
若是你项目中,是不使用AndroidX的,请食用该地址:ide
https://github.com/CaesarShao/CSBrvahBinding布局
若是项目中使用了AndroidX的,请食用该地址:
https://github.com/CaesarShao/CSBrvahBindingX
默认习惯,先上图再说,主要是里面的代码逻辑:
首先,你们能够看我library中的CSBrvahBindingAdapter这个类,里面就是经过BindingAdapter的方法,将列表跟适配器绑定在一块儿。而后是CSItemBindingAdapter这个适配器,这就是本控件核心的适配器了,在这个adapter中,调用了databinding的绑定方法。而后你们能够看BaseBindingViewModel这个类,这个是viewModel的基类,里面将一些属性和方法定义,还有仿网络请求的动做跟列表结合。后面边写边解释。
还有再提醒一下,你们看文章,要跟demo结合起来食用,否则可能会看得有点模糊。
###首页样式
例子列表截图:
首页列表调用方式:
很是简单,这个是Activity的内容,只要绑定viewModel,而后调用model.load(),加载列表的数据就能够了
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil .setContentView<ActivityMainBinding>(this, R.layout.activity_main) val model = MainViewModel() binding.vm = model model.load() CSLog.Open()//测试用开启日志 }
首页的viewModel:
@Override//获取绑定的布局和数据 protected Map<Integer, CSBravhItemBinding> getItemBinding() { Map<Integer, CSBravhItemBinding> mp = new HashMap<>(); mp.put(0, new CSBravhItemBinding(BR.bean, R.layout.item_main, BR.action, new Action())); return mp; } @Override//已经集成好的加载数据的方法 public void load() { load(getData()); } //另外的点击事件动做,我为了方便这样写,也能够mvvm模式默认是写在data数据中 public class Action implements CSAction1<MainData> { @Override public void call(MainData param) { Intent intent = new Intent(FramGroble.INSTANCE.getTopActivity(), param.activity); FramGroble.INSTANCE.getTopActivity().startActivity(intent); } } //模拟网络请求的数据 private Flowable<List<MainData>> getData() { return Flowable.create(new FlowableOnSubscribe<List<MainData>>() { @Override public void subscribe(FlowableEmitter<List<MainData>> emitter) throws Exception { ArrayList<MainData> data = new ArrayList<>(); data.add(new MainData("Animation,line,加载动画效果", AnimationActivity.class)); data.add(new MainData("Animation,Grid,加载自定义动画效果", AnimationCustomActivity.class)); data.add(new MainData("MultipleItem,line,多布局", MultipleLineActivity.class)); data.add(new MainData("非MultipleItem,Grid,多布局,(不想继承MultiItemEntity,用本身的bean类)", NonMultipleActivity.class)); data.add(new MainData("添加多个头部和尾部,有各自的数据,优雅", HeadFootActivity.class)); data.add(new MainData("空布局及下拉刷新", EmptyRefreshActivity.class)); data.add(new MainData("侧滑删除", SwipeActivity.class)); data.add(new MainData("长按拖动,多布局", DragActivity.class)); data.add(new MainData("可扩展的,多布局", ExpandActivity.class)); data.add(new MainData("下拉刷新,上拉加载", LoadMoreLineActivity.class)); data.add(new MainData("聊天界面,下拉加载", LoadMoreChatActivity.class)); data.add(new MainData("2个列表的绑定,仿外卖", TwoListActivity.class)); data.add(new MainData("用本身的适配器(继承万能适配器)", CustomAdapterActivity.class)); emitter.onNext(data); emitter.onComplete(); } }, BackpressureStrategy.BUFFER); } @Override//设置自定义item的间距 public RecyclerView.ItemDecoration onitemDecoration() { return new NormalLineDecoration(30, true); }
viewModel中,没有复杂的逻辑调用,只有一些回调。只要继承BaseBindingViewModel<>,里面设置数据的泛型,(多布局泛型下面说明),经过getItemBinding()这个回调,设置每一个item的布局与绑定的数据(能够绑定多个data),其中map的键就是itemType的类型,若是是单布局,写0就能够了,若是是多布局,按照itemType类型写,mainActivity中的item,还另外绑定了一个事件Action,我这边是设置为item的点击事件。load()中的回调,调用了load(getData())来加载数据(我这边使用rxjava来模拟数据的加载,如今的项目基本都是用retrofit和rxjava结合获取网络请求)。最下面的onitemDecoration()回调,是设置recyclerview的item间距。是否是很干净整洁,逻辑一目了然。
这时,就会有人问:哎呀,古诚欺啊,为何没有看到列表的适配器啊。嘿嘿,适配器已经封装在BaseBindingViewModel中了,别急,后面会一一讲到,接下来,来看布局文件,超级简单:
<data> <variable name="vm" type="com.caesar.brvahbinding.MainViewModel" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <androidx.recyclerview.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" app:cs_brvah_Decoration="@{vm.itemDecoration}" app:cs_brvah_adapter="@{vm.bindingAdapter}" /> </LinearLayout>
databinging的格式就很少说了,其中,app:cs_brvah_adapter="@{vm.bindingAdapter}",就是绑定viewModel中的适配器,由于在BaseBindingViewModel这个基类中已经初始化好了,因此直接引用就能够了。app:cs_brvah_Decoration="@{vm.itemDecoration}"这个是绑定Decoration,也就是设置每一个item的间隔,在上面的viewModel中,不是有onitemDecoration()回调么,就能够本身定制。
怎么样,是否是特别地简便。目前只是简单地说明一个,接下来会慢慢地讲解,越后面,本控件的便捷之处就越会体现,有不懂的能够集合例子看,就会明白的。
怎么引用该控件
这时就会有人问:哎呀,古诚欺啊,那该怎么依赖这么方便快捷的控件呢?
很简单,在根build.gradle中:
allprojects { repositories { ... maven { url 'https://jitpack.io' } } }
而后,若是你项目是使用AndroidX的,就引用该地址:
dependencies { implementation 'com.github.CaesarShao:CSBrvahBindingX:1.0.8' }
若是项目不支持AndroidX的,就引用:
dependencies { implementation 'com.github.CaesarShao:CSBrvahBinding:1.0.8' }
还有,由于我依赖的万能适配器的版本是com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.50,因此你们项目中的版本最好不低于2.9.50。否则可能会有兼容问题。
是否是贼贴心,都给你们考虑到了.好接下来我就一一讲解具体的调用方式,请你们耐心看下去。
Activity的调用我就不讲了,很简单,你们看AnimationActivity这个类常规调用,先将代码贴出来:
public AdapterView.OnItemSelectedListener onItemClickListener = getOnItemCli(); //构造方法,里面能够设置基础属性 public AnimationViewModel() { super(); //该viewmodel是演示效果,实际在构造方法中,直接调用便可, // animationType.set(BaseQuickAdapter.SLIDEIN_BOTTOM); } @Override protected Map<Integer, CSBravhItemBinding> getItemBinding() { Map<Integer, CSBravhItemBinding> mp = new HashMap<>(); mp.put(0, new CSBravhItemBinding(com.caesar.brvahbinding.BR.bean, R.layout.item_simple)); return mp; } @Override public void load() { load(CreateData.getSimpleData()); } //这个是Spinner控件的OnItemSelectedListener的监听,在布局中绑定,当spinner使用时,会回调这个方法. public AdapterView.OnItemSelectedListener getOnItemCli() { return new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { switch (position) { case 0: animationType.set(BaseQuickAdapter.SLIDEIN_BOTTOM); break; case 1: animationType.set(BaseQuickAdapter.ALPHAIN); break; case 2: animationType.set(BaseQuickAdapter.SCALEIN); break; case 3: animationType.set(BaseQuickAdapter.SLIDEIN_LEFT); break; case 4: animationType.set(BaseQuickAdapter.SLIDEIN_RIGHT); break; } } @Override public void onNothingSelected(AdapterView<?> parent) { } }; } @Override public RecyclerView.ItemDecoration onitemDecoration() { return new NormalLineDecoration(30, true); }
你们先看布局文件,有一个
app:cs_brvah_animation="@{vm.animationType}",这个就是设置brvah自带的动画效果,当依赖好以后,只要在viewmodel的构造方法中设置,好比animationType.set(BaseQuickAdapter.SLIDEIN_BOTTOM);这样调用就能够了,若是布局中依赖过,它默认的动画效果是BaseQuickAdapter.SLIDEIN_BOTTOM。
而后在RecyclerView中,绑定adapter以后,就是app:cs_brvah_adapter="@{vm.bindingAdapter}",会默认设置RecyclerView的显示方式为LinearLayoutManager。
如何加载自定义动画和GridLayoutManager呢
你们看AnimationCustomActivity这个界面,
<data> <variable name="vm" type="com.caesar.brvahbinding.animation.AnimationCustomViewModel" /> <import type="com.caesarlib.brvahbinding.CSBrvahLayoutManager" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{vm.onBack}" android:text="返回" /> <androidx.recyclerview.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" app:cs_brvah_Decoration="@{vm.itemDecoration}" app:cs_brvah_adapter="@{vm.bindingAdapter}" app:cs_brvah_animation_custom="@{vm.customAnimation}" app:cs_brvah_layoutManager="@{CSBrvahLayoutManager.grid(2)}" /> </LinearLayout>
@Override protected Map<Integer, CSBravhItemBinding> getItemBinding() { Map<Integer, CSBravhItemBinding> mp = new HashMap<>(); mp.put(0, new CSBravhItemBinding(com.caesar.brvahbinding.BR.bean, R.layout.item_simple)); return mp; } @Override public void load() { load(CreateData.getSimpleData()); @Override public RecyclerView.ItemDecoration onitemDecoration() { return new GridSpacingItemDecoration(2, 30, true); } @Override//设置自定义动画 public BaseAnimation onCustomAnimation() { return new CustomAnimation(); }
能够看到,布局文件中,绑定了2个其余的东西,其中app:cs_brvah_animation_custom="@{vm.customAnimation}"就是绑定自定义动画,在viewModel中的onCustomAnimation()回调中设置,我这边copy了brvah官方的自定义动画。在布局文件中,还能够看到,我引入了<import type="com.caesarlib.brvahbinding.CSBrvahLayoutManager"/>,在RecyclerView中,我调用,app:cs_brvah_layoutManager="@{CSBrvahLayoutManager.grid(2)}",这个就是将列表的显示方式,设置为2格的GridLayoutManager,你们能够去看看CSBrvahLayoutManager这个类,里面有设置各类LayoutManager的方法。
###多布局,实现MultipleItem
你们看MultipleLineActivity这个类,activity还
你们能够看到,布局文件很简单,跟首页的基本如出一辙,在MultiLineViewModel中,继承baseviewmodel时,要传泛型为MultiItemEntity,能够看到,是否是跟其余的基本一毛同样,而后每一个item的data也都很简单。
这时,就会有同窗问了:诶呀,古诚欺啊,若是我想要个人item数据不实现MultiItemEntity怎么办?固然能够。
@Override public void load() { load(getData()); } private Flowable<List<MultiItemEntity>> getData() { return Flowable.create(new FlowableOnSubscribe<List<MultiItemEntity>>() { @Override public void subscribe(FlowableEmitter<List<MultiItemEntity>> emitter) throws Exception { ArrayList<MultiItemEntity> data = new ArrayList<>(); data.add(new MultiDataOne("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new MultiDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img0)); data.add(new MultiDataTwo("这货是个标题", "这货是个内容加描述", R.mipmap.head_img2)); data.add(new MultiDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new MultiDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new MultiDataOne("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new MultiDataTwo("这货是个标题", "这货是个内容加描述", R.mipmap.head_img2)); data.add(new MultiDataOne("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new MultiDataOne("这货是个标题", "这货是个内容加描述", R.mipmap.head_img0)); data.add(new MultiDataTwo("这货是个标题", "这货是个内容加描述", R.mipmap.head_img0)); data.add(new MultiDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new MultiDataOne("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); emitter.onNext(data); emitter.onComplete(); } }, BackpressureStrategy.BUFFER); } @Override public RecyclerView.ItemDecoration onitemDecoration() { return new NormalLineDecoration(30, true); }
<data> <variable name="vm" type="com.caesar.brvahbinding.multiple.MultiLineViewModel" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{vm.onBack}" android:text="返回" /> <androidx.recyclerview.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" app:cs_brvah_Decoration="@{vm.itemDecoration}" app:cs_brvah_adapter="@{vm.bindingAdapter}" app:cs_brvah_animation="@{vm.animationType}" /> </LinearLayout>
</layout>
···
你们能够看到,布局文件很简单,跟首页的基本如出一辙,在MultiLineViewModel中,继承baseviewmodel时,要传泛型为MultiItemEntity,能够看到,是否是跟其余的基本一毛同样,而后每一个item的data也都很简单。
这时,就会有同窗问了:诶呀,古诚欺啊,若是我想要个人item数据不实现MultiItemEntity怎么办?固然能够。
###item不实现MultiItemEntity的多布局
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="vm" type="com.caesar.brvahbinding.nonMultiple.NonMultiViewModel" /> <import type="com.caesarlib.brvahbinding.CSBrvahLayoutManager" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".nonMultiple.NonMultipleActivity"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{vm.onBack}" android:text="返回" /> <androidx.recyclerview.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" android:padding="4dp" app:cs_brvah_Decoration="@{vm.itemDecoration}" app:cs_brvah_adapter="@{vm.bindingAdapter}" app:cs_brvah_layoutManager="@{CSBrvahLayoutManager.grid(4)}" app:cs_brvah_multiType="@{vm.multiTypeDelegat}" app:cs_brvah_spansize="@{vm.spanSizeLookup}" /> </LinearLayout> </layout>
public class NonMultiViewModel extends BaseBindingViewModel<customData> { //在构造方法中,设置SpanSizeLookup,这个是每一个item占几格的回调.MultiTypeDelegat这个是itemType的回调判断了,若是不想继承MultiItemEntity的话 //就要由该回调来判断 public NonMultiViewModel() { super(); setSpan(new BaseQuickAdapter.SpanSizeLookup() { @Override public int getSpanSize(GridLayoutManager gridLayoutManager, int i) { if (items.get(i).getItemType() == 0) { return 1; } else if (items.get(i).getItemType() == 1) { return 2; } else if (items.get(i).getItemType() == 2) { return 4; } else { return 0; } } }); setMultiTypeDelegat(new MultiTypeDelegate<customData>() { @Override protected int getItemType(customData customData) { return customData.getItemType(); } }); } @Override//这里面仍是跟其余的同样 protected Map<Integer, CSBravhItemBinding> getItemBinding() { Map<Integer, CSBravhItemBinding> mp = new HashMap<>(); mp.put(0, new CSBravhItemBinding(com.caesar.brvahbinding.BR.data, R.layout.item_nomulti_zero)); mp.put(1, new CSBravhItemBinding(com.caesar.brvahbinding.BR.data, R.layout.item_nomulti_one)); mp.put(2, new CSBravhItemBinding(com.caesar.brvahbinding.BR.data, R.layout.item_nomulti_two)); //这边的0,1,2要跟上面setMultiTypeDelegat返回的要对应起来 return mp; } @Override public void load() { load(getData()); } private Flowable<List<customData>> getData() { return Flowable.create(new FlowableOnSubscribe<List<customData>>() { @Override public void subscribe(FlowableEmitter<List<customData>> emitter) throws Exception { ArrayList<customData> data = new ArrayList<>(); data.add(new NeoDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataOne("这货是个标题", "这货是个内容加描述", R.mipmap.head_img0)); data.add(new NeoDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataTwo("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img2)); data.add(new NeoDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataOne("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataOne("这货是个标题", "这货是个内容加描述", R.mipmap.head_img0)); data.add(new NeoDataOne("这货是个标题", "这货是个内容加描述", R.mipmap.head_img0)); data.add(new NeoDataTwo("这货是个标题", "这货是个内容加描述", R.mipmap.head_img2)); data.add(new NeoDataOne("这货是个标题", "这货是个内容加描述", R.mipmap.head_img0)); data.add(new NeoDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataTwo("这货是个标题", "这货是个内容加描述", R.mipmap.head_img2)); data.add(new NeoDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataTwo("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataZero("这货是个标题", "这货是个内容加描述", R.mipmap.head_img1)); data.add(new NeoDataOne("这货是个标题", "这货是个内容加描述", R.mipmap.head_img0)); emitter.onNext(data); emitter.onComplete(); } }, BackpressureStrategy.BUFFER); } @Override public RecyclerView.ItemDecoration onitemDecoration() { return new NormaltemDecoration(10); } }
public class customData {//能够是接口,能够是类,只要有type类型判断 public int itemType; public String title; public String discribe; public int imgRes; public customData(String title, String discribe, int imgRes) { this.title = title; this.discribe = discribe; this.imgRes = imgRes; } public int getItemType() { return itemType; } public void setItemType(int itemType) { this.itemType = itemType; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDiscribe() { return discribe; } public void setDiscribe(String discribe) { this.discribe = discribe; } public int getImgRes() { return imgRes; } public void setImgRes(int imgRes) { this.imgRes = imgRes; } }
你们看NonMultipleActivity这个类,界面没啥好讲。customData这个类中,有一个itemType,反正只要有一个能够判断item类型的参数就能够,在NonMultiViewModel的泛型中,写customData。在构造方法中,有一个setSpan()方法,这个方法就是适配器每一个item占几个的回调,通常状况下,多布局Grid形式的,而且item所占格式不一样的,基本都要回调这个方法,设置完该方法后,别忘记在布局中,用
app:cs_brvah_spansize="@{vm.spanSizeLookup}"进行绑定。在构造方法中,还有一个setMultiTypeDelegat方法,这个方法就是设置每一个item的类型的回调了,而后在布局文件中,经过
app:cs_brvah_multiType="@{vm.multiTypeDelegat}"进行绑定,是否是特别简单。
文章不易,若是你们喜欢这篇文章,或者对你有帮助但愿你们多多,点赞,转发,关注 哦。文章会持续更新的。绝对干货!!!