RxBinding使用和源码解析

547 阅读12分钟
原文链接: mp.weixin.qq.com

作者 | juexingzhe

地址 | https://www.jianshu.com/u/ea71bb3770b4

声明 | 本文是 juexingzhe 原创,已获授权发布,未经原作者允许请勿转载

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) {             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;        }    }

到这里就明白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.3 ListView点击源码分析

直接上源码,看出来了吧?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(数据)。一行代码实现各种监听绑定,你也可以的。

推荐公众号

「安卓干货铺」专注于Android技术分享,从起步到进阶,坚持原创,认真总结每一个阶段的技术干货;不仅如此,还会分享一些职场经验感悟。每周更新三篇文章。