Android RxBinding

时间:2020-02-23 14:29:14  来源:igfitidea点击:

在先前的教程中,我们讨论了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的次数。
但是,吐司只被展示了一次。