Android RxBinding
在先前的教程中,我们讨论了RxJava及其一些运算符。
今天,我们将在Android应用程序中讨论RxBinding库。
Rx绑定
我们知道RxJava是一种基于事件的反应式编程范例。
RxBinding是一组支持库,可简化与Android中的UI元素的用户交互的实现。
要在您的应用程序中使用RxBinding,您必须包括:
implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
与RxJava依赖项一起使用:
implementation 'io.reactivex.rxjava2:rxjava:2.1.9' implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
要将RxBinding与Appcompat和其他子模块一起使用,我们只需要导入它们各自的rxbinding库:
implementation com.jakewharton.rxbinding2:rxbinding-recyclerview-v7:2.0.0'
implementation 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0'
implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
implementation 'com.jakewharton.rxbinding2:rxbinding-recyclerview-v7:2.0.0'
compile 'com.jakewharton.rxbinding2:rxbinding-leanback-v17:2.0.0'
我们可以在应用程序中使用RxBinding功能。
RxView.click()
通常,要在Button上设置点击侦听器事件,以下是我们编写的代码段:
Button b = (Button)findViewById(R.id.button); b.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getApplicationContext(),"Button clicked",Toast.LENGTH_SHORT).show(); } });
现在,使用RxBinding,我们可以执行以下操作:
Disposable d = RxView.clicks(button). subscribe(new Consumer<Object>() { @Override public void accept(Object o) { Toast.makeText(getApplicationContext(),"Button clicked",Toast.LENGTH_SHORT).show(); } });
在" RxView.clicks"内部,我们传递了要单击的View实例。
如果您不熟悉RxJava2,则Disposable等同于Subscription。
有关RxJava2中更改的更多信息,请参见此处。
在Disposable实例上,我们可以通过调用d.dispose();取消订阅该事件。
Disposable实例包含对该视图的引用。
因此,如果此代码是在Activity的上下文之外定义的,则必须取消订阅该事件,以防止内存泄漏。
RxView.click()返回一个Observable。
因此,我们可以添加RxJava运算符以在其上执行转换和链式实现。
EditText TextChanges
通常,要在EditText上添加文本更改侦听器,我们需要实现TextWatcher方法:
EditText editText = findViewById(R.id.editText); editText.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) { } @Override public void afterTextChanged(Editable s) { } });
使用RxBinding,我们可以执行以下操作:
Disposable d2 = RxTextView.textChanges(editText) .subscribe(new Consumer<CharSequence>() { @Override public void accept(CharSequence charSequence) throws Exception { //Add your logic to work on the Charsequence } });
我们在RxTextView.textChanges内部传递EditText。
现在,让我们看看RxJava运算符和转换如何给我们带来影响。
使用地图运算符
使用地图运算符,我们可以更改正在发送的数据。
例如,在EditText中,我们可以将CharSequence更改为String
Disposable d2 = RxTextView.textChanges(editText) .map(charSequence -> charSequence.toString()) .subscribe(new Consumer<CharSequence>() { @Override public void accept(String string) throws Exception { //Add your logic to work on the Charsequence } });
使用反跳运算符
使用反跳运算符,我们可以延迟事件动作。
例如,在"按钮"单击上,我们可以设置2秒的去抖动。
2秒后将运行该操作:
RxView.clicks(button).debounce(2, TimeUnit.SECONDS). observeOn(AndroidSchedulers.mainThread()). map(o -> button.getText().toString()). subscribe(new Consumer<String>() { @Override public void accept(String o) throws Exception { Toast.makeText(getApplicationContext(),o + "was clicked",Toast.LENGTH_SHORT).show(); } });
反跳操作不在UI线程上运行。
它在计算线程上运行。
因此,如上所述,您必须在observeOn
方法中调用主线程。
反跳运算符通常在EditText中使用,尤其是在SearchView中,以使用户在运行操作/请求之前停止键入几秒钟。
使用节流阀
与防抖会延迟动作不同,throttleFirst
运算符用于防止在特定时间间隔内重复动作。
ThrottleFirst对于防止重复单击按钮时的双重操作很有用。
RxView.clicks(button).throttleFirst(2, TimeUnit.SECONDS) .observeOn(AndroidSchedulers.mainThread()). subscribe(new Consumer<Object>() { @Override public void accept(Object o) { Toast.makeText(getApplicationContext(), "Avoid multiple clicks using throttleFirst", Toast.LENGTH_SHORT).show(); } });
在上面的代码中,Button直到2秒后才会再次显示Toast。
合并多个按钮单击动作
我们可以通过以下方式合并RxView.click()
Observables:
Button button1 = findViewById(R.id.button1); Button button2 = findViewById(R.id.button2); Observable<Object> observable1 = RxView.clicks(button1); Observable<Object> observable1 = RxView.clicks(button2); Observable.merge(observable1, observable2) .subscribe(new Consumer<Object>() { @Override public void accept(Object o) { //This common logic would be triggered when either of them are clicked } });
一个按钮上的多个单击侦听器
CompositeDisposable compositeDisposable = new CompositeDisposable(); Observable<Button> clickObservable = RxView.clicks(button).map(o -> button).share(); Disposable buttonShowToast = clickObservable.subscribe(new Consumer<Button>() { @Override public void accept(Button o) throws Exception { Toast.makeText(getApplicationContext(), "Show toast", Toast.LENGTH_SHORT).show(); } }); compositeDisposable.add(buttonShowToast); Disposable changeButtonText = clickObservable.subscribe(new Consumer<Button>() { @Override public void accept(Button o) throws Exception { o.setText("New text"); } }); compositeDisposable.add(changeButtonText);
使用可观察到的RxView.click()上的共享运算符,单击该按钮时,将同时运行下面创建的两个Disposable。
让我们在Android应用程序中将以上概念与更多运算符合并:
代码
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Enter here!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.35000002" <TextView android:id="@+id/txtBelowEditText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginStart="8dp" android:layout_marginTop="16dp" android:text="TextView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/editText" <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@+id/button2" app:layout_constraintBaseline_toBaselineOf="@+id/button2" <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Button2" app:layout_constraintEnd_toStartOf="@+id/button" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" <TextView android:id="@+id/txtBelowButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="TextView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/button2" <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="24dp" android:layout_marginTop="8dp" android:src="@android:drawable/ic_input_add" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Simulateneous Actions" app:layout_constraintBottom_toTopOf="@+id/fab" android:layout_margin="8dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" </android.support.constraint.ConstraintLayout>
MainActivity.java
package com.theitroad.androidrxbinding; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import com.jakewharton.rxbinding2.view.RxView; import com.jakewharton.rxbinding2.widget.RxTextView; import java.util.concurrent.TimeUnit; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; public class MainActivity extends AppCompatActivity { Button button, button2, button3; FloatingActionButton fab; TextView txtBelowEditText, txtBelowButton; EditText editText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = findViewById(R.id.button); button2 = findViewById(R.id.button2); button3 = findViewById(R.id.button3); fab = findViewById(R.id.fab); txtBelowEditText = findViewById(R.id.txtBelowEditText); txtBelowButton = findViewById(R.id.txtBelowButton); editText = findViewById(R.id.editText); Observable<Object> observable1 = RxView.clicks(button2).map(o -> button2); Observable<Object> observable2 = RxView.clicks(fab).map(o -> fab); Disposable d1 = Observable.merge(observable1, observable2).throttleFirst(2, TimeUnit.SECONDS) .observeOn(AndroidSchedulers.mainThread()). subscribe(new Consumer<Object>() { @Override public void accept(Object o) { Toast.makeText(getApplicationContext(), "Avoid multiple clicks using throttleFirst", Toast.LENGTH_SHORT).show(); if (o instanceof Button) { txtBelowButton.setText(((Button) o).getText().toString() + " clicked"); } else if (o instanceof FloatingActionButton) { txtBelowButton.setText("Fab clicked"); } } }); Disposable d = RxView.clicks(button).debounce(5, TimeUnit.SECONDS). observeOn(AndroidSchedulers.mainThread()). map(o -> button.getText().toString()). subscribe(new Consumer<String>() { @Override public void accept(String o) throws Exception { txtBelowButton.setText(o + " was clicked"); } }); Disposable d2 = RxTextView.textChanges(editText) .filter(s -> s.toString().length() > 6) .debounce(2, TimeUnit.SECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<CharSequence>() { @Override public void accept(CharSequence charSequence) throws Exception { txtBelowEditText.setText(charSequence); } }); CompositeDisposable compositeDisposable = new CompositeDisposable(); Observable<Button> clickObservable = RxView.clicks(button3).map(o -> button3).share(); Disposable buttonShowToast = clickObservable.subscribe(new Consumer<Button>() { @Override public void accept(Button o) throws Exception { Toast.makeText(getApplicationContext(), "Show toast", Toast.LENGTH_SHORT).show(); } }); compositeDisposable.add(buttonShowToast); Disposable changeButtonText = clickObservable.subscribe(new Consumer<Button>() { @Override public void accept(Button o) throws Exception { o.setText("New text"); } }); compositeDisposable.add(changeButtonText); } }
在EditText中,我们设置了一个filter
运算符,直到长度超过阈值时才将输入文本设置到TextView上。
要清除所有Disposables,而不是分别对它们进行一次性调用,我们还可以执行以下操作:
CompositeDisposable clearAllDisposables = new CompositeDisposable(); clearAllDisposables.add(d1); clearAllDisposables.add(d2); clearAllDisposables.add(d); clearAllDisposables.clear();
实际应用程序的输出如下:
注意单击FloatingActionButton的次数。
但是,吐司只被展示了一次。