RxBinding使用和源码解析

RxJava想必做Android都用过,即使没用过肯定也听过。RxBinding这个库是 JakeWharton的大作,可以响应式的方式来处理UI的响应问题,比如按钮的点击事件,ListView的点击事件,EditText的文本变化事件等等。今天我们就来看一些RxBinding的使用场景,并且分析下源码。
分成下面几部分内容:

1.表单验证
2.按钮点击分发多个事件
3.ListView点击事件
4.源码解析

写了个简单的Demo,先看下效果:


example.png

主要就是对应的三部分,表单验证,按钮,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官方的一个解释图:

CombineLastest.PNG

这个和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,很难看。看下点击效果:


Login.png

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

这样点击按钮后就都能收到通知了:

Send Event.PNG

关于上面的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();
            }
        });
    }

空口无凭,看下点击截图:


ItemClick.png

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,361评论 25 707
  • 最近项目里面有用到Rxjava框架,感觉很强大的巨作,所以在网上搜了很多相关文章,发现一片文章很不错,今天把这篇文...
    Scus阅读 6,852评论 2 50
  • 我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard 的...
    Jason_andy阅读 5,451评论 7 62
  • 人只要生活在这个世界上,就会有很多烦恼。但是,痛苦和快乐取决于你的内心。再重的担子,笑着也是挑,哭着也是挑。再不顺...
    湉湉_5aee阅读 442评论 0 1
  • “读书是会上瘾的”你也是这么觉得吧! 从小学我就不喜欢看书,特别是老师强烈要求的,我都特别排斥,以至于到...
    陌沫邩阅读 1,252评论 0 3