# 数据绑定基础类使用
包引用:
* ui库: `compile "com.healthmall.android:healthmall-core-ui:2.0.0-beta1"`
* 基础页面库: `compile "com.healthmall.android:basemodule:2.0.0-beta1"`
## 普通页面
以往的页面,Activity或Fragment等,都是继承`AbstractMvpXXXX`的,在使用数据绑定时,需要继承`AbstractDataBindingXXX`,总体跟MVP的使用上没多大区别,只是多了一个`<B extends ViewDataBinding>`的泛型成员,用于绑定变量。当实现的页面有需要`presenter`的时候,我们还自动进行了`presenter`的绑定(需要xml中定义好`presenter`变量):
public abstract class AbstractDataBindingActivity<T extends BasePresent, B extends ViewDataBinding> extends AbstractMvpActivity<T> implements BaseView {
protected B binding;
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
if (presenter != null) {
binding.setVariable(com.gzdxjk.ihealth.basemodule.BR.presenter, presenter);
}
}
@CallSuper
@Override
public void dealOnForemost(Bundle savedInstanceState) {
super.dealOnForemost(savedInstanceState);
binding = DataBindingUtil.setContentView(this, setContentView());
}
@Override
protected void onDestroy() {
super.onDestroy();
binding.unbind();
}
}
以上就是基础类的全部代码。使用示例略。
## 列表页面
以往的列表页都是通过继承`BaseLoadMoreAdapter`,然后在`onBindViewHolder`方法里面完成数据绑定。因为新版本需要在xml中完成数据绑定,所以我们有了一个新的基础绑定适配器`BaseDataBindingAdapter`,大大的简化了列表开发中的代码量。同时提供了一个接口`BindItem`,用以修饰绑定到列表的VM,并要求其提供一个对应布局layout。得益于此接口,列表可以轻松的处理多种不同类型的VM。`BaseLoadMoreAdapter`为使用`BindItem`的列表提供了构造函数的支持。
`BaseDataBindingAdapter`依然保留与旧的接口相似的初始化方式(`public BaseAdapter(Context context, int layout,List<?> list)`)提供默认统一的布局文件,但不再需要提供泛型限制(因为有数据绑定的动态变量设置能力),即Adapter现在可以接受任何类型的数据,而不管数据是否实现`BindItem`。即便如此,我希望大家不要在使用旧的初始化方式的时候,再往列表添加/替换`BindItem`接口类型的数据(目前是可行);或者是使用新的初始化方式的时候,添加/替换非`BindItem`接口类型的数据(不可行,会报错)。推荐使用新的构造函数和新的`BindItem`接口。下面是示例:
* 使用`BindItem`接口实现统一类型的列表:
VM实现`BindItem`接口:
public class DataBindListItemInfoVM implements BindItem {
...
@Override
public int getBindingLayout(){
return R.layout.your_layout;
}
}
一般情况下不需要额外编写一个类去实现`BaseDataBindingAdapter`,寥寥数行代码就可以完成:
BaseDataBindingAdapter adapter = new BaseDataBindingAdapter(getApplicationContext(), bindingVMs) {
@Override
protected void bindPresenter(BaseBindingViewHolder holder, ViewDataBinding bind) {
bind.setVariable(BR.presenter, value);
}
};
上面`bindPresenter`方法,是一个供以绑定除了数据以外的变量的模版方法,如果需要不需要任何处理,那么整个适配器一行就可以完成。除了这个模版方法以外,`changeViewSize`模版方法同样保留,其他例如`onClickHook`等方法由于数据绑定或者其他历史原因,都无需保留。
* 使用`BindItem`实现多种类型的列表方式和上面方法完全一致,在构造、添加/替换列表元素的时候,去添加你实现了`BindItem`接口的实际的VM即可。
* 使用旧的构造方式实现统一类型的列表,与旧的适配器相比,基本不再需要额外编写一个类,同样简洁的完成了:
BaseDataBindingAdapter adapter = new BaseDataBindingAdapter(getApplicationContext(), R.layout.your_layout, bindingVMs) {
};
## 局部页面元素绑定
目前我们没有一个基础的View来实现我们的MVP的V接口,所以局部的视图元素绑定,直接用绑定库提供生成的的绑定类去inflate即可,具体参考数据绑定教程。
# 项目中的MVPVM规则
与从MVC转换到MVP不同,我们的MVPVM可以看作只是MVP架构的一个数据绑定能力的补充和规范。也没必要有了databinding就一定要用MVVM,这只是一个工具。
MVPVM中M、V、P三者的角色和作用与MVP中一致,而VM则是充当一个数据转换(Transfer Object)和绑定的角色,实际上讲单元测试的时候,我已经向大家提出过VM这一点,所以单元测试的编写也是遵循我们目前的方式。下图箭头表示它们之间数据的流动方向:
Model
↓ ↑
Presenter
↓ ↑
ViewModel
↓
View
为了实现一些代码上的统一和精简,以及保持项目的可读性和可维护性,需要对使用数据绑定的使用进行必要的规范,暂时有以下几点:
* 禁止在布局xml中包含任何业务逻辑,不允许表达式使用各种`+ - * | & instanceOf`等等符号;
* 禁止直接把接口数据作为ViewModel使用!禁止接口数据实现`BindItem`接口!;
* 为了统一使用`BaseDataBindingAdapter`,列表项的布局文件中`<variable>`变量标签的`name`属性**必须**是`item`。同理,使用我们的`AbstractDataBindingXXXX`的页面布局,事件处理的`<variable>`变量标签必须命名为`presenter`;
* 禁止私自自定义控件的已有属性,其他自定义属性必须有统一的文档维护(下面的章节)。自定义控件的自定义属性方法必须写在控件类里面,属性名不能包含任何缩写;
* 严禁为基础通用控件设置任何业务相关的非通用自定义属性;
# 项目中的属性封装
得益于databinding库提供的自定义属性设置器功能,使我们有能力通过简单的属性设置实现我们想要的效果,从而减少重复代码的编写。但同时因为自定义属性是通过`@BindAdapter`注解来实现,编译器生成代码的时候无须关心注解来源。为了防止各种不明所以的属性满天飞,必须有统一的文档维护和规则去限制,避免它成为一把伤人的剑。
通过自动设置器可以设置的属性不再阐述,以下是我们的库中已有的自定义属性封装:
* 流式布局`FlowLayout`中的`child_layout`和`items`属性,同时设置它们即可以填充布局。下面是它们对应的属性和方法定义:
@BindingAdapter({"bind:child_layout", "bind:items"})
public static void bindTextItem(FlowLayout flowLayout, int childLayoutId, List<String> itemList) {
flowLayout.addTextTag(childLayoutId, itemList);
}
* 待补充。
希望大家在项目的过程中,遇到常用的可以自定义的部分积极提出,我再去一一实现。例如需要WebView增加一个url属性,去自动加载设置的地址(因为WebView加载的方法是`loadData`,无法通过自动设置器设置),又如给ViewPager的`addOnPageChangeListener`方法封装自定义属性(ViewPager确实有`setOnPageChangeListener`方法,但已经过时),来进行代码精简。