原文: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,是你的应用更为模块化与响应化。
编程快乐!