一个月没有写博客了;
一个月时间里将以前的两个个项目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的相关功能;
下周我也要放假回老家了,提前祝大家新年快乐。