部落格地址:http://www.jianshu.com/u/ea71bb3770b4, 歡迎關注
RxJava想必做Android都用過,即使沒用過肯定也聽過。RxBinding這個庫是 JakeWharton的大作,可以響應式的方式來處理UI的響應問題,比如按鈕的點選事件,ListView的點選事件,EditText的文字變化事件等等。今天我們就來看一些RxBinding的使用場景,並且分析下原始碼。 分成下面幾部分內容:
1.表單驗證 2.按鈕點選分發多個事件 3.ListView點選事件 4.原始碼解析
寫了個簡單的Demo,先看下效果:
![example.png](https://i.iter01.com/images/3d6d8c8c9b82886ca01744dbff64ef212fa4748cb77ba87599a897c1a9474e0f.png)
###1.表單驗證 如果按照傳統的方式EditText監聽輸入事件是這樣:
@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();
}
});
複製程式碼
當然可以使用RxJava的操作符做一些其他的變化,比如通過map講文字輸入轉化為字串:
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();
}
});
複製程式碼
有了上面的知識我們來看一下稍微複雜點的例子,表單驗證,輸入正確的名字和密碼才能點選登入按鈕。 先看下錶單的佈局檔案,很簡單就不多說了:
<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" />
複製程式碼
看下驗證用RxBinding的方式是怎麼實現的,看之前先了解一下combineLatest
這個操作符。這個操作符可以結合兩個Observable
的資料來源進行輸出,這個正好我們這裡需要驗證輸入的Name和Password兩個資料來源,驗證通過才讓按鈕可以點選登入。看下RxJava
官方的一個解釋圖:
![CombineLastest.PNG](https://i.iter01.com/images/00e003d3958b145eb4857e9193832f7a1503f04e79a4507bef546ceb92dd50da.png)
這個和
zip
操作符還是有點不一樣,在第一個資料來源沒有傳送資料,會取最近的資料和第二個資料來源進行結合傳送,比如途中的2C
/2D
/3D
等等
言歸正傳,有了上面的儲備,就可以愉快看下錶單驗證的實現了,如果輸入的名字"RxBind",密碼"123",就會在subscribe
中接收到aBoolean
==true,然後我們在使能按鈕,RxView.clicks
這個可以先忽略,我們在第二部分進行詳細說明。
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);
}
複製程式碼
整個驗證過程很是流程,一擼到底絲綢般潤滑。如果用老套路會有巢狀的ifelse,很難看。看下點選效果:
![Login.png](https://i.iter01.com/images/7a3a0a0aac19af22e2ee728a52edfd6aeb6e1730b17f1f4822e8babc49b7e9ff.png)
###2.按鈕點選分發多個事件
老套路的按鈕點選事件想必大家都爛熟於胸了,看下上面RxBinding
按鈕點選是什麼姿勢, 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
簡單啊,還更復雜,你是不是在騙我。。。。
先等等,聽我解釋,如果要實現多個監聽呢?就是點選了一個按鈕在多個地方收到通知,怎麼玩?
這個用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);
}
複製程式碼
這樣點選按鈕後就都能收到通知了:
![Send Event.PNG](https://i.iter01.com/images/812d176db16b9865eac37ceafc793718a99235217b55c088ba10e33249d19f43.png)
INSTANCE
其實是RxBinding
預設傳送的資料,可以忽略。
###3.ListView點選事件
其實有了前面的例子,就基本瞭解了RxBinding
的套路了,使用方式都差不多。這裡寫了個簡單的ListView
,通過RxAdapterView.itemClicks(mListView)
封裝了一個Observable
,就可以在點選的時候進行回撥了。
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();
}
});
}
複製程式碼
空口無憑,看下點選截圖:
![ItemClick.png](https://i.iter01.com/images/e6ec196a2d032f7369aee7868143fa54f02e2b588fd18c47f8f1b6bffdeae7c2.png)
###4.原始碼解析
####4.1表單驗證原始碼分析
RxBinding
的原始碼可不少,但是基本和View是一一對應的,套路基本差不多,我們就拿上面三個例子的原始碼進行分析。
先看下錶單驗證的,主要是下面這句話:
Disposable mEditTextDisposable = RxTextView.textChanges(mEditName).subscribe()
先看下textChanges
, 是個靜態方法,首先是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);
}
}
}
複製程式碼
重點坐下解釋哈,
1.先看下
subscribeListener
這個方法在哪裡呼叫, 在父類InitialValueObservable
中的subscribeActual
方法中呼叫,
@Override protected final void subscribeActual(Observer<? super T> observer) {
subscribeListener(observer);
observer.onNext(getInitialValue());
}
複製程式碼
subscribeActual
這個方法就在Observable
中進行呼叫:
@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的時候進行呼叫的。再看下這個方法裡面做了什麼
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的時候會回撥onDispose()
方法,這裡可以解除監聽;Listener還實現了TextWatcher介面,主要看下這個方法:
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!isDisposed()) {
observer.onNext(s);
}
}
複製程式碼
其實就是對系統介面方法的封裝,在文字傳送變化的時候呼叫observer.onNext(s);
,這個observer
就是我們在Observable.subscribe(observer)
使用的時候傳入的,這樣就保證了接收到文字的資料。
2.第二行程式碼
observer.onSubscribe(listener);
這個其實就是提供一個Disposable
,供解除用,在Listener
中實現了這個方法,在解除監聽的時候呼叫
@Override
protected void onDispose() {
view.removeTextChangedListener(this);
}
複製程式碼
3.第三行程式碼
view.addTextChangedListener(listener);
其中view在我們這個例子中就是EditText,給這個EditText註冊系統的監聽事件,前面已經說了Listener還實現了TextWatcher介面,所以沒毛病吧。
這樣我們表單驗證的原始碼就分析差不多了,其實就是RxTextView
封裝了一個Observable
,這樣就可以使用RxJava
的各種操作符了,然後註冊系統原生的響應事件,在事件發生時通過observer.onNext(s);
傳送資料給observer
,這個observer
就是我們自己實現也是最關心的,回撥的函式。
####4.2按鈕點選原始碼分析 再看下按鈕點選的原始碼:
Observable<Object> observable = RxView.clicks(mBtnEvent)
複製程式碼
這個也是返回一個封裝的Observable
,基本邏輯和上面是差不多的,主要區別的static final class Listener extends MainThreadDisposable implements OnClickListener
,這裡實現的是implements OnClickListener
介面,在onClick
中預設傳送一個資料observer.onNext(Notification.INSTANCE);
按鈕點選傳送的資料沒什麼用。在解除監聽的onDispose
時候設定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);
}
}
}
複製程式碼
相信小夥伴們已經看出來套路了,就是在每個View對應封裝的Observable
中實現不同的Listener
。再看下ListView點選的原始碼。
####4.3ListView點選原始碼分析
直接上原始碼,看出來了吧?static final class Listener extends MainThreadDisposable implements OnItemClickListener
中實現的是OnItemClickListener
,然後在onItemClick
中呼叫回撥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.總結
到這裡就RxBinding
的使用和原始碼分析就結束了,當然這裡只是分析了一些常用的點選場景,並沒有每一個View都分析,這樣也沒什麼必要,通過三個例子我們基本就看到了原始碼的套路,針對每一個View封裝Observable
,然後在內部類Listener
中實現不同的原生系統介面,比如按鈕就實現OnClickListener
, EditText就實現TextWatcher
, ListView就實現OnItemClickListener
,在事件發生時, 呼叫回撥observer.onNext(資料)
。一行程式碼實現各種監聽繫結,你也可以的。
希望對大家有點幫助哈,歡迎關注juexingzhe哈。
謝謝!