原文:Reactive Android UI Programming with RxBinding
作者:Donn Felker
翻譯:DreamWinter
關(guān)于軟件有這樣一句古老的名言:
唯一不變的就是變化
這句話對(duì)于Android同樣適用。比如,想想你實(shí)現(xiàn)過多少次UI監(jiān)聽事件,有OnClickListener, TextChangeListener, 以及其它各種各樣的回調(diào)事件,但是非常遺憾的是這些回調(diào)毫無一致性。一段時(shí)間后,你的fragment或者activity中由于各種匿名類而顯得十分混亂。這時(shí),如果你想再為該類中控件/視圖添加由其它視圖觸發(fā)的響應(yīng)事件,那將變得非常復(fù)雜。對(duì)大多數(shù)開發(fā)者來說,用這樣的方式來實(shí)現(xiàn)UI響應(yīng)即費(fèi)時(shí)又易出錯(cuò)。非常幸運(yùn)的是,RxBinding 這個(gè)庫可以幫我們解決前面的問題,而且使用起來非常簡單。
什么是RxBinding?
RxBinding 是一組開源庫,它允許你以RxJava的形式來處理UI事件。讓我們來看一個(gè)小小的例子。這是Android開發(fā)者對(duì)button點(diǎn)擊事件的常規(guī)處理方式:
Button b = (Button)findViewById(R.id.button);
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// do some work here
}
});
使用RxBinding, 你可以以RxJava的形式實(shí)現(xiàn)同樣的功能:
Button b = (Button)findViewById(R.id.button);
Subscription buttonSub =
RxView.clicks(b).subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
// do some work here
}
});
// make sure to unsubscribe the subscription
讓我們來看另一個(gè)例子,這次是為EditText添加文本改變事件:
final EditText name = (EditText) v.findViewById(R.id.name);
name.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// do some work here with the updated text
}
@Override
public void afterTextChanged(Editable s) {
}
});
用RxBinding實(shí)現(xiàn)同樣功能是這樣子的:
final EditText name = (EditText) v.findViewById(R.id.name);
Subscription editTextSub =
RxTextView.textChanges(name)
.subscribe(new Action1<String>() {
@Override
public void call(String value) {
// do some work with the updated text
}
});
// Make sure to unsubscribe the subscription
看起來好像只是把蘋果換成了橘子,但實(shí)際上帶來了非常大的改變:一致性。這僅僅是無數(shù)個(gè)監(jiān)聽事件中的兩個(gè)而已。使用RxBinding時(shí),你對(duì)這些監(jiān)聽事件的可以有一致的實(shí)現(xiàn):RxJava的subscription。只需要對(duì)RxJava稍有了解即可。
更細(xì)微的控制
在前面的例子中,我使用RxTextView.textChanges()方法僅僅對(duì)文本改變作出響應(yīng)。在傳統(tǒng)Android中,我們必須實(shí)現(xiàn)整個(gè)TextWatcher才行,這會(huì)多出許多行沒必要的代碼,因?yàn)槟氵€得實(shí)現(xiàn)beforeTextChanged方法與 afterTextChanged方法。這些無用代碼僅僅是曾加了行數(shù),除此之外毫無益處。使用RxBinding,我可以細(xì)微控制只實(shí)現(xiàn)我需要的功能而無需實(shí)現(xiàn)整個(gè)接口。
必須注意到前面的例子中使用RxBinding只是簡單實(shí)現(xiàn)了TextWatcher的onTextChanged方法。下面我們來看看如何用RxBinding完全實(shí)現(xiàn)TextWatcher。
final class TextViewTextOnSubscribe implements Observable.OnSubscribe<CharSequence> {
final TextView view;
TextViewTextOnSubscribe(TextView view) {
this.view = view;
}
@Override public void call(final Subscriber<? super CharSequence> subscriber) {
checkUiThread();
final TextWatcher watcher = new TextWatcher() {
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(s);
}
}
@Override public void afterTextChanged(Editable s) {
}
};
view.addTextChangedListener(watcher);
subscriber.add(new MainThreadSubscription() {
@Override protected void onUnsubscribe() {
view.removeTextChangedListener(watcher);
}
});
// Emit initial value.
subscriber.onNext(view.getText());
}
}
這語法糖真的很棒,超越了Android現(xiàn)成的API,使你的代碼可讀性更高。不管你observing點(diǎn)擊事件、文本改變事件、甚至Snackbar的觸發(fā),RxBinding都能為事件響應(yīng)提供一致的實(shí)現(xiàn)。
可以實(shí)現(xiàn)類型轉(zhuǎn)換
使用RxBinding之后,你可以使用RxJava operators來對(duì)響應(yīng)的內(nèi)容進(jìn)行實(shí)時(shí)轉(zhuǎn)換。讓我們來看一下這個(gè)例子:
假設(shè)你想察看一個(gè)EditText輸入文字時(shí)文本的變化(查看指定類型的數(shù)據(jù))。EditText的原始文本類型是CharSequence,而你要獲取倒序的String類型的文本,你可以這樣:
final TextView nameLabel = (TextView) findViewById(R.id.name_label);
final EditText name = (EditText) findViewById(R.id.name);
Subscription editTextSub =
RxTextView.textChanges(name)
.map(new Func1<CharSequence, String>() {
@Override
public String call(CharSequence charSequence) {
return new StringBuilder(charSequence).reverse().toString();
}
})
.subscribe(new Action1<String>() {
@Override
public void call(String value) {
nameLabel.setText(value);
}
});
在上面的例子中,每當(dāng)EditText 文本發(fā)生改變,RxTextView.textChanges() 的 observable 被map() operator 轉(zhuǎn)換成了返回值為String 的 observable,然后 subscription 將String類型的值顯示在nameLabel上。你可以想象,通過RxJava的操作方法及自定義的操作方法你可以實(shí)現(xiàn)許多功能。
我想再表揚(yáng)一下這么強(qiáng)大的語法糖,遠(yuǎn)超Android這些視圖/控件API。遵照一致的RxJava Observable 語法規(guī)范,你可以執(zhí)行一系列通常無法做到的連鎖操作。這將為你構(gòu)建一個(gè)響應(yīng)式應(yīng)用帶來極大的幫助。
更多功能
極少數(shù)場合我們需要對(duì)一個(gè)視圖的點(diǎn)擊事件進(jìn)行多次監(jiān)聽(由于各種原因)。你知道Android是不能多次監(jiān)聽同一個(gè)點(diǎn)擊事件的除非你自己寫一堆代碼去手動(dòng)實(shí)現(xiàn)。而RxBinding支持對(duì)點(diǎn)擊事件的多次監(jiān)聽并且實(shí)現(xiàn)起來非常簡單。必須提醒一下,RxBinding本身不能做到,但它與RxJava的操作方法結(jié)合可以做到,例如publish(), share(), replay()。至于用哪個(gè)方法,這取決于你的需求。在下面的這個(gè)例子中,我將使用share()操作方法來實(shí)現(xiàn)對(duì)點(diǎn)擊事件的多次監(jiān)聽:
Button b = (Button) v.findViewById(R.id.do_magic);
Observable<Void> clickObservable = RxView.clicks(b).share();
Subscription buttonSub =
clickObservable.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
// button was clicked.
}
});
compositeSubscription.add(buttonSub);
Subscription loggingSub =
clickObservable.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
// Button was clicked
}
});
compositeSubscription.add(loggingSub);
如果你把上面代碼中的 .share() 移除的話,那只有最后一個(gè)subscription才能被回調(diào)。正如share()操作方法的文檔描述一樣:
返回一個(gè)新的Observable ,該Observable會(huì)廣播給所有之前的。
在 context 中使用 share 允許對(duì)同一個(gè)button點(diǎn)擊事件的多次監(jiān)聽,簡直太強(qiáng)大了。
RxBinding 癖好與安裝
在使用RxBinding時(shí)需要注意一些地方。
首先,不能使用弱關(guān)聯(lián)——如文檔所說:
不可使用弱關(guān)聯(lián)。RxJava的subscription會(huì)做適當(dāng)?shù)睦厥眨蹶P(guān)聯(lián)可能會(huì)被回收掉。
第二,許多Android UI 事件內(nèi)部接口返回多個(gè)參數(shù)。但RxJava observables 只能返回一個(gè)參數(shù)(也不能是…)。因此,你需要把這些參數(shù)封裝為一個(gè)才行。比如, scroll change listener 返回多個(gè)參數(shù):scrollX, scrollY, oldScrollX, oldScrollY。在RxBinding中,這些參數(shù)被封裝成一個(gè)ViewScrollChangeEvent 。當(dāng)RxView.scrollChangeEvents() observable被subscribed時(shí),該ViewScrollChangeEvent將作為onNext方法的參數(shù)。因此,你可以得到ViewScrollChangeEvent中你需要的參數(shù)。
第三,RxBinding庫是根據(jù)其所支持控件在Android平臺(tái)的位置而單獨(dú)分離的。例如,android.widget.* 包內(nèi)的視圖與控件對(duì)應(yīng)的RxBinding在com.jakewharton.rxbinding.widget.*包內(nèi)。
RxBinding對(duì)不同平臺(tái)的類沒有局限。這里的RxBinding庫對(duì)Android支持庫也有效。比如基本的平臺(tái)類的RxBinding庫依賴如下:
compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'
讓我們假設(shè)你使用 design support library ,想要RxBinding表現(xiàn)正常的話,你可以添加該依賴:
compile 'com.jakewharton.rxbinding:rxbinding-design:0.4.0'
此外,如果你使用Kotlin,對(duì)于任何依賴簡單地加上 -kotlin 就OK啦。例如:
compile 'com.jakewharton.rxbinding:rxbinding-kotlin:0.4.0'
擴(kuò)展你的RxJava工具箱
如果你還沒有開始RxJava之旅,RxBinding也許是你開啟旅程的第一站。如果你已經(jīng)在RxJava旅途了,RxBinding將是你強(qiáng)有力的補(bǔ)給。RxBinding簡單易用,提供一致的API,是你的應(yīng)用更為模塊化與響應(yīng)化。
編程快樂!