RxBinding使用和源碼解析

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,先看下效果:


example.png

主要就是對應(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官方的一個解釋圖:

CombineLastest.PNG

這個和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)擊效果:


Login.png

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)擊按鈕后就都能收到通知了:

Send Event.PNG

關(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)擊截圖:


ItemClick.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,355評論 25 708
  • 最近項(xiàng)目里面有用到Rxjava框架,感覺很強(qiáng)大的巨作,所以在網(wǎng)上搜了很多相關(guān)文章,發(fā)現(xiàn)一片文章很不錯,今天把這篇文...
    Scus閱讀 6,994評論 2 50
  • 我從去年開始使用 RxJava ,到現(xiàn)在一年多了。今年加入了 Flipboard 后,看到 Flipboard 的...
    Jason_andy閱讀 5,778評論 7 62
  • 人只要生活在這個世界上,就會有很多煩惱。但是,痛苦和快樂取決于你的內(nèi)心。再重的擔(dān)子,笑著也是挑,哭著也是挑。再不順...
    湉湉_5aee閱讀 579評論 0 1
  • “讀書是會上癮的”你也是這么覺得吧! 從小學(xué)我就不喜歡看書,特別是老師強(qiáng)烈要求的,我都特別排斥,以至于到...
    陌沫邩閱讀 1,337評論 0 3

友情鏈接更多精彩內(nèi)容