RxBinding使用和原始碼解析

juexingzhe發表於2017-12-23

部落格地址:http://www.jianshu.com/u/ea71bb3770b4, 歡迎關注

RxJava想必做Android都用過,即使沒用過肯定也聽過。RxBinding這個庫是 JakeWharton的大作,可以響應式的方式來處理UI的響應問題,比如按鈕的點選事件,ListView的點選事件,EditText的文字變化事件等等。今天我們就來看一些RxBinding的使用場景,並且分析下原始碼。 分成下面幾部分內容:

1.表單驗證 2.按鈕點選分發多個事件 3.ListView點選事件 4.原始碼解析

寫了個簡單的Demo,先看下效果:

example.png
主要就是對應的三部分,表單驗證,按鈕,ListView,下面我們詳細的看下每個部分。

###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

這個和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

###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
關於上面的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

###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哈。

謝謝!

相關文章