RxJava想必做Android都用过,即使没用过肯定也听过。RxBinding这个库是 JakeWharton的大作,可以响应式的方式来处理UI的响应问题,比如按钮的点击事件,ListView的点击事件,EditText的文本变化事件等等。今天我们就来看一些RxBinding的使用场景,并且分析下源码。
分成下面几部分内容:
1.表单验证
2.按钮点击分发多个事件
3.ListView点击事件
4.源码解析
写了个简单的Demo,先看下效果:
主要就是对应的三部分,表单验证,按钮,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
官方的一个解释图:
这个和
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,很难看。看下点击效果:
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);
}
这样点击按钮后就都能收到通知了:
关于上面的
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();
}
});
}
空口无凭,看下点击截图:
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哈。
谢谢!
欢迎关注公众号:JueCode