RxBinding详解: 规范而强大的安卓UI响应式编程

原文:Reactive Android UI Programming with RxBinding
作者:Donn Felker
翻译:DreamWinter

关于软件有这样一句古老的名言:

唯一不变的就是变化

这句话对于Android同样适用。比如,想想你实现过多少次UI监听事件,有OnClickListener, TextChangeListener, 以及其它各种各样的回调事件,但是非常遗憾的是这些回调毫无一致性。一段时间后,你的fragment或者activity中由于各种匿名类而显得十分混乱。这时,如果你想再为该类中控件/视图添加由其它视图触发的响应事件,那将变得非常复杂。对大多数开发者来说,用这样的方式来实现UI响应即费时又易出错。非常幸运的是,RxBinding 这个库可以帮我们解决前面的问题,而且使用起来非常简单。

什么是RxBinding?

RxBinding 是一组开源库,它允许你以RxJava的形式来处理UI事件。让我们来看一个小小的例子。这是Android开发者对button点击事件的常规处理方式:

Button b = (Button)findViewById(R.id.button);
b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
              // do some work here     
            }
        });

使用RxBinding, 你可以以RxJava的形式实现同样的功能:

Button b = (Button)findViewById(R.id.button);
Subscription buttonSub =
                RxView.clicks(b).subscribe(new Action1<Void>() {
                    @Override
                    public void call(Void aVoid) {
                        // do some work here
                    }
                });
// make sure to unsubscribe the subscription

让我们来看另一个例子,这次是为EditText添加文本改变事件:

final EditText name = (EditText) v.findViewById(R.id.name);
name.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        // do some work here with the updated text
    }

    @Override
    public void afterTextChanged(Editable s) {

    }
});

用RxBinding实现同样功能是这样子的:

final EditText name = (EditText) v.findViewById(R.id.name);
Subscription editTextSub =
    RxTextView.textChanges(name)
            .subscribe(new Action1<String>() {
                @Override
                public void call(String value) {
                    // do some work with the updated text
                }
            });
// Make sure to unsubscribe the subscription

看起来好像只是把苹果换成了橘子,但实际上带来了非常大的改变:一致性。这仅仅是无数个监听事件中的两个而已。使用RxBinding时,你对这些监听事件的可以有一致的实现:RxJava的subscription。只需要对RxJava稍有了解即可。

更细微的控制

在前面的例子中,我使用RxTextView.textChanges()方法仅仅对文本改变作出响应。在传统Android中,我们必须实现整个TextWatcher才行,这会多出许多行没必要的代码,因为你还得实现beforeTextChanged方法与 afterTextChanged方法。这些无用代码仅仅是曾加了行数,除此之外毫无益处。使用RxBinding,我可以细微控制只实现我需要的功能而无需实现整个接口。

必须注意到前面的例子中使用RxBinding只是简单实现了TextWatcher的onTextChanged方法。下面我们来看看如何用RxBinding完全实现TextWatcher。

final class TextViewTextOnSubscribe implements Observable.OnSubscribe<CharSequence> {
  final TextView view;

  TextViewTextOnSubscribe(TextView view) {
    this.view = view;
  }

  @Override public void call(final Subscriber<? super CharSequence> subscriber) {
    checkUiThread();

    final TextWatcher watcher = new TextWatcher() {
      @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 (!subscriber.isUnsubscribed()) {
          subscriber.onNext(s);
        }
      }

      @Override public void afterTextChanged(Editable s) {
      }
    };
    view.addTextChangedListener(watcher);

    subscriber.add(new MainThreadSubscription() {
      @Override protected void onUnsubscribe() {
        view.removeTextChangedListener(watcher);
      }
    });

    // Emit initial value.
    subscriber.onNext(view.getText());
  }
}

这语法糖真的很棒,超越了Android现成的API,使你的代码可读性更高。不管你observing点击事件、文本改变事件、甚至Snackbar的触发,RxBinding都能为事件响应提供一致的实现。

可以实现类型转换

使用RxBinding之后,你可以使用RxJava operators来对响应的内容进行实时转换。让我们来看一下这个例子:

假设你想察看一个EditText输入文字时文本的变化(查看指定类型的数据)。EditText的原始文本类型是CharSequence,而你要获取倒序的String类型的文本,你可以这样:

final TextView nameLabel = (TextView) findViewById(R.id.name_label);

final EditText name = (EditText) findViewById(R.id.name);
Subscription editTextSub =
    RxTextView.textChanges(name)
            .map(new Func1<CharSequence, String>() {
                @Override
                public String call(CharSequence charSequence) {
                    return new StringBuilder(charSequence).reverse().toString();
                }
            })
            .subscribe(new Action1<String>() {
                @Override
                public void call(String value) {
                    nameLabel.setText(value);
                }
            });

在上面的例子中,每当EditText 文本发生改变,RxTextView.textChanges() 的 observable 被map() operator 转换成了返回值为String 的 observable,然后 subscription 将String类型的值显示在nameLabel上。你可以想象,通过RxJava的操作方法及自定义的操作方法你可以实现许多功能。

我想再表扬一下这么强大的语法糖,远超Android这些视图/控件API。遵照一致的RxJava Observable 语法规范,你可以执行一系列通常无法做到的连锁操作。这将为你构建一个响应式应用带来极大的帮助。

更多功能

极少数场合我们需要对一个视图的点击事件进行多次监听(由于各种原因)。你知道Android是不能多次监听同一个点击事件的除非你自己写一堆代码去手动实现。而RxBinding支持对点击事件的多次监听并且实现起来非常简单。必须提醒一下,RxBinding本身不能做到,但它与RxJava的操作方法结合可以做到,例如publish(), share(), replay()。至于用哪个方法,这取决于你的需求。在下面的这个例子中,我将使用share()操作方法来实现对点击事件的多次监听:

Button b = (Button) v.findViewById(R.id.do_magic);
Observable<Void> clickObservable = RxView.clicks(b).share();

Subscription buttonSub =
        clickObservable.subscribe(new Action1<Void>() {
            @Override
            public void call(Void aVoid) {
                // button was clicked. 
            }
        });
compositeSubscription.add(buttonSub);

Subscription loggingSub =
        clickObservable.subscribe(new Action1<Void>() {
            @Override
            public void call(Void aVoid) {
                // Button was clicked
            }
        });
compositeSubscription.add(loggingSub);

如果你把上面代码中的 .share() 移除的话,那只有最后一个subscription才能被回调。正如share()操作方法的文档描述一样:

返回一个新的Observable ,该Observable会广播给所有之前的。

在 context 中使用 share 允许对同一个button点击事件的多次监听,简直太强大了。

RxBinding 癖好与安装

在使用RxBinding时需要注意一些地方。

首先,不能使用弱关联——如文档所说:

不可使用弱关联。RxJava的subscription会做适当的拉近回收,弱关联可能会被回收掉。

第二,许多Android UI 事件内部接口返回多个参数。但RxJava observables 只能返回一个参数(也不能是…)。因此,你需要把这些参数封装为一个才行。比如, scroll change listener 返回多个参数:scrollX, scrollY, oldScrollX, oldScrollY。在RxBinding中,这些参数被封装成一个ViewScrollChangeEvent 。当RxView.scrollChangeEvents() observable被subscribed时,该ViewScrollChangeEvent将作为onNext方法的参数。因此,你可以得到ViewScrollChangeEvent中你需要的参数。

第三,RxBinding库是根据其所支持控件在Android平台的位置而单独分离的。例如,android.widget.* 包内的视图与控件对应的RxBinding在com.jakewharton.rxbinding.widget.*包内。
RxBinding对不同平台的类没有局限。这里的RxBinding库对Android支持库也有效。比如基本的平台类的RxBinding库依赖如下:

compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'

让我们假设你使用 design support library ,想要RxBinding表现正常的话,你可以添加该依赖:

compile 'com.jakewharton.rxbinding:rxbinding-design:0.4.0'

此外,如果你使用Kotlin,对于任何依赖简单地加上 -kotlin 就OK啦。例如:

compile 'com.jakewharton.rxbinding:rxbinding-kotlin:0.4.0'

扩展你的RxJava工具箱

如果你还没有开始RxJava之旅,RxBinding也许是你开启旅程的第一站。如果你已经在RxJava旅途了,RxBinding将是你强有力的补给。RxBinding简单易用,提供一致的API,是你的应用更为模块化与响应化。

编程快乐!

查看Demo

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

推荐阅读更多精彩内容