RxBinding使用和源码解析

作者 | 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(数据)。一行代码实现各种监听绑定,你也可以的。

原文发布于微信公众号 - 刘望舒(liuwangshuAndroid)

原文发表时间:2018-01-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android干货园

Android谈谈封装那些事--BaseActivity和BaseFragment(一)

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/53...

4703
来自专栏Android开发小工

优雅地实现RecyclerView的上拉加载

这篇博客是承接上一篇博客--探索Android架构的DataLayer层(DataManager方式)具体实现,其实是上篇博客的一个使用比较普遍的例子,当然如果...

1024
来自专栏Android知识点总结

1--安卓多媒体之图片综合篇

772
来自专栏何俊林

Android View框架总结(八)ViewGroup事件分发机制

前言:昨天获得了CSDN博客专家殊荣,也意味着,文章可有有更多的人看到,也让我确信:要得到别人的关注,关键是要让自己的产出对别人有用,也可关注我的csdn:ht...

19310
来自专栏Android小菜鸡

ConvenientBanner广播栏的使用

  最近对广播栏进行了一次学习,因为要设计一个较为复杂的滚动广播。复杂在布局上并非单一的图片,而是有一个标题,然后又内容,然后是一个图片列表。   采用的Co...

1582
来自专栏一个会写诗的程序员的博客

第14章 使用Kotlin 进行 Android 开发(2)

我们使用 fastjson 来解析这个数据。在 app 下面的 build.gradle中添加依赖

982
来自专栏Android开发与分享

【Android】数据存储(一) SharedPreferences详解

3867
来自专栏刘望舒

打造一个灵活易用的Banner组件

之前做项目时候出于各种考虑,自己开发了Banner组件FBanner,欢迎大家的Star和PR。github上成熟的轮播图库已经有非常多了,比如banner和A...

1305
来自专栏Android-薛之涛

Android - 懒加载

如果我们的项目中使用了ViewPager+Framgment实现底部Tab可点可滑,那么我们都知道ViewPager有预加载功能,通过viewpager.set...

1912
来自专栏移动开发

Glide 如何实现正确加载图片而没有错位

当我们在常见的列表界面中(如 recycleview 实现的列表),使用上面的代码,在我们快速滑动中,glide 是如何实现正确加载图片,而没有导致图片内容的错...

2763

扫码关注云+社区

领取腾讯云代金券