# 數據綁定基礎類使用
包引用:
* 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`方法,但已經過時),來進行代碼精簡。