RxJava想必做Android都用過,即使沒用過肯定也聽過。RxBinding這個庫是 JakeWharton的大作,可以響應(yīng)式的方式來處理UI的響應(yīng)問題,比如按鈕的點(diǎn)擊事件,ListView的點(diǎn)擊事件,EditText的文本變化事件等等。今天我們就來看一些RxBinding的使用場景,并且分析下源碼。
分成下面幾部分內(nèi)容:
1.表單驗(yàn)證
2.按鈕點(diǎn)擊分發(fā)多個事件
3.ListView點(diǎn)擊事件
4.源碼解析
寫了個簡單的Demo,先看下效果:

主要就是對應(yīng)的三部分,表單驗(yàn)證,按鈕,ListView,下面我們詳細(xì)的看下每個部分。
1.表單驗(yàn)證
如果按照傳統(tǒng)的方式EditText監(jiān)聽輸入事件是這樣:
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
}
看下RxBinding是什么姿勢, mEditName就是EditText,一行代碼搞定。
RxTextView.textChanges(mEditName).subscribe(new Consumer<CharSequence>() {
@Override
public void accept(CharSequence s) throws Exception {
Toast.makeText(MainActivity.this, String.valueOf(s), Toast.LENGTH_SHORT).show();
}
});
當(dāng)然可以使用RxJava的操作符做一些其他的變化,比如通過map講文本輸入轉(zhuǎn)化為字符串:
RxTextView.textChanges(mEditName)
.map(new Function<CharSequence, String>() {
@Override
public String apply(CharSequence charSequence) throws Exception {
return String.valueOf(charSequence);
}
}).subscribe(new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
}
});
有了上面的知識我們來看一下稍微復(fù)雜點(diǎn)的例子,表單驗(yàn)證,輸入正確的名字和密碼才能點(diǎn)擊登錄按鈕。
先看下表單的布局文件,很簡單就不多說了:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="2"
android:text="@string/name" />
<EditText
android:id="@+id/edit_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="8" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/pwd"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="2"
android:text="@string/password" />
<EditText
android:id="@+id/edit_pwd"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="8" />
</LinearLayout>
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:enabled="false"
android:text="@string/click1" />
看下驗(yàn)證用RxBinding的方式是怎么實(shí)現(xiàn)的,看之前先了解一下combineLatest這個操作符。這個操作符可以結(jié)合兩個Observable的數(shù)據(jù)源進(jìn)行輸出,這個正好我們這里需要驗(yàn)證輸入的Name和Password兩個數(shù)據(jù)源,驗(yàn)證通過才讓按鈕可以點(diǎn)擊登錄??聪?code>RxJava官方的一個解釋圖:
這個和
zip操作符還是有點(diǎn)不一樣,在第一個數(shù)據(jù)源沒有發(fā)送數(shù)據(jù),會取最近的數(shù)據(jù)和第二個數(shù)據(jù)源進(jìn)行結(jié)合發(fā)送,比如途中的2C/2D/3D等等
言歸正傳,有了上面的儲備,就可以愉快看下表單驗(yàn)證的實(shí)現(xiàn)了,如果輸入的名字"RxBind",密碼"123",就會在subscribe中接收到aBoolean==true,然后我們在使能按鈕,RxView.clicks這個可以先忽略,我們在第二部分進(jìn)行詳細(xì)說明。
private void rxEditText() {
Observable.combineLatest(RxTextView.textChanges(mEditName).map(new Function<CharSequence, String>() {
@Override
public String apply(CharSequence charSequence) throws Exception {
return String.valueOf(charSequence);
}
}), RxTextView.textChanges(mEditPwd).map(new Function<CharSequence, String>() {
@Override
public String apply(CharSequence charSequence) throws Exception {
return String.valueOf(charSequence);
}
}), new BiFunction<String, String, Boolean>() {
@Override
public Boolean apply(String name, String password) throws Exception {
return isNameValid(name) && isPwdValid(password);
}
}).subscribe(new Consumer<Boolean>() {
@Override
public void accept(Boolean aBoolean) throws Exception {
if (aBoolean) {
mBtnLogin.setEnabled(true);
RxView.clicks(mBtnLogin).subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
Toast.makeText(MainActivity.this, "Login Success!", Toast.LENGTH_SHORT).show();
}
});
}
}
});
}
private boolean isNameValid(String name) {
return "RxBind".equals(name);
}
private boolean isPwdValid(String pwd) {
return "123".equals(pwd);
}
整個驗(yàn)證過程很是流程,一擼到底絲綢般潤滑。如果用老套路會有嵌套的ifelse,很難看。看下點(diǎn)擊效果:

2.按鈕點(diǎn)擊分發(fā)多個事件
老套路的按鈕點(diǎn)擊事件想必大家都爛熟于胸了,看下上面RxBinding按鈕點(diǎn)擊是什么姿勢, mBtnLogin就是按鈕。
RxView.clicks(mBtnLogin).subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
Toast.makeText(MainActivity.this, "Login Success!", Toast.LENGTH_SHORT).show();
}
});
有小伙伴就要摔桌子了,這沒比setOnClickListener簡單啊,還更復(fù)雜,你是不是在騙我。。。。
先等等,聽我解釋,如果要實(shí)現(xiàn)多個監(jiān)聽呢?就是點(diǎn)擊了一個按鈕在多個地方收到通知,怎么玩?
這個用RxBinding就很簡單了,看下Code:
1.
RxView.clicks(mBtnEvent).share()首先需要使用share這個操作符
2.通過CompositeDisposable訂閱多個Disposable
private void rxButton() {
Observable<Object> observable = RxView.clicks(mBtnEvent).share();
CompositeDisposable compositeDisposable = new CompositeDisposable();
Disposable disposable1 = observable.subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
Log.d(TAG, "disposable1, receive: " + o.toString());
}
});
Disposable disposable2 = observable.subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
Log.d(TAG, "disposable2, receive: " + o.toString());
}
});
compositeDisposable.add(disposable1);
compositeDisposable.add(disposable2);
}
這樣點(diǎn)擊按鈕后就都能收到通知了:
關(guān)于上面的
INSTANCE其實(shí)是RxBinding默認(rèn)發(fā)送的數(shù)據(jù),可以忽略。
3.ListView點(diǎn)擊事件
其實(shí)有了前面的例子,就基本了解了RxBinding的套路了,使用方式都差不多。這里寫了個簡單的ListView,通過RxAdapterView.itemClicks(mListView)封裝了一個Observable,就可以在點(diǎn)擊的時候進(jìn)行回調(diào)了。
private void rxList() {
ArrayList<String> datas = new ArrayList<>();
for (int i = 0; i < 10; i++) {
datas.add("rxList " + i);
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_expandable_list_item_1, datas);
mListView.setAdapter(adapter);
RxAdapterView.itemClicks(mListView).subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
Toast.makeText(MainActivity.this, "List Item Clicked, Position = " + integer, Toast.LENGTH_LONG).show();
}
});
}
空口無憑,看下點(diǎn)擊截圖:

4.源碼解析
4.1表單驗(yàn)證源碼分析
RxBinding的源碼可不少,但是基本和View是一一對應(yīng)的,套路基本差不多,我們就拿上面三個例子的源碼進(jìn)行分析。
先看下表單驗(yàn)證的,主要是下面這句話:
Disposable mEditTextDisposable = RxTextView.textChanges(mEditName).subscribe()
先看下textChanges, 是個靜態(tài)方法,首先是checkNotNull判空,這個沒什么好解釋的,然后會返回TextViewTextObservable這個Observable對象。
@CheckResult @NonNull
public static InitialValueObservable<CharSequence> textChanges(@NonNull TextView view) {
checkNotNull(view, "view == null");
return new TextViewTextObservable(view);
}
接著跟到TextViewTextObservable里面看看,
final class TextViewTextObservable extends InitialValueObservable<CharSequence> {
private final TextView view;
TextViewTextObservable(TextView view) {
this.view = view;
}
@Override
protected void subscribeListener(Observer<? super CharSequence> observer) {
Listener listener = new Listener(view, observer);
observer.onSubscribe(listener);
view.addTextChangedListener(listener);
}
@Override protected CharSequence getInitialValue() {
return view.getText();
}
final static class Listener extends MainThreadDisposable implements TextWatcher {
private final TextView view;
private final Observer<? super CharSequence> observer;
Listener(TextView view, Observer<? super CharSequence> observer) {
this.view = view;
this.observer = observer;
}
@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 (!isDisposed()) {
observer.onNext(s);
}
}
@Override
public void afterTextChanged(Editable s) {
}
@Override
protected void onDispose() {
view.removeTextChangedListener(this);
}
}
}
重點(diǎn)坐下解釋哈,
1.先看下
subscribeListener這個方法在哪里調(diào)用, 在父類InitialValueObservable中的subscribeActual方法中調(diào)用,
@Override protected final void subscribeActual(Observer<? super T> observer) {
subscribeListener(observer);
observer.onNext(getInitialValue());
}
subscribeActual這個方法就在Observable中進(jìn)行調(diào)用:
@SchedulerSupport(SchedulerSupport.NONE)
@Override
public final void subscribe(Observer<? super T> observer) {
ObjectHelper.requireNonNull(observer, "observer is null");
try {
observer = RxJavaPlugins.onSubscribe(this, observer);
ObjectHelper.requireNonNull(observer, "Plugin returned null Observer");
**subscribeActual(observer);**
} catch (NullPointerException e) { // NOPMD
throw e;
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
// can't call onError because no way to know if a Disposable has been set or not
// can't call onSubscribe because the call might have set a Subscription already
RxJavaPlugins.onError(e);
NullPointerException npe = new NullPointerException("Actually not, but can't throw other exceptions due to RS");
npe.initCause(e);
throw npe;
}
}
到這里就明白subscribeListener這個方法是在Observable被Subscribe的時候進(jìn)行調(diào)用的。再看下這個方法里面做了什么
Listener listener = new Listener(view, observer);
observer.onSubscribe(listener);
view.addTextChangedListener(listener);
1.第一行代碼new一個Listener,
final static class Listener extends MainThreadDisposable implements TextWatcher
繼承MainThreadDisposable ,這個是在dispose的時候會回調(diào)onDispose()方法,這里可以解除監(jiān)聽;Listener還實(shí)現(xiàn)了TextWatcher接口,主要看下這個方法:
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!isDisposed()) {
observer.onNext(s);
}
}
其實(shí)就是對系統(tǒng)接口方法的封裝,在文本發(fā)送變化的時候調(diào)用observer.onNext(s);,這個observer就是我們在Observable.subscribe(observer)使用的時候傳入的,這樣就保證了接收到文本的數(shù)據(jù)。
2.第二行代碼
observer.onSubscribe(listener);這個其實(shí)就是提供一個Disposable,供解除用,在Listener中實(shí)現(xiàn)了這個方法,在解除監(jiān)聽的時候調(diào)用
@Override
protected void onDispose() {
view.removeTextChangedListener(this);
}
3.第三行代碼
view.addTextChangedListener(listener);其中view在我們這個例子中就是EditText,給這個EditText注冊系統(tǒng)的監(jiān)聽事件,前面已經(jīng)說了Listener還實(shí)現(xiàn)了TextWatcher接口,所以沒毛病吧。
這樣我們表單驗(yàn)證的源碼就分析差不多了,其實(shí)就是RxTextView封裝了一個Observable,這樣就可以使用RxJava的各種操作符了,然后注冊系統(tǒng)原生的響應(yīng)事件,在事件發(fā)生時通過observer.onNext(s);發(fā)送數(shù)據(jù)給observer,這個observer就是我們自己實(shí)現(xiàn)也是最關(guān)心的,回調(diào)的函數(shù)。
4.2按鈕點(diǎn)擊源碼分析
再看下按鈕點(diǎn)擊的源碼:
Observable<Object> observable = RxView.clicks(mBtnEvent)
這個也是返回一個封裝的Observable,基本邏輯和上面是差不多的,主要區(qū)別的static final class Listener extends MainThreadDisposable implements OnClickListener,這里實(shí)現(xiàn)的是implements OnClickListener接口,在onClick中默認(rèn)發(fā)送一個數(shù)據(jù)observer.onNext(Notification.INSTANCE);按鈕點(diǎn)擊發(fā)送的數(shù)據(jù)沒什么用。在解除監(jiān)聽的onDispose時候設(shè)置view.setOnClickListener(null);
final class ViewClickObservable extends Observable<Object> {
private final View view;
ViewClickObservable(View view) {
this.view = view;
}
@Override protected void subscribeActual(Observer<? super Object> observer) {
if (!checkMainThread(observer)) {
return;
}
Listener listener = new Listener(view, observer);
observer.onSubscribe(listener);
view.setOnClickListener(listener);
}
static final class Listener extends MainThreadDisposable implements OnClickListener {
private final View view;
private final Observer<? super Object> observer;
Listener(View view, Observer<? super Object> observer) {
this.view = view;
this.observer = observer;
}
@Override public void onClick(View v) {
if (!isDisposed()) {
observer.onNext(Notification.INSTANCE);
}
}
@Override protected void onDispose() {
view.setOnClickListener(null);
}
}
}
相信小伙伴們已經(jīng)看出來套路了,就是在每個View對應(yīng)封裝的Observable中實(shí)現(xiàn)不同的Listener。再看下ListView點(diǎn)擊的源碼。
4.3ListView點(diǎn)擊源碼分析
直接上源碼,看出來了吧?static final class Listener extends MainThreadDisposable implements OnItemClickListener中實(shí)現(xiàn)的是OnItemClickListener,然后在onItemClick中調(diào)用回調(diào)observer.onNext(position);
final class AdapterViewItemClickObservable extends Observable<Integer> {
private final AdapterView<?> view;
AdapterViewItemClickObservable(AdapterView<?> view) {
this.view = view;
}
@Override protected void subscribeActual(Observer<? super Integer> observer) {
if (!checkMainThread(observer)) {
return;
}
Listener listener = new Listener(view, observer);
observer.onSubscribe(listener);
view.setOnItemClickListener(listener);
}
static final class Listener extends MainThreadDisposable implements OnItemClickListener {
private final AdapterView<?> view;
private final Observer<? super Integer> observer;
Listener(AdapterView<?> view, Observer<? super Integer> observer) {
this.view = view;
this.observer = observer;
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
if (!isDisposed()) {
observer.onNext(position);
}
}
@Override protected void onDispose() {
view.setOnItemClickListener(null);
}
}
}
5.總結(jié)
到這里就RxBinding的使用和源碼分析就結(jié)束了,當(dāng)然這里只是分析了一些常用的點(diǎn)擊場景,并沒有每一個View都分析,這樣也沒什么必要,通過三個例子我們基本就看到了源碼的套路,針對每一個View封裝Observable,然后在內(nèi)部類Listener中實(shí)現(xiàn)不同的原生系統(tǒng)接口,比如按鈕就實(shí)現(xiàn)OnClickListener, EditText就實(shí)現(xiàn)TextWatcher, ListView就實(shí)現(xiàn)OnItemClickListener,在事件發(fā)生時, 調(diào)用回調(diào)observer.onNext(數(shù)據(jù))。一行代碼實(shí)現(xiàn)各種監(jiān)聽綁定,你也可以的。
希望對大家有點(diǎn)幫助哈,歡迎關(guān)注juexingzhe哈。
謝謝!
歡迎關(guān)注公眾號:JueCode