一个月没有写博客了;
一个月时间里将以前的两个个项目MVVM Databinding搭配MVC结构改成了MVVM结构;
使用ViewModel处理业务逻辑,Activity只负责UI的处理;
使用组件化方式将两个项目合并;
本篇文章不详细说明组件化,关于组件化文章很多,具体问题再写一篇记录组件化;
过程中遇到很多的问题,其中要说的就是从以前RecyclerView搭配Adapter说起;
我这个项目界面不多,但是列表特别多,列表嵌套列表更是多得要命;
什么滑动冲突,什么item展示不完整等问题在以前开发的时候遇到次数特别多;
以前还是每个列表一个Adapter;
这次最终目标就是使用ItemDatabind的方式将Adapter省略掉,但是还是需要一个ViewModel来处理一些东西;
在MVVM使用RecyclerView的时候,找资料找demo找了挺长的时间;
下面贴出一个github上很棒的开源项目---binding-collection-adapter
地址:点我点我点我
英文比较好的还有扩展性强的童鞋可以直接去看他的项目说明;
注:github阅读代码觉得累的可以看下我另一篇博客,给chorme安装个插件:
github代码阅读的chrome插件(insightio-for-github)
让你在github看代码更轻松,目录更清晰,看个效果图:
好了,懒得去的可以看我下面的栗子演示,并且附带一些我遇到的问题和解决思路(到这里应该能剩点人吧,恩希望吧,要不我下面的都白写了);
我建个空项目,然后什么MVVM初始化那一套先弄上,假设现在可以放心使用databinding了;
gradle中引入刚才那个开源库:
// recyclerView的Databinding套装 compile 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter:2.2.0' compile 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-recyclerview:2.2.0'
然后建个DemoActvity:
public class DemoActivity extends AppCompatActivity { private InputaActivityDemoBinding demoBinding; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); demoBinding = DataBindingUtil.setContentView(DemoActivity.this, R.layout.inputa_activity_demo); demoBinding.setDemoViewModel(new DemoViewModel(this)); } }可以看出,初始化activity没啥东西,就是将ViewModel初始化设置到xml中;
下面看看activity的xml:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:binding="http://schemas.android.com/apk/res-auto"> <data> <variable name="demoViewModel" type="com.reliable.inputa.ui.demo.DemoViewModel"/> <import type="me.tatarka.bindingcollectionadapter2.LayoutManagers"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="wrap_content" binding:items="@{demoViewModel.viewModelObservableList}" binding:layoutManager="@{LayoutManagers.linear()}" binding:itemBinding="@{demoViewModel.itemBinding}"/> </LinearLayout> </layout>可以看到,只有一个recyclerView;
上面有三个以前没见过的属性,其中LayoutaManager是开源项目中带的,直接import进xml使用即可,如果你想设置一行多个条目,或者其他的展现形式,可以点进去看他的源码,它使用工厂模式,创建不同manager;
现在假如你想设置每行四列的列表,那么上面的linear你可以换成 grid(4)
具体还有哪些command+左键进去瞅瞅,很简单;
剩下俩个属性在ViewModel中设置,关键点来了:
看下DemoViewModel:
import android.content.Context; import android.databinding.ObservableArrayList; import android.databinding.ObservableList; import com.reliable.baselib.base.BaseViewModel; import com.reliable.inputa.BR; import com.reliable.inputa.R; import java.util.ArrayList; import java.util.List; import me.tatarka.bindingcollectionadapter2.ItemBinding; /** * 列表Activity的ViewModel * Created by ge on 2018/2/5. */ public class DemoViewModel extends BaseViewModel{ // 给RecyclerView添加ObservableList public ObservableList<DemoItemViewModel> viewModelObservableList = new ObservableArrayList<>(); // 给RecyclerView添加ItemView public ItemBinding<DemoItemViewModel> itemBinding = ItemBinding.of(BR.demoItemViewModel, R.layout.inputa_demo_item); DemoViewModel(Context context){ super(context); // 造点假数据,这里你可以是网络请求或者数据库获取 List<DemoEntity> entityList = new ArrayList<>(); DemoEntity entity = new DemoEntity(); entity.setAddress("辽宁大连高新园区腾讯大厦"); entity.setName("葛大宝"); DemoEntity entity1 = new DemoEntity(); entity1.setAddress("西安市碑林区中科创星"); entity1.setName("葛大宝1"); entityList.add(entity); entityList.add(entity1); // -----------------华丽分割线--------------- // 初始化item条目 int itemCount = entityList.size(); for (int i = 0;i < itemCount;i++){ viewModelObservableList.add(new DemoItemViewModel(context)); } } }
关键就是:
// 给RecyclerView添加ObservableList public ObservableList<DemoItemViewModel> viewModelObservableList = new ObservableArrayList<>(); // 给RecyclerView添加ItemView public ItemBinding<DemoItemViewModel> itemBinding = ItemBinding.of(BR.demoItemViewModel, R.layout.inputa_demo_item);初始化的时候添加observableList,即循环创建条目,并将每个条目的数据对象传过去:
// 初始化item条目 int itemCount = entityList.size(); for (int i = 0;i < itemCount;i++){ viewModelObservableList.add(new DemoItemViewModel(context, entityList.get(i))); }其中itemViewModel我起名叫DemoItemVIewModel:
import android.content.Context; import android.databinding.ObservableField; import com.reliable.baselib.base.BaseViewModel; /** * item条目ViewModel * Created by ge on 2018/2/5. */ public class DemoItemViewModel extends BaseViewModel{ public ObservableField<DemoEntity> observableField = new ObservableField<>(); DemoItemViewModel(Context context, DemoEntity demoEntity){ super(context); observableField.set(demoEntity); } }注:其中ObservableField是设置个观察者数据,后面当我们数据发生变化时,绑定在xml的展示的效果自己会刷新;
下面看看item的xml:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="demoItemViewModel" type="com.reliable.inputa.ui.demo.DemoItemViewModel"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{demoItemViewModel.observableField.get().getName()}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{demoItemViewModel.observableField.get().getAddress()}"/> </LinearLayout> </layout>好了,完事了,看着其实挺多的,刚开始写我也有点懵逼,慢慢理解它的思路,理解理解发现这样的结构很棒,比我写adapter舒服多了。
到这里一个itemBinding的用法就介绍完了;
我们实际的项目会比这个复杂的多,例如:
列表有头部,有尾部,每个item根据type不一样布局不一样,item的点击事件应该在哪里写?等等
下面看看如果你的列表头部尾部或者多item样式的布局应该怎么写;
很简单,只需要给刚才observableArrayList那里修改一下:
// 给RecyclerView添加ObservableList public ObservableList<DemoItemViewModel> viewModelObservableList = new ObservableArrayList<>(); public MergeObservableList<Object> headFooterItems = new MergeObservableList<>() .insertItem("Header") .insertList(viewModelObservableList) .insertItem("Foot"); public final OnItemBind<Object> onItemBind = new OnItemBind<Object>() { @Override public void onItemBind(ItemBinding itemBinding, int position, Object item) { if (String.class.equals(item.getClass())) { itemBinding.set(BR.demoItemViewModel, R.layout.inputa_demo_item); } else if (TestMemberViewModel.class.equals(item.getClass())) { itemBinding.set(BR.demoItemViewModel, R.layout.inputa_demo_item); } } };对应的xml中:
<android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="wrap_content" binding:items="@{demoViewModel.headFooterItems}" binding:layoutManager="@{LayoutManagers.linear()}" binding:itemBinding="@{demoViewModel.onItemBind}"/>
上面的使用MergeObservableList替换原来的observableList,根据你的需要插入数据,插入头或尾巴;
onItemBind复写的方法中就可以根据你的类型不同加载不同的布局;再看看绑定点击事件两种思路:
以前写adapter肯定都用过接口,下面方式一就可以通过接口回调到activity的ViewModel中,先定义一个接口:
public interface OnDemoListener{ void onDemoItemClick(DemoEntity demoEntity); } public OnDemoListener onDemoListener = new OnDemoListener() { @Override public void onDemoItemClick(DemoEntity demoEntity) { } };在上面设置onBinding的时候, 直接将listener传到item中:
public final OnItemBind<Object> onItemBind = new OnItemBind<Object>() { @Override public void onItemBind(ItemBinding itemBinding, int position, Object item) { if (String.class.equals(item.getClass())) { itemBinding.set(BR.demoItemViewModel, R.layout.inputa_demo_item); } else if (TestMemberViewModel.class.equals(item.getClass())) { itemBinding.set(BR.demoItemViewModel, R.layout.inputa_demo_item) // 绑定点击事件 .bindExtra(BR.onDemoListener, onDemoListener); } } };
那么对应的item的xml变成如下:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="demoItemViewModel" type="com.reliable.inputa.ui.demo.DemoItemViewModel"/> <variable name="onDemoListener" type="com.reliable.inputa.ui.demo.DemoViewModel.OnDemoListener"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:onClick="@{() -> onDemoListener.onDemoItemClick(demoItemViewModel)}"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{demoItemViewModel.observableField.get().getName()}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{demoItemViewModel.observableField.get().getAddress()}"/> </LinearLayout> </layout>
这是方式一,还有一种就是将item的点击事件返回给item的ViewModel,然后通过ItemViewModel回传或者设置一个观察者或者EventBus(不推荐)发给总的ViewModel;
开源的那个库中还有几种别的功能,这里不做介绍了,主要是ItemBinding的相关功能;
下周我也要放假回老家了,提前祝大家新年快乐。