DBinding權(quán)威使用指南
標(biāo)簽(空格分隔): dbing
使用方式
layout:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<!-- 定義變量: private org.kale.viewModel vm -->
<variable
name="user"
type="org.kale.vm.UserviewModel"
/>
</data>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@{user.name}"/>
</layout>
Activity:
private UserviewModel mUserVm = new UserviewModel();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding b = DBinding.bind(this, R.layout.activity_main); // 設(shè)置布局
DBinding.setVariables(b, mUserVm); //設(shè)置vm
mUserVm.setName("漩渦鳴人"); // textview中就會(huì)自動(dòng)渲染出文字了
}
一、設(shè)計(jì)思路
1.1 三層結(jié)構(gòu)
我們的項(xiàng)目結(jié)構(gòu)里經(jīng)常會(huì)出現(xiàn)這三種東西————M/V/C,這三個(gè)東西一定要廣義理解為層,他們絕對(duì)不是狹義的類對(duì)象(因?yàn)橛行┱Z(yǔ)言中會(huì)有view、controller、model這樣的類,請(qǐng)不要混淆)。所謂各種模式其實(shí)就是這三者的不同組合和通信方式。
.png)
要說(shuō)明白這個(gè)問(wèn)題,就要知道哪些是v,哪些是m,哪些是c。
V:視圖層
v層是可以獨(dú)立數(shù)據(jù)而顯示的,它里面沒(méi)有什么業(yè)務(wù)邏輯,僅僅是做展現(xiàn)。簡(jiǎn)單比喻來(lái)看就是一個(gè)提線木偶,它本身并不會(huì)有生命。Android中的view,比如textview,button,自定義的view當(dāng)然也屬于此類。
除了上述這些類對(duì)象外,activity、fragment算不算v層的東西呢?
如果看前面的定義,他們貌似都處于灰色地帶,很難得到明確的定義。不過(guò)我們可以進(jìn)行思維方式的轉(zhuǎn)換,人為定義它們的意義。相比起傳統(tǒng)的思路,我反而認(rèn)為activity和fragment是屬于v層的,activity可以做視圖的綁定操作,并且可以在activity中方便的寫(xiě)視圖的動(dòng)畫(huà)和布局切換效果。如果把a(bǔ)ctivity變成別的層,那么你就很難找到一個(gè)適合的類去做這些事情了。
M:邏輯層
用于封裝業(yè)務(wù)邏輯相關(guān)的數(shù)據(jù)以及對(duì)數(shù)據(jù)的處理方法。M本身是完全獨(dú)立的個(gè)體,并且應(yīng)該能被監(jiān)聽(tīng)到執(zhí)行的結(jié)果。M不應(yīng)該知道view的存在。m層最直觀的例子就是網(wǎng)絡(luò)請(qǐng)求,網(wǎng)絡(luò)請(qǐng)求是完全獨(dú)立于視圖層的,而且做的事情也是很單一,可以很好的被復(fù)用。
C:控制層
用來(lái)控制數(shù)據(jù)、處理view和數(shù)據(jù)的交互,它主要接收來(lái)自view的交互信號(hào)和數(shù)據(jù)層的改變結(jié)果,然后做相應(yīng)的操作。早期的c層是鍵盤(pán)和鼠標(biāo),所以是可以直接面向用戶進(jìn)行操作的,但是在移動(dòng)時(shí)代它慢慢變成了一個(gè)純的控制對(duì)象。
如果說(shuō)c層是用來(lái)做控制的,adapter算不算c呢?
adapter的意義大家都心知肚明,是用來(lái)做數(shù)據(jù)和view的綁定工作的,順便更新下ui。如果說(shuō)數(shù)據(jù)是血液,view是皮囊,那么adapter就是一個(gè)賦予view生命的輸血機(jī)器。它本該屬于的層最初我是很難把控的,我嘗試過(guò)把它放入c層,也嘗試過(guò)把它放入activity這樣的view層,但都沒(méi)能得到很好的答案。
最終,在commonAdapter這個(gè)項(xiàng)目的啟發(fā)下,明白了adapter最合適的位置應(yīng)該是c層。因?yàn)閍dapter中會(huì)做很多數(shù)據(jù)的處理,比如根據(jù)數(shù)據(jù)類型選擇item這樣的操作。而這樣的操作如果放入view層,就會(huì)讓view層一下子失去復(fù)用的能力,因此adapter放入c層是最合適的選擇。
順便說(shuō)一句,c層是最不容易被復(fù)用的。因此如果一個(gè)東西是和當(dāng)前頁(yè)面獨(dú)有的,不可能被復(fù)用的。那么它就應(yīng)該死在c里,不要把它放出來(lái)。
1.2 MVC
.png)
我剛接觸android的時(shí)候就聽(tīng)過(guò)android是MVC模式的,以為android的view層可以理解為layout(xml)層。但之后發(fā)現(xiàn)很多項(xiàng)目中竟會(huì)在自定義view中處理了很多業(yè)務(wù)邏輯,而且activity經(jīng)常被用來(lái)做了view和controller的事情,慢慢的android項(xiàng)目就成了下面這樣:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main); // 設(shè)置布局(v層的事情)
final Button button = (Button) findViewById(R.id.button); // 綁定view(v層的事情)
button.setOnClickListener(new View.OnClickListener() { // 設(shè)置監(jiān)聽(tīng)器(v層的事情)
@Override
public void onClick(View v) {
// 發(fā)請(qǐng)求,做數(shù)據(jù)的處理(c層的事情)
HttpUtil.doPostAsync("http://www.baidu.com", "kale", new CallBack() {
@Override
public void onRequestComplete(String result) {
button.setText(result); // 更新視圖(v層的事情)
}
});
}
});
}
}
分析
在這段代碼中我們認(rèn)為xml文件就是view層,activity做view和model的綁定操作,看起來(lái)activity又像c又像v。而且,在這種情況下activity會(huì)越來(lái)越臃腫,即便有fragment的加入,也無(wú)濟(jì)于事。這種糟糕的情況就是android設(shè)計(jì)之初對(duì)activity定義不明的一個(gè)惡果。
我先拋幾個(gè)問(wèn)題:
- activity該做view的綁定工作么?
- activity要做view的動(dòng)畫(huà)操作么?
- activity應(yīng)處理從網(wǎng)絡(luò)返回的結(jié)果么?
- activity需要做不同狀態(tài)下view的狀態(tài)的控制么?
我相信大家都有了答案吧。為了解決activity臃腫和含義不清的矛盾,慢慢出現(xiàn)了mvp規(guī)范。
1.3 MVP
.png)
mvp做的事情也相當(dāng)簡(jiǎn)單,僅僅是把mvc做了一個(gè)小小的改造,產(chǎn)生了清晰的封層。
【流程】view操作p,p會(huì)去調(diào)用model執(zhí)行操作,p中接收到結(jié)果后去調(diào)用v來(lái)更新界面。
下面是某個(gè)使用mvp的項(xiàng)目中activity的代碼:
presenter = new AppInfoPresenter(); // p層
mShowPackageNameBtn.setOnClickListener(v -> {
v.setEnabled(false); // activity變成v層,這里控制view的相關(guān)狀態(tài)
// 點(diǎn)擊后的事情交給p做,p做完后應(yīng)該給v一個(gè)回調(diào)。為了說(shuō)明簡(jiǎn)單,這里是同步回調(diào)。
string name = presenter.getPackageInfo(getApplication()); // p層將最終的結(jié)果交給v層
mShowPackageNameBtn.setText(name); // 得到回調(diào)后更新視圖
});
這種方式將activity和xml文件變成了一個(gè)v,那么所有邏輯都交由p處理。這樣的好處就是model對(duì)外層不知情,p對(duì)view不知情。成為了這樣的一個(gè)蛋形結(jié)構(gòu):
.png)
內(nèi)層對(duì)外層不知情的好處就是內(nèi)層可以隨意地做復(fù)用,壞處就是需要建立相互通信的機(jī)制,會(huì)帶來(lái)各種回調(diào)。當(dāng)然,你可以用Rx的方式很簡(jiǎn)單地做回調(diào),但是我們是否真的應(yīng)該采用這種到處拋出回調(diào)的方式呢?
先別急,我們先看看如果上面的例子變復(fù)雜的情況:
我希望點(diǎn)擊按鈕后,出現(xiàn)一個(gè)loading界面,p去做網(wǎng)絡(luò)請(qǐng)求。無(wú)論成功與否,請(qǐng)求回來(lái)后都停止loading。如果成功得到了網(wǎng)絡(luò)數(shù)據(jù),才更新頁(yè)面。
presenter.getPackageInfo(new completeCallback(){
onAny(){
// 停止loading
}
},new successCallback(){
onSuccess(){
// 更新界面
}
});
可以看出mvp中一個(gè)很蛋疼的后遺癥————各種回調(diào)。而且這些回調(diào)都是要自己根據(jù)不同頁(yè)面寫(xiě)的,而且每寫(xiě)一個(gè)回調(diào)就要寫(xiě)一個(gè)接口,接口的參數(shù)也要根據(jù)需求變。我敢保證絕對(duì)沒(méi)辦法一次性知道這個(gè)回調(diào)方法需要幾個(gè)參數(shù),而參數(shù)的類型也不能一次性就確定。
我覺(jué)得太麻煩了,我要簡(jiǎn)單一些。比如讓P對(duì)v知曉,v也知道p的存在,會(huì)不會(huì)簡(jiǎn)單?趕快來(lái)看看這種方案是什么樣的。
首先讓activity實(shí)現(xiàn)某個(gè)接口比如IAppInfoUi,然后讓P調(diào)用這個(gè)接口對(duì)象進(jìn)行交互。
Activity:
presenter = new AppInfoPresenter(); // p層
protected void onCreate(){}
mShowPackageNameBtn.setOnClickListener(v -> {
v.setEnabled(false);
// 點(diǎn)擊后的事情交給p做,不會(huì)給view回調(diào)
presenter.getPackageInfo(getApplication());
});
}
/**
* public方法,讓p去調(diào)用
*/
public void onGotPkgInfo(String name){
// 得到結(jié)果
}
Presenter:
public class AppInfoPresenter extends BasePresenter<IAppInfoUi> implements IAppInfoP {
@Override
public void getPackageInfo(Context context) {
// p對(duì)v知情,直接調(diào)用v中的public方法(onGotPkgInfo())。
// getView其實(shí)得到的就是activity的接口對(duì)象,即IAppInfoUi
getView().onGotPkgInfo(context.getPackageName());
}
}
現(xiàn)在,p和v的交互也不用各種回調(diào)了(將activity整體變成了一個(gè)回調(diào)接口)。那,這種方式有不會(huì)有什么問(wèn)題?
p知道了v,那么p的復(fù)用性就喪失了。正如你所見(jiàn),我利用接口來(lái)降低了二者互相知情造成的影響。但這樣,你就必須在寫(xiě)view的時(shí)候定義接口,但如果你這個(gè)頁(yè)面根本沒(méi)復(fù)用價(jià)值,你還要做接口么。如果不做接口,你怎么知道這個(gè)頁(yè)面真的沒(méi)復(fù)用價(jià)值呢?而且接口的名字和參數(shù)你真的可以一次確定么?
好,我們偷懶一下,先看看能否不定義接口。
Presenter:
public class Presenter {
// 注意這里的activity的類型是具體的而不是接口
public MainActivity mActivity;
public void loadData() {
// request network 做數(shù)據(jù)處理
HttpUtil.doPostAsync("http://www.baidu.com", "kale", (result)-> {
mActivity.onGotData(result); // 拋數(shù)據(jù)給v層
}
});
}
}
Activity:
@Override
protected void onCreate(Bundle savedInstanceState) {
final Presenter presenter = new Presenter(this); // presenter
button.setOnClickListener(v->
presenter.loadData(); // p完全不知道這是因?yàn)辄c(diǎn)擊而觸發(fā)的動(dòng)作,只知道要加載數(shù)據(jù)了
);
}
/**
* 被presenter調(diào)用的方法
*/
public void onGotData(String name) {
button.setText(name); // 更新視圖
}
}
用這種方式p中包含了v對(duì)象,可以不寫(xiě)任何回調(diào)就能直接觸發(fā)v的動(dòng)作,而且不用寫(xiě)接口和回調(diào),甚至還可以支持一個(gè)v對(duì)應(yīng)多個(gè)p的需求。但是如果你這個(gè)view被復(fù)用了,就得改一改。這種方案的一大壞處就是靈活性會(huì)比較低,但實(shí)際運(yùn)用下來(lái)還不錯(cuò),算是一種偷懶的做法。
順便提及一下mvp的重要優(yōu)點(diǎn):
- 當(dāng)activity意外重啟時(shí)presenter不會(huì)被重啟。
- activity重啟時(shí),presenter與activity會(huì)重新綁定,根據(jù)數(shù)據(jù)恢復(fù)activity狀態(tài)。
- 而當(dāng)activity真正銷毀時(shí),對(duì)應(yīng)地presenter應(yīng)該隨之銷毀
這樣的好處可以解決以下2個(gè)很實(shí)際的問(wèn)題:
不會(huì)每次翻轉(zhuǎn)屏幕都去顯示進(jìn)度條,重新加載數(shù)據(jù)。
某些低端機(jī),內(nèi)存不足時(shí)activity被銷毀,但p會(huì)持有數(shù)據(jù),避免數(shù)據(jù)丟失。
1.4 谷歌的MVVM
我們看到了上述mvp的兩種實(shí)現(xiàn)方案:
第一種靈活但是寫(xiě)起來(lái)復(fù)雜;第二種簡(jiǎn)單,但是靈活性不足。
mvvm模式利用數(shù)據(jù)綁定的特性,自動(dòng)化實(shí)現(xiàn)了第一種的回調(diào)模式,很棒對(duì)不?但也僅此而已。
先來(lái)看看谷歌的dataBinding是怎么做分層處理的吧:
1.先定義一個(gè)User類
public class User {
private final String firstName;
private final String lastName;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
void onSomething(){
// 這里去加載網(wǎng)絡(luò)
}
}
2.在layout文件中綁定user
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{"from:" + user.lastName}" />
3.在java代碼中設(shè)置實(shí)現(xiàn)和數(shù)據(jù)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityBasicBinding binding = DataBindingUtil.setContentView(
this, R.layout.activity_basic);
binding.setUser(new User());
}
看完之后,是不是感覺(jué)很精妙。對(duì),就是這么“精妙”,精妙到view都沒(méi)辦法被復(fù)用了。來(lái)看看這行代碼:
android:text="@{"from:" + user.lastName}"
天哪!你怎么知道這個(gè)布局文件不會(huì)被復(fù)用,如果復(fù)用了,這里如果展示的是一個(gè)ad.info你該怎么處理。這個(gè)先不說(shuō),databinding還支持xml中寫(xiě)java代碼,比如引入靜態(tài)方法和簡(jiǎn)易判斷什么的。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{MyStringUtils.capitalize(user.firstName)}" // 靜態(tài)方法
// or
android:text="@{user.displayName != null ? user.displayName : user.lastName}" />
干的漂亮,直接讓layout文件和java融為一體?,F(xiàn)在請(qǐng)你告訴我我該怎么復(fù)用這個(gè)layout文件。如果我在這里也寫(xiě)了java的簡(jiǎn)單判斷,java代碼中也寫(xiě)了一些邏輯,我怎么快速定位問(wèn)題。
這么寫(xiě)帶來(lái)的嚴(yán)重后果就是,layout文件和java類強(qiáng)耦合,而且很難定位問(wèn)題。你就永遠(yuǎn)不知道這個(gè)邏輯是在java代碼中寫(xiě)的還是xml中寫(xiě)的了。
注意:我強(qiáng)烈不建議在xml中寫(xiě)java代碼,它會(huì)增加定位問(wèn)題的難度。
1.5 理想化的MVVM
理想化的mvvm最好是只用寫(xiě)少量代碼就完成了具體需求的東西,超級(jí)酷!我希望我加載網(wǎng)絡(luò)成功后自動(dòng)會(huì)更新到視圖上。
之前的做法:
建立xml,在activity中找到view,建立數(shù)據(jù)模型,寫(xiě)好網(wǎng)絡(luò)回調(diào),回調(diào)成功后依次設(shè)置view的狀態(tài)。
理想化的做法:
寫(xiě)好數(shù)據(jù)模型,建立xml時(shí)直接綁定數(shù)據(jù)模型,在activity中寫(xiě)好回調(diào)就行。
代碼如下:
數(shù)據(jù)模型:
public class UserInfo extends BaseObservable {
private String name;
@Bindable
public String getName() { return name; }
public void setName(String name) {
this.name = name;
notifyPropertyChanged(com.kale.dream.BR.name);
}
}
布局文件:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.kale.dream.UserInfo"/>
</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
/>
</layout>
Activity代碼:
public class Dream01 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Dream01Binding binding = DataBindingUtil.setContentView(this, R.layout.dream_01);
HttpUtil.doGet("http://www.kalexxxxxx", new HttpUtil.HttpCallback() {
@Override
public void onSuccess(UserInfo info) {
binding.setUser(info);
}
});
}
}
如果真的可以這么寫(xiě),那么一切都會(huì)簡(jiǎn)單很多。三個(gè)文件搞定了model,vm,v層。只是如果真的這么寫(xiě)就會(huì)出現(xiàn)很多問(wèn)題。比如你直接對(duì)json的數(shù)據(jù)模型做了很多的處理,讓model的set和get方法不在純粹。如果你今天不用dataBinding了,以后要改就會(huì)相當(dāng)困難。現(xiàn)在再來(lái)復(fù)雜一點(diǎn),我希望對(duì)name進(jìn)行判斷,根據(jù)name的數(shù)據(jù)不同讓view呈現(xiàn)View.VISIBLE, View.INVISIBLE, View.GONE三種狀態(tài)。
數(shù)據(jù)模型:
public class UserInfo extends BaseObservable {
private String name;
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(com.kale.dream.BR.name);
}
@Bindable
public int getViz() {
switch (name) {
case "jack":
return View.VISIBLE;
case "tony":
return View.INVISIBLE;
default:
return View.GONE;
}
}
}
布局文件:
//……
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
android:visibility="@{user.viz}" // 僅僅添加了這一行
/>
其余的文件不變。
我們?cè)黾恿艘粋€(gè)需求,只需要?jiǎng)觾蓚€(gè)文件,是不是很贊。那么壞處就是對(duì)model的操作太多了,它已經(jīng)不再純粹,可能未來(lái)還會(huì)做更多的事情。為了解決這個(gè)問(wèn)題,谷歌說(shuō)不要再動(dòng)model了,我允許你在xml中寫(xiě)java代碼:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
android:visibility="@{user.name.equals(jack)?View.gone:View.visible}"
/>
這樣的做法就可以讓我們不動(dòng)model類,只需要在xml中做邏輯操作。只可惜xml中肯定不能完成所有的視圖操作(比如動(dòng)畫(huà)),而且xml是有復(fù)用價(jià)值的。所以我給出的結(jié)論就是,理想很豐滿,現(xiàn)實(shí)很骨感。
1.6 理想妥協(xié)于現(xiàn)實(shí)后的MVVM
我們不希望一個(gè)框架對(duì)現(xiàn)有的項(xiàng)目結(jié)構(gòu)做太多的影響,框架影響到用于json解析的model是不能容忍的!這樣的話,我們就要建立一個(gè)給框架用的數(shù)據(jù)對(duì)象,所以vm現(xiàn)在就變成了一個(gè)和前面的json的model無(wú)關(guān)的獨(dú)立類。
需要注意的是:
vm僅僅處理和視圖展示內(nèi)容有關(guān)的邏輯,比如對(duì)顯示的內(nèi)容做格式化這樣的事情。除此之外不應(yīng)處理其他的視圖邏輯。
數(shù)據(jù)模型:
public class UserInfo {
public String name;
}
viewModel:
public class DreamVm extends BaseObservable{
private String name;
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(com.kale.dream.BR.name);
}
@Bindable
public int getViz() {
switch (name) {
case "jack":
return View.VISIBLE;
case "tony":
return View.INVISIBLE;
default:
return View.GONE;
}
}
///////////////////////////////////////////////////////////////////////////
// 事務(wù)操作
///////////////////////////////////////////////////////////////////////////
public void load() {
HttpUtil.doGet("http://www.kalexxxxxx", new HttpUtil.HttpCallback<UserInfo02>() {
@Override
public void onSuccess(UserInfo02 info) {
setName(info.name);
}
});
}
}
布局文件:
// 將之前的userinfo換為vm來(lái)綁定
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{vm.name}"
android:visibility="@{vm.viz}"
/>
Activity代碼:
public class DreamAct extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
final Dream02Binding binding = DataBindingUtil.setContentView(this, R.layout.dream_02);
DreamVmp02 viewModel = new DreamVmp02();
binding.setVm(viewModel);
viewModel.load(); // 加載網(wǎng)絡(luò)請(qǐng)求
}
}
這樣的做法就沒(méi)啥問(wèn)題了,vm處理了邏輯,model變成純粹的類。而且也不用在xml中寫(xiě)什么java代碼了,只要綁定vm的字段即可。view相關(guān)的java代碼都在activity中完成,如果view的邏輯出錯(cuò)了直接進(jìn)入activity中定位即可。
我們注意到了vm中做了加載數(shù)據(jù)的操作,應(yīng)不應(yīng)該這么寫(xiě)呢?
不應(yīng)該!viewModel就是view的數(shù)據(jù)模型,所以不應(yīng)該圖省事,在vm中寫(xiě)和view無(wú)關(guān)的業(yè)務(wù)邏輯。所以下面的這塊代碼不應(yīng)該出現(xiàn)在vm中,而是應(yīng)該放在別的地方。至于放在哪里合適,這就是后話了。
///////////////////////////////////////////////////////////////////////////
// 事務(wù)操作
///////////////////////////////////////////////////////////////////////////
public void load() {
HttpUtil.doGet("http://www.kalexxxxxx", new HttpUtil.HttpCallback<UserInfo02>() {
@Override
public void onSuccess(UserInfo02 info) {
setName(info.name);
}
});
}
1.5 MVVM模式
看到了databinding的不足和問(wèn)題,但我們也不能否認(rèn)它的好處。它優(yōu)雅的幫我們搞定了回調(diào),而且支持了數(shù)據(jù)的自動(dòng)綁定。基于以上的分析,我做了一個(gè)小小的改造,讓layout和viewModel緊密聯(lián)系,如圖所示:
.png)
view層:具體的view,activity,fragment等,做ui展示、ui邏輯、ui動(dòng)畫(huà)。
viewModel層:由插件自動(dòng)生成的具體類,是view展示的數(shù)據(jù)的java抽象,僅能被model層直接操作。
model層:非ui層面的業(yè)務(wù)邏輯的實(shí)現(xiàn)。包含網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)遍歷等操作,是很多具體類的抽象載體。
這里除了model層的定義很抽象外,其余的v、vm層我都給出了具體類做載體,下面詳細(xì)說(shuō)下model層包含些什么。
model層:
因?yàn)閙odel層是很多具體類的聚合,主要是做和展示無(wú)關(guān)的業(yè)務(wù)邏輯,在其中也會(huì)包含很多具體類。
.png)
要實(shí)現(xiàn)一個(gè)頁(yè)面的需求,我們可能會(huì)用到很多類,包括工具類和各種庫(kù)。這些類都提供了一個(gè)或多個(gè)功能,我們利用這些功能便可最終實(shí)現(xiàn)需求。而所有的調(diào)用應(yīng)該是由一個(gè)類來(lái)進(jìn)行的,這個(gè)類你可以叫做presenter,也可以叫做別的名字??傊?,它就是一個(gè)執(zhí)行非ui層面邏輯的個(gè)體。
需要注意的是,這個(gè)p和mvp中的p是無(wú)關(guān)的,我僅僅是沒(méi)有找到很好的名字,才稱之為presenter。
二、如何使用Dbinding
2.1 編寫(xiě)layout文件
首先我們先建立一個(gè)xml文件,這里推薦通過(guò)改模板的方式快速建立一個(gè)dbinding風(fēng)格的layout文件。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<!-- 定義變量: private org.kale.viewModel vm -->
<variable
name="user"
type="org.kale.vm.UserviewModel"
/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
</layout>
在建立好的layout文件中的<data>標(biāo)簽里可以由<variable/>標(biāo)簽來(lái)定義viewModel,這個(gè)對(duì)象之后會(huì)和view的屬性進(jìn)行綁定。目前,我們不用管這個(gè)類是否存在,我們只需要定義你想要的類和其對(duì)象的名字即可,比如:
<data>
<!-- 定義變量: private org.kale.UserviewModel user -->
<variable type="org.kale.UserviewModel" name="user"/>
</data>
定義好了變量名,我們就可以將其內(nèi)部的字段與view的attribute進(jìn)行綁定了:
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:text="@{user.name}"
/>
這里我將user中的name字段與text這個(gè)屬性進(jìn)行了綁定。(需要注意,這個(gè)text字段現(xiàn)在我們還沒(méi)創(chuàng)建,僅僅是寫(xiě)了出來(lái))。
現(xiàn)在這個(gè)textview展示的文字就是我們定義好的UserviewModel中的name的值了。通過(guò)對(duì)textview的了解,我們可以推測(cè)出name這個(gè)字段肯定是CharSequence類型的。
2.2 編寫(xiě)layout文件時(shí)的問(wèn)題
1.如何建立layout模板
idea中有個(gè)功能就是可以編輯文件模板,下面演示下如何定義這個(gè)模板。

2.為什么數(shù)據(jù)塊是用<data>包裹,為什么叫這個(gè)名字呢?
因?yàn)檫@里的xml變成了兩部分,一個(gè)就是傳統(tǒng)的viewgroup包裹的布局文件,其余就是需要和布局文件綁定的vm。而vm的區(qū)塊用data做命名是比較合適的,因?yàn)関m本身就是數(shù)據(jù)。
這里需要格外注意的是,現(xiàn)在的layout文件已經(jīng)不僅僅是一個(gè)單純的布局文件了,更合理的叫法是視圖+數(shù)據(jù)文件。
3.為什么這里定義變量是用<variable/>標(biāo)簽,而不是別的名字呢?
variable本身就是變量的意思,這個(gè)名稱是很合適的。當(dāng)然如果寫(xiě)過(guò)js的同學(xué),會(huì)更加熟悉它的縮寫(xiě)var。但這里的起名上,大家應(yīng)該不會(huì)有什么爭(zhēng)議和容易理解錯(cuò)誤的地方。
4.為什么是通過(guò)type和name這兩個(gè)屬性來(lái)定義一個(gè)vm?
在java世界中我們是通過(guò):
private org.kale.viewModel vm
來(lái)做變量的定義的。在xml文件中不存在什么相互調(diào)用的情況,定義的變量都是private的,所以省略了private這個(gè)關(guān)鍵字。至于,為什么變量的對(duì)象名用type,名字用name,這也是有原因的。
為了說(shuō)清楚這個(gè)問(wèn)題,我們先來(lái)看看jdk中Field這個(gè)類的部分代碼。
package java.lang.reflect;
public final class Field extends AccessibleObject implements Member {
private Class<?> clazz;
private int slot;
private String name; // 參數(shù)的名字【name】
private Class<?> type; // 參數(shù)的類型【type】
private int modifiers;
private transient String signature;
private transient FieldRepository genericInfo;
private byte[] annotations;
private FieldAccessor fieldAccessor;
private FieldAccessor overrideFieldAccessor;
private Field root;
private transient Map<Class<? extends Annotation>, Annotation> declaredAnnotations;
// 省略...
}
可見(jiàn),這里的命名是參照了java中field的命名來(lái)的。這種命名方式會(huì)比較符合大眾直覺(jué),明白了這個(gè)原因,相信以后定義的時(shí)候就不會(huì)有什么疑惑了。
5.viewModel的name字段該怎么取名?
既然我們的viewModel會(huì)和view進(jìn)行綁定,而且view是有可能被復(fù)用的。所以這里的取名我強(qiáng)烈建議和view的意義有關(guān)。千萬(wàn)不可脫離view的意義隨便取名字,這樣以后你用的時(shí)候就會(huì)很麻煩。簡(jiǎn)單來(lái)說(shuō),你完全可以參考之前給view取id的做法來(lái)給viewModel取名字。
6.viewModel的type應(yīng)該怎么寫(xiě)?
既然我們名字搞定了,那么類名基本就出來(lái)了。這里需要注意的是,這里的類名是包含完整包名的。我強(qiáng)烈建議所有的viewModel都在一個(gè)包中,不要隨便放。因?yàn)関iewModel以后可能會(huì)出現(xiàn)重命名和被刪除的情況,放在一個(gè)包下面方便管理。這里我是在vm這個(gè)包下面放所有的viewModel,所以就取了org.kale.vm.UserviewModel這樣一個(gè)名字。至于其他的viewModel的包名前綴我們也可以規(guī)范為org.kale.vm。
7.可以在同一個(gè)xml中寫(xiě)兩個(gè)相同類型的viewModel么?
可以,但沒(méi)必要!
在java中會(huì)出現(xiàn)這樣的情況:
String firstName;
String lastName;
在一個(gè)類文件中定義了兩個(gè)相同類型(String)的field。但是在xml中這種情況是嚴(yán)格禁止的,也是完全沒(méi)必要的。因?yàn)関iewModel是一個(gè)有明確含義的對(duì)象,并不是基本類型。而且其綁定地view也是特定的,完全沒(méi)必要定義兩個(gè)相同類型的viewModel。順便提一下,兩個(gè)不同類型的viewModel的name必須是不同的,這個(gè)和java的規(guī)則完全一致的。
8.真的不用寫(xiě)具體的viewModel類么?
是的,不用。你在xml中定義好了
<variable
name="user"
type="org.kale.vm.UserviewModel"
/>
后,這個(gè)UserviewModel會(huì)通過(guò)插件(或通過(guò)快捷鍵)出現(xiàn)在相應(yīng)的包下,你定義好就可以直接用了,沒(méi)必要關(guān)心具體的實(shí)現(xiàn)。這也是dbinding插件的一個(gè)強(qiáng)大之處!
9.如何在別的xml中復(fù)用已經(jīng)定義好的類?
viewModel的一大特性就是可復(fù)用,viewModel和view都可以是多對(duì)多的關(guān)系。比如你這個(gè)UserviewModel中包含了username這個(gè)字段,而別的xml文件中正好需要這個(gè)UserviewModel,你完全可以把username定義到那個(gè)xml文件中。這樣兩個(gè)xml文件就會(huì)共用一個(gè)共同類型的數(shù)據(jù)。復(fù)用的方式很簡(jiǎn)單,就是在別的xml文件中寫(xiě)上
<variable
name="user"
type="org.kale.vm.UserviewModel"
/>
就行。
10.如何知道一個(gè)vm是否已經(jīng)存在?
目前如果已經(jīng)存在的vm會(huì)有代碼提示的,如果沒(méi)有代碼提示,并且報(bào)紅的就是之前沒(méi)有的vm。
11.可以在一個(gè)xml文件中定義多個(gè)viewModel么?
當(dāng)然可以,正如我所說(shuō)的:viewModel是和view綁定的。一個(gè)界面中有多個(gè)不同模塊的view是很常見(jiàn)的,遇到這樣的情況你完全可以在xml中定義多個(gè)viewModel。要知道viewModel和view都是多對(duì)多的關(guān)系。
12.viewModel如何做全局改名、刪除、移動(dòng)這樣的重構(gòu)操作?
1.改名和改包名
這個(gè)就是和重構(gòu)相關(guān)的問(wèn)題了。
我們?cè)趚ml中通過(guò)插件產(chǎn)生了viewModel,但如果要改包名和改變viewModel的類名的時(shí)候,最簡(jiǎn)單快捷的方式是,進(jìn)入到這個(gè)類的實(shí)體中,通過(guò)idea的重構(gòu)工具進(jìn)行修改。這樣所有的改動(dòng)會(huì)自動(dòng)同步到使用這個(gè)類的xml文件中了。當(dāng)然,你也可以在這個(gè)類被調(diào)用的地方通過(guò)重構(gòu)工具進(jìn)行改名。

2.刪除
至于刪除某個(gè)viewModel也是一樣的,仍舊是對(duì)java類進(jìn)行操作。刪除的時(shí)候注意排查下用到的地方,以免出錯(cuò)。

13.viewModel中的字段如何做改名、刪除這樣的操作?
1.改名
我們先來(lái)看下插件會(huì)給我們通過(guò)我們的xml生成什么東西:
package org.kale.vm;
public class UserviewModel extends BaseviewModel {
private java.lang.CharSequence name;
public final void setName(java.lang.CharSequence name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public final java.lang.CharSequence getName() {
return this.name;
}
}
我們看到了我們定義的name字段和其get和set方法。如果我們突然想把這個(gè)“name”改名為“nickname”,或者是刪除這個(gè)name字段呢?做法就是直接重構(gòu)name這個(gè)字段。
下面為了演示方便,減少干擾選項(xiàng)。我把name這個(gè)過(guò)于通用的字母先改為了nickname。我將演示如何將nickname通過(guò)idea的重構(gòu)工具改為name。

2.刪除
因?yàn)閕dea對(duì)于databinding的支持力度很低(未來(lái)或許就可以通過(guò)重構(gòu)工具做了),所以在重構(gòu)字段的時(shí)候只能我們自己去排查了。我的排查方案是通過(guò)檢索當(dāng)前類使用過(guò)的地方,來(lái)看下使用當(dāng)前類的xml中有沒(méi)有使用過(guò)我要?jiǎng)h除的字段,如果有就進(jìn)行處理,如果沒(méi)有就直接刪除。以此來(lái)避免刪除后出現(xiàn)程序出錯(cuò)的問(wèn)題。

14.如果我不想通過(guò)插件生成vm,可以手動(dòng)寫(xiě)么?
當(dāng)然可以的,只需要在xml中加入ignore="true"就好。這樣插件就會(huì)忽略這個(gè)vm,交給開(kāi)發(fā)者自己去建立。
<variable
name="vm"
ignore="true" // 加上這個(gè)屬性就會(huì)被插件無(wú)視掉
type="kale.db.ignore.IgnoredviewModel"
/>
15.如果插件生成的viewModel的屬性不能滿足我的要求,我可以自己配置生成規(guī)則么?
可以的,只需要做下面兩步:
1.在項(xiàng)目中的values下的dbinding_config.xml文件中,增加插件生成的規(guī)則:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
For original view.
Example: android:text="name"
-->
<string name="text">java.lang.CharSequence</string>
<!--
For Custom view
Example: bind:customAttr="name"
下面這個(gè)就是我自定義的規(guī)則,如果屬性名是customAttr,那么vm中的字段類型是CharSequence
-->
<string name="customAttr">java.lang.CharSequence</string>
</resources>
2.在隨便一個(gè)類中寫(xiě)入如下的java代碼:
@BindingAdapter("app:customAttr")
public static void setSrc(CustomView view, CharSequence c) {
view.setSpecialText(c);
}
這樣插件在自動(dòng)生成代碼的時(shí)候就會(huì)讀取你新加入的規(guī)則,生成你想要的字段類型了。
2.3 編寫(xiě)java代碼
上面說(shuō)了那么多的layout文件的編寫(xiě)策略,現(xiàn)在該說(shuō)下如何寫(xiě)java代碼了。java代碼主要做的工作就是綁定vm和layout文件,其余的操作就直接對(duì)vm進(jìn)行就行了。對(duì)于復(fù)雜的界面,可以把ui無(wú)關(guān)的邏輯放入presenter中。
Activity:
public class MainActivity extends AppCompatActivity {
// 管理界面事件的vm
private EventViewModel mEvent = new EventViewModel();
private final UserViewModel mUserVm;
private ActivityMainBinding b;
public MainActivity() {
mUserVm = new UserViewModel();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bindViews();
setViews();
doTransaction();
}
private void bindViews() {
b = DBinding.bindViewModel(this, R.layout.activity_main, mEvent, mUserVm);
}
private void setViews() {
mEvent.setOnClick(v -> {
if (v == b.userInfoInclude.headPicIv) {
startActivity(UserDetailActivity.withIntent(MainActivity.this, mUserVm));
}
});
}
private void doTransaction() {
MainPresenter presenter = new MainPresenter(mUserVm);
if (presenter.init(this)) {
Toast.makeText(MainActivity.this, "Init Complete", Toast.LENGTH_SHORT).show();
}
presenter.loadData();
}
}
MainPresenter:
public class MainPresenter {
private UserviewModel mUserviewModel;
public MainViewModel(UserviewModel userviewModel) {
mUserviewModel = userviewModel;
}
/**
* 這個(gè)當(dāng)然可以放在構(gòu)造方法中進(jìn)行,我這里為了說(shuō)明view層調(diào)用vm的方法,強(qiáng)制加入了一個(gè)回調(diào)。
*/
public boolean init(Context context) {
mUserviewModel.setPic(BitmapFactory.decodeResource(context.getResources(),
R.drawable.mingren));
mUserviewModel.setName("漩渦鳴人"); // textview中就會(huì)自動(dòng)渲染出文字了
return true;
}
public void loadData(){
// 模擬加載網(wǎng)絡(luò)成功,將名字更新的操作
mUserviewModel.setName("kale");
}
// java層面的測(cè)試,不用安裝apk
public static void main(String[] args) {
UserviewModel userviewModel = new UserviewModel();
MainPresenter model = new MainPresenter(userviewModel);
}
}
2.4 編寫(xiě)java代碼時(shí)的問(wèn)題
1.presenter會(huì)被復(fù)用么?
會(huì),但是極少。因?yàn)閜是和某個(gè)特定的邏輯極其相關(guān)的,因此復(fù)用p的情況十分少見(jiàn)。
2.v和p的關(guān)系是什么樣的?
p可以做很多和業(yè)務(wù)邏輯相關(guān)的操作,但是vm必須是純粹的,vm對(duì)p完全不知情。
一個(gè)p會(huì)操作多個(gè)vm,一個(gè)vm也會(huì)被多個(gè)p使用。
注意:在本框架中vm是由框架自動(dòng)生成的,強(qiáng)烈不建議對(duì)vm直接做增添與業(yè)務(wù)邏輯相關(guān)的事情。
3.有了mvvm框架后是否還需要fragment?
需要。因?yàn)閍ctivity中肯定會(huì)有不同的ui區(qū)塊,fragment既可以劃分不同的ui區(qū)域,又可以做到讓這些細(xì)顆粒度的ui能夠被復(fù)用,因此還是需要fragment來(lái)幫忙解耦ui,給activity減負(fù)的。
4.dbinding能否支持雙向綁定?
不準(zhǔn)備支持。因?yàn)関iew的事件和vm的數(shù)據(jù)綁定其實(shí)是無(wú)關(guān)的,而且谷歌db的設(shè)計(jì)思路,本身就是單向的。如果非要套雙向綁定,我不能確保支持所有的view,所以目前是不準(zhǔn)備支持的。
5.p對(duì)vm的操作是否必須在ui線程中?
可以在任何線程中操作vm,再也不用切回主線程操來(lái)操作作ui了。
6.如果p中的某些操作需要通知給activity,該怎么處理?
強(qiáng)烈建議用回調(diào)的方式做通知,不要把a(bǔ)ctivity通過(guò)構(gòu)造方法傳給p。如果傳了,就需要注意持有activity對(duì)象的問(wèn)題,小心造成內(nèi)存泄漏。
順便問(wèn)一句,為什么fragment必須需要持有activity的引用呢?
首先,fragment也是ui,fragment中需要有很多和context有關(guān)的操作。比如啟動(dòng)activity什么的,所以需要持有activity對(duì)象來(lái)做這些事情。最重要的是,fragment本身是可以對(duì)用戶行為產(chǎn)生事件的,而p絕對(duì)不會(huì)自己產(chǎn)生事件,必須通過(guò)外部觸發(fā)才行。因此p完全可以走純回調(diào)的方式,不必持有任何全局的context對(duì)象。
public String getPackageName(Activity context) {
return context.getPackageName();
}
這個(gè)例子中,我通過(guò)參數(shù)傳入context,利用return返回結(jié)果。例子雖然簡(jiǎn)單,但對(duì)于復(fù)雜的情況也是是如此。如果你覺(jué)得回調(diào)寫(xiě)起來(lái)很麻煩,不妨試試用rxJava的形式做。
7.在mvvm框架中應(yīng)該有什么編碼思路?
應(yīng)該有明確的分層思路。在mvvm中我們應(yīng)該把所有數(shù)據(jù)同步的事情交給框架,而不是自己去維護(hù)。將v層的邏輯(如:動(dòng)畫(huà),控件A改變引起的控件B改變等)獨(dú)立寫(xiě)出;在p中獨(dú)立寫(xiě)出數(shù)據(jù)對(duì)viewModel產(chǎn)生影響的邏輯。
下面舉個(gè)adapter的例子:
/**
* 數(shù)據(jù)改變后ui會(huì)做一些改變。
* 應(yīng)該利用對(duì)vm的字段監(jiān)聽(tīng)的方式做處理,不應(yīng)該在數(shù)據(jù)改變時(shí),通過(guò)開(kāi)發(fā)者做ui層面的更新。
*
* @param bind 為什么不是單一監(jiān)聽(tīng)器,而是觀察者模式?
* 因?yàn)闀?huì)有多個(gè)東西對(duì)同一個(gè)數(shù)據(jù)進(jìn)行監(jiān)聽(tīng),如果是單一的就沒(méi)辦法實(shí)現(xiàn)這個(gè)功能。
*/
public void notifyData(final NewsItemBinding bind) {
mviewModel.addOnPropertyChangedCallback((sender, propertyId)-> {
// 監(jiān)聽(tīng)title的改變,然后設(shè)置文字
if (propertyId == kale.db.BR.title) {
setSmartText(bind.title, mviewModel.getTitle());
}
}
});
}
/**
* 如果有數(shù)據(jù),那么就顯示textView;
* 如果沒(méi)數(shù)據(jù),那么就讓textView消失
*/
public void setSmartText(TextView textView, CharSequence text) {
textView.setVisibility(!TextUtils.isEmpty(text) ? View.VISIBLE : View.GONE);
}
在數(shù)據(jù)來(lái)的時(shí)候,數(shù)據(jù)僅僅對(duì)vm進(jìn)行綁定,不用考慮綁定后ui層面的邏輯:
///////////////////////////////////////////////////////////////////////////
// 這里就僅僅做數(shù)據(jù)和ui的綁定工作了,不用想ui層面的任何邏輯,這個(gè)算是數(shù)據(jù)層面的綁定工作。
///////////////////////////////////////////////////////////////////////////
/**
* 將viewModel和model的數(shù)據(jù)進(jìn)行同步
* model模型可能很復(fù)雜,但viewModel的模型很簡(jiǎn)單,這里就是做二者的轉(zhuǎn)換。
*/
@Override
public void handleData(NewsInfo data, int pos) {
mviewModel.setTitle(data.title);
}
8.如果我這個(gè)界面本身就沒(méi)有復(fù)用價(jià)值,能不能不用viewModel?
我們知道vm算是對(duì)xml文件的一種抽象,那么如果我這個(gè)界面本身就沒(méi)復(fù)用價(jià)值,能不能直接把p當(dāng)作vm,直接綁定p中的字段呢?
這樣做當(dāng)然是可行的。但問(wèn)題就在于,你真的可以確保某個(gè)xml沒(méi)有復(fù)用價(jià)值么?如果你當(dāng)前認(rèn)為無(wú)復(fù)用價(jià)值的xml,以后卻要被復(fù)用了,那么你之前偷懶做的事情,對(duì)以后的人來(lái)說(shuō)就是災(zāi)難。雖然需要改一兩行代碼沒(méi)啥問(wèn)題,但對(duì)于程序設(shè)計(jì)來(lái)說(shuō),你之前的設(shè)計(jì)方案和現(xiàn)在的需求產(chǎn)生了沖突,就得重新?lián)Q設(shè)計(jì)思路,這其實(shí)是有些嚴(yán)重的。
在android設(shè)計(jì)之初就給出了xml文件和java代碼分離的編碼方案。但仍舊允許在java代碼中通過(guò)new出view來(lái)寫(xiě)ui。xml和java分離的設(shè)計(jì)方案就是強(qiáng)制復(fù)用的思路,而activity中手動(dòng)寫(xiě)view的編碼方案就是所謂的無(wú)復(fù)用思路。二者一類比,你就知道你需不需要寫(xiě)vm了。
9.在Activity中應(yīng)該做什么事情?
Activity中應(yīng)該做一些view的配置工作。比如配置recyclerView的layoutmanager,設(shè)置下下拉刷新,view的動(dòng)畫(huà)效果等等。
如果你的view的某種狀態(tài)的改變會(huì)引起其他view的改變,這個(gè)邏輯操作也需要放入activity中。很常見(jiàn)例子的就是,輸入密碼框的旁邊有個(gè)是否顯示明文密碼的按鈕,點(diǎn)擊按鈕會(huì)把密碼已明文的形式顯示出來(lái),再點(diǎn)就變成了密文。這個(gè)東西是絕對(duì)屬于ui邏輯的范疇,所以應(yīng)該寫(xiě)在activity中,并且不應(yīng)影響到vm和p。
10.我們真的不需要view的id了么?
我們?nèi)耘f需要id,只是不再需要findViewById了!
我們通過(guò)監(jiān)聽(tīng)vm的某個(gè)字段來(lái)做相應(yīng)的操作,但如上一個(gè)問(wèn)題所說(shuō)到的,密碼是否顯示的按鈕和輸入框的相互作用是不應(yīng)該用vm做的。所以,在ui層面的邏輯中肯定還會(huì)有大量的id出現(xiàn)。幸好,databinding幫我們自動(dòng)生成了id對(duì)象,再也不用寫(xiě)findViewById了。
11.Adapter應(yīng)該放在哪里?
adapter的位置比較尷尬,而且復(fù)用價(jià)值不高,實(shí)踐了很久后,我推薦放入p中。在dbinding中,我提供了ObservableArrayList這個(gè)list做數(shù)據(jù)的處理?,F(xiàn)在只需要對(duì)list進(jìn)行操作即可,不用關(guān)心界面的更新問(wèn)題。notifyDataSetChanged()會(huì)自動(dòng)執(zhí)行。
public MainPresenter(UserViewModel userVm, final Activity activity) {
mList = new ObservableArrayList<>();
mUserVm.setAdapter(new CommonRcvAdapter<NewsInfo>(mList) {
@NonNull
@Override
public AdapterItem<NewsInfo> createItem(Object o) {
return new GameItem(activity);
}
});
}
public void loadData() {
List<NewsInfo> data = NetworkService.loadDataFromNetwork();
mList.addAll(0, data); // 再也不用手動(dòng)寫(xiě)notifyDataSetChanged()了
}
12.什么該在xml中定義vm的字段?
當(dāng)數(shù)據(jù)的變化會(huì)“直接”引起view的某個(gè)屬性改變,那么就應(yīng)該在layout中寫(xiě)一個(gè)vm的字段進(jìn)行綁定。如果這個(gè)view的某個(gè)狀態(tài),是根據(jù)view其他的狀態(tài)改變而改變的,和數(shù)據(jù)層無(wú)關(guān)。那么就不應(yīng)該定這個(gè)字段,而是用監(jiān)聽(tīng)vm字段的方式來(lái)做。
監(jiān)聽(tīng)的方案:
mGameVm.addOnValueChangedCallback(id -> {
switch (id) {
case BR.title:
b.titleTv.setVisibility(mGameVm.getViz() ? View.VISIBLE : View.GONE);
break;
case BR.isLikeText:
final int color =
mGameVm.getIsLikeText().equals(LIKED) ? R.color.yellow : R.color.gray;
b.isLikeText.setTextColor(getResources().getColor(color));
break;
}
});
13.如果兩個(gè)頁(yè)面需要同步很多數(shù)據(jù),可以直接共用vm么?
當(dāng)然可以!vm自身的自動(dòng)綁定特性會(huì)讓兩個(gè)頁(yè)面共用數(shù)據(jù)變得十分簡(jiǎn)單,可以通過(guò)viewModel.toSerializable()來(lái)將其序列化,然后在接收的地方通過(guò):
NewsviewModel vm = NewsviewModel.toviewModel(getIntent().getSerializableExtra(KEY));
得到它。得到后,你就可以方便的利用上個(gè)頁(yè)面?zhèn)鱽?lái)的vm進(jìn)行l(wèi)ayout層面的綁定了。
雖然這種方式十分簡(jiǎn)單,但不要濫用,它僅僅針對(duì)于兩頁(yè)面是含有共同vm的情況,其他情況我還是推薦通過(guò)回調(diào)、廣播、事件總線等方式做。要記得,vm雖好,但它不是萬(wàn)能的。
請(qǐng)測(cè)試在低內(nèi)存中這種方案會(huì)不會(huì)有bug。
14.如何對(duì)自動(dòng)生成的vm做定制
插件僅僅能生成普通情況下的vm,它不可能知道你具體的邏輯和特殊需求。如果你要由這樣的需求,可以在new出vm的時(shí)候通過(guò)重載set方法來(lái)實(shí)現(xiàn)。
mUserVm = new UserViewModel() {
@Override
public void setName(CharSequence name) {
// 對(duì)于復(fù)雜的ui需求,可以重載對(duì)應(yīng)的set方法,不應(yīng)該重載get方法
super.setName(String.format(name, "kale", "saber"););
}
};
15.view的事件該如何綁定
因?yàn)関m僅僅是view的字段,vm的字段也應(yīng)該和數(shù)據(jù)保持一致的,這時(shí)候你就會(huì)發(fā)現(xiàn)view的事件是不應(yīng)該做vm綁定的,因?yàn)樗皇菙?shù)據(jù)。但,為了節(jié)約findviewById和配置監(jiān)聽(tīng)器的代碼,我提供了一個(gè)event對(duì)象來(lái)做界面的事件綁定。界面中所有view對(duì)象的事件都交給它來(lái)做就行。
layout文件:
<variable
name="event"
type="vm.Event"
/>
//……
<Button
android:id="@+id/change_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{event.onClick}"
/>
java代碼:
mEvent.setOnClick(v -> {
if (v == b.changeBtn) {
mUserVm.setName("Kale");
} else if (v == b.headPicIv) {
Toast.makeText(UserDetailActivity.this, "點(diǎn)擊了頭像", Toast.LENGTH_SHORT).show();
}
});
相比起之前的做法,是必須要findviewbyid找到這個(gè)控件,然后設(shè)置監(jiān)聽(tīng)器,為了一個(gè)頁(yè)面共用listener,減少代碼。就必須寫(xiě)成這樣:
Button btn = (Button)findViewById(R.id.btn);
btn.setOnClickListenter(this);
// ……
void onClick(View v){
//……
}
其實(shí)這樣的問(wèn)題就在于view的設(shè)計(jì)監(jiān)聽(tīng)和實(shí)現(xiàn)是脫離的,你必須要進(jìn)行跳轉(zhuǎn)才能找到監(jiān)聽(tīng)器的實(shí)現(xiàn),沒(méi)有聚合好。
避免NullPointerException
自動(dòng)生成的 data binding 代碼會(huì)自動(dòng)檢查和避免 null pointer exceptions。舉個(gè)例子,在表達(dá)式 @{user.name} 中,如果 user 是 null,user.name 會(huì)賦予默認(rèn)值 null。如果你引用了 user.age,因?yàn)?age 是 int 類型,所以默認(rèn)賦值為 0
參考自:
https://github.com/LyndonChin/MasteringAndroidDataBinding
http://www.itdecent.cn/p/add73330d106
http://www.itdecent.cn/p/e7b6ff1bc360
http://www.itdecent.cn/p/918719151e72
http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html