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.