The author | juexingzhe

Address | https://www.jianshu.com/u/ea71bb3770b4

Statement | this is the original juexingzhe, release has been authorized, without author permission please do not reprint

RxJava must do Android have used, even if not certainly heard of. The RxBinding library, a masterpiece of JakeWharton, handles UI responses in a responsive manner, such as button clicks, ListView clicks, EditText changes, and so on. Today we will look at some usage scenarios of RxBinding and analyze the source code.

It is divided into the following parts:

1. Form validation 2. Button click distribute multiple events 3.ListView click event 4. The source code parsing

Write a simple Demo, first look at the effect:

There are three main parts, form validation, buttons, and ListView. Let’s take a look at each part in detail.

1. Form verification

If EditText listens for input events in the traditional way, it looks like this:

   @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) {    }Copy the code

Let’s see what RxBinding is, mEditName is EditText, one line of code.

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(); }});Copy the code

Of course you can use the RxJava operators to make some other changes, such as converting text input to strings via 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(); }});Copy the code

With that in mind let’s take a look at a slightly more complicated example, form validation, where you enter the correct name and password to hit the login button. Let’s take a look at the layout file of the form. It’s very simple and I won’t go into details:

<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" />Copy the code

To see how validation is implemented using RxBinding, take a look at the combineLatest operator. This operator can be output in combination with two Observable data sources. We need to verify the input Name and Password data sources, so that the button can click to log in. Take a look at an illustration from RxJava:

This is a bit different from the zip operator. Instead of sending data from the first data source, it takes the latest data and sends it with the second data source, such as 2C/2D/3D on the way

If we enter the name “RxBind” and password “123”, we subscribe to aBoolean==true, and then click on the enable button, RxView. We can skip this. We elaborate in part 2.

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); }Copy the code

The whole verification process is very smooth, smooth as silk. If you do it the old-fashioned way you have nested ifelse, which is ugly. Take a look at the click effect:

2. Button click dispatches multiple events

The mBtnLogin button is the RxBinding button.

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();            }        });Copy the code

A setOnClickListener is more complicated than a setOnClickListener. Are you kidding me?

Wait, let me explain. What if you want to implement multiple listeners? Click a button and receive notifications in multiple places.

This is easy with RxBinding. Look at the Code:

1. Rxview.clicks (mBtnEvent).share() Subscribe to multiple Disposable with CompositeDisposable

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); }Copy the code

Click the button and you’ll be notified:

About the above

INSTANCE

Is actually

RxBinding

The data sent by default can be ignored.

3.ListView Click events

In fact, with the previous example, you have a basic understanding of the RxBinding approach, and the way to use it is similar. Here’s a simple ListView that wraps an Observable with rxadapterView.ItemClicks (mListView) to call back when clicked.

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(); }}); }Copy the code

Empty words without proof, see click screenshot:

4. Source code analysis

4.1 Source code analysis of form verification

There are many source codes for RxBinding, but they basically correspond to View one by one, and the routines are basically similar. Let’s take the above three examples and analyze them. Let’s take a look at form validation, mainly the following sentence:

Disposable mEditTextDisposable = RxTextView.textChanges(mEditName).subscribe()Copy the code

So textChanges, it’s a static method, it nulls first, there’s nothing to explain, and it returns TextViewTextObservable, an Observable.

@CheckResult @NonNull  public static InitialValueObservable<CharSequence> textChanges(@NonNull TextView view) {    checkNotNull(view, "view == null");    return new TextViewTextObservable(view);  }Copy the code

Then follow 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);    }  }}Copy the code

Sit down and explain.

1. See where the subscribeListener method is called. It is called from the subscribeActual method in the parent InitialValueObservable class.

@Override protected final void subscribeActual(Observer<? super T> observer) {    subscribeListener(observer);    observer.onNext(getInitialValue());  }Copy the code

The subscribeActual method is called in 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) {             throw e;        } catch (Throwable e) {            Exceptions.throwIfFatal(e);                 RxJavaPlugins.onError(e);            NullPointerException npe = new NullPointerException("Actually not, but can't throw other exceptions due to RS");            npe.initCause(e);            throw npe;        }    }Copy the code

You can see that the subscribeListener method is called when an Observable is subscribed. Now what’s going on in this method

Listener listener = new Listener(view, observer);    observer.onSubscribe(listener);    view.addTextChangedListener(listener);Copy the code

1. The first line of code creates a Listener, Final Static class Listener extends MainThreadDisposable implements TextWatcher The onDispose() method is called when dispose is done, and the listener can be removed. The Listener also implements the TextWatcher interface.

   @Override    public void onTextChanged(CharSequence s, int start, int before, int count) {      if (!isDisposed()) {        observer.onNext(s);      }    }Copy the code

Call observer.onNext(s) when text is sent to change; The observer is passed in when we use Observable.subscribe(Observer) to ensure that we receive text data.

2. Observer. onSubscribe(listener); So this is actually providing a Disposable that we can unload, and we implement this method in the Listener, and we call it when we unload

   @Override    protected void onDispose() {      view.removeTextChangedListener(this);    }Copy the code

3. The third line of code that view. AddTextChangedListener (the listener); The view in our example is the EditText, and the EditText registers the system’s Listener events. The Listener also implements the TextWatcher interface.

RxTextView encapsulates an Observable that uses RxJava operators, registers the system’s native response event, and passes observer.onNext(s) when the event occurs. Send data to the observer, which is the callback function that we implement and are most concerned about.

4.2 Button click source code analysis

Look again at the source of the button click:

Observable<Object> observable = RxView.clicks(mBtnEvent)Copy the code

This returns a encapsulated Observable, too, with the same basic logic as above. Static Final Class Listener extends MainThreadDisposable implements OnClickListener. Static final Class Listener extends MainThreadDisposable implements OnClickListener The OnClickListener interface sends a data observer.onnext (notification.instance) by default in onClick; The data sent by button clicks is useless. Set view.setonClickListener (NULL) when dispose of onDispose

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); }}}Copy the code

The routine is to implement a different Listener in each Observable that a View encapsulates. Take a look at the ListView click source.

4.3 ListView Click source code analysis

Go directly to the source code, see it? Static Final Class Listener extends MainThreadDisposable implements OnItemClickListener Then call the callback observer.onNext(position) in onItemClick;

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); }}}Copy the code

5. To summarize

This is the end of RxBinding usage and source code analysis. Of course, it only analyzes some common click scenarios, not every View, which is not necessary. Through three examples, we can basically see the source code routines. Then implement the various native system interfaces in the Listener class, such as OnClickListener for buttons, TextWatcher for EditText, and OnItemClickListener for ListView. When an event occurs, Call the callback observer.onNext(data). One line of code to implement various listener bindings, you can too.

Recommendation Public Account

“Android dry goods shop” focus on Android technology sharing, from the beginning to the advanced, adhere to the original, carefully summarize each stage of the technical dry goods; Not only that, but also share some workplace experience. Three articles are updated each week.