RxJava(十二):RxBinding

博客主页

1. RxBinding 简介

1.1 RxBinding 介绍

RxBinding 是 Jake Wharton 大神写的框架,它的 API 能够把 Android 平台和兼容包内的 UI 控件变为 Observable 对象,这样就可以把 UI 控件的事件当作 RxJava 中的数据流来使用了。

比如 View 的 onClick 事件,使用 RxView.clicks(view)即可获取一个 Observable 对象,每当用户单击这个 View 的时候,该 Observable 对象就会发射一个事件, Observable 的观察者就可以通过 onNext 回调知道用户单击了 View。

RxBinding GitHub 地址:https://github.com/JakeWharto...

RxBinding 的优点:

  • 它是对 Android View 事件的扩展,它使得开发者可以对 View 事件使用 RxJava 的各种操作
  • 提供了与 RxJava 一致的回调,使得代码简洁明了,尤其是页面上充斥着大量的监昕件,以及各种各样的匿名内部类
  • 几乎支持所有的常用控件及事件( v4 、v7、 design、recyclerview 等〉,另外每个库还有对应的 Kotlin 支持库。

Rx.Binding 的下载:

Platform bindings:

implementation 'com.jakewharton.rxbinding3:rxbinding:3.1.0'

AndroidX library bindings:

implementation 'com.jakewharton.rxbinding3:rxbinding-core:3.1.0'
implementation 'com.jakewharton.rxbinding3:rxbinding-appcompat:3.1.0'
implementation 'com.jakewharton.rxbinding3:rxbinding-drawerlayout:3.1.0'
implementation 'com.jakewharton.rxbinding3:rxbinding-leanback:3.1.0'
implementation 'com.jakewharton.rxbinding3:rxbinding-recyclerview:3.1.0'
implementation 'com.jakewharton.rxbinding3:rxbinding-slidingpanelayout:3.1.0'
implementation 'com.jakewharton.rxbinding3:rxbinding-swiperefreshlayout:3.1.0'
implementation 'com.jakewharton.rxbinding3:rxbinding-viewpager:3.1.0'
implementation 'com.jakewharton.rxbinding3:rxbinding-viewpager2:3.1.0'

Google 'material' library bindings:

implementation 'com.jakewharton.rxbinding3:rxbinding-material:3.1.0'

1.2 响应式的 Android UI

对 UI 事件(例如点击、滑动和文本输入)的响应几乎是 Android App 开发的基本部分,但是 Android SDK 对 UI 事件的处理有些复杂 ,我们通常需要使用各种 listeners、handlers、
TextWatchers 和其他组件等组合来响应 UI 事件。这些组件中的每一个都需要编写大量的样板代码,更为糟糕的是,实现这些不同组件的方式并不一致。例如,你可以通过实现 OnClickListener 来处理 OnClick 件。

这种一致性的缺乏可能会为代码增加很多复杂性。如果有些 UI 组件需要依赖于其他 UI 组件的输出,那么事情会变得更加复杂。

即使是一个简单的需求,例如要求用户将其名称输入到 EditText ,以便个性化地展示 TextView 的文本内容,而 TextView 需要嵌套回调,这是非常难以实现和维护的(有人将嵌套回调称为“回调地狱”〉。

显然,处理 UI 事件的标准化方法有大大简化代码的空间,而 RxBinding 就是这样的库,它
提供的绑定能够将任何 Android View 事件转换为 Observable。

一旦将 View 事件转换为 Observable ,它将发射数据流形式的 UI 事件,我们就可以订阅这个数据流,这与订阅其他 Observable 方式相同。接下来,看看如何实现 OnClick 事件:

Button button = findViewById(R.id.button);

RxView.clicks(button)
        .subscribe(new Consumer() {
            @Override
            public void accept(Object obj) throws Exception {
                Log.d(TAG, "RxBinding.click");
            }
        }); 
 

这种方法不仅更简洁,而且是一种标准的实现方式,我们可以将其应用于整个 App 的所有 UI 事件。例如,捕获文本输入与捕获点击事件的模式是一样的:

EditText editText = findViewById(R.id.edit_text);
RxTextView.textChanges(editText)
        .subscribe(new Consumer() {
            @Override
            public void accept(CharSequence charSequence) throws Exception {
                Log.d(TAG, "RxBinding.textChanges-> " + charSequence);
            }
        });

2. RxBinding 使用场景

RxBinding 可以应用于整个 App 的所有 UI 事件,下面列举一些 RxBinding 比较常见的使用场景。

2.1 点击事件

按钮的点击事件是每 App 都会用到的场景,可以使用 RxView 的 clicks(@NonNull View view)方法来绑定 UI 控件

RxView.clicks(button1)
        .subscribe(new Consumer() {
            @Override
            public void accept(Object o) throws Exception {
                Log.d(TAG, "演示点击事件");
            }
        }); 
 

2.2 长点击事件

长点击事件也是一个比较常见的事件,可以使用 RxView 的 longClicks(@NonNull View view) 方法来绑定 UI 控件

RxView.longClicks(button2)
        .subscribe(new Consumer() {
            @Override
            public void accept(Unit unit) throws Exception {
                Log.d(TAG, "演示长点击事件");
            }
        });

2.3 防止重复点击

在弱网络环境下,经常会遇到点击某个按钮没有响应的情况,此时心急的用户可能会多次点击按钮,从而造成事件的多次触发,显然这是我们不愿意看到的情况。可以利用 throttleFirst 操作符获取某段时间内的第一次点击事件。

RxView.clicks(button3)
        .compose(RxUtils.useRxViewTransformer(RxBindingAct.this))
        .subscribe(new Consumer() {
            @Override
            public void accept(Object o) throws Exception {
                Log.d(TAG, "防止重复点击");
            }
        });

这里使用了 compose 操作符,它封装了两个操作: 一个是防止 UI 事件的重复点击,另一个
是绑定 Activity 的生命周期,防止内存泄露。来看一下 useRxViewTransformer 方法的具体代码,
它里面包含了两个 compose()

// 对 RxView 绑定事件
// 封装了防止重复点击和RxLifecycle的生命周期
public static ObservableTransformer useRxViewTransformer(final RxAppCompatActivity targetActivity) {
    return new ObservableTransformer() {
        @Override
        public ObservableSource apply(Observable upstream) {
            return upstream.compose(RxJavaUtils.preventDuplicateClicksTransformer())
                    .compose(targetActivity.bindToLifecycle());
        }
    };
}

先来看看第一个 compose 的作用

// 防止重复点击的Transformer
@JvmStatic
fun  preventDuplicateClicksTransformer(): ObservableTransformer {
    return ObservableTransformer { upstream ->
        upstream.throttleFirst(1000, TimeUnit.MILLISECONDS)
    }
}

它的用途是在 ls 内只取第一次点击,这样可以防止 ls 内产生多次点击事件。

https://github.com/fengzhizi7...

2.4 表单的验证

App 内常见的表单验证是用户登录页面,我们需要对用户名、密码做一些校验。对于校验,有些是服务端做的,例如,用户名是否存在、用户名的密码是否正确等。而有些校验则需要客户端来做,例如,用户名是否输入、输入的用户名是否规范、密码是否输入等。

例如,手机号码不足 11 位时,会出现一个提示

如果密码没有输入,就点击“登录”按钮,则会弹出一个提示

EditText phone = findViewById(R.id.phone);
EditText password = findViewById(R.id.password);


Observable observablePhone = RxTextView.textChanges(phone);
Observable observablePassword = RxTextView.textChanges(password);


Observable.combineLatest(observablePhone, observablePassword, new BiFunction() {
    @Override
    public ValidationResult apply(CharSequence charSequence1, CharSequence charSequence2) throws Exception {

        ValidationResult result = new ValidationResult();

        if (charSequence1.length() == 0) {
            result.flag = false;
            result.message = "手机号码不能为空";
        } else if (charSequence1.length() != 11) {
            result.flag = false;
            result.message = "手机号码需要11位";
        } else if (charSequence2.length() == 0) {
            result.flag = false;
            result.message = "密码不能为空";
        }

        return result;
    }
}).subscribe(new Consumer() {
    @Override
    public void accept(ValidationResult validationResult) throws Exception {
        result = validationResult;
    }
});

Button login = findViewById(R.id.login);

RxView.clicks(login)
        .compose(RxUtils.useRxViewTransformer(RxBindingAct.this))
        .subscribeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer() {
            @Override
            public void accept(Object o) throws Exception {
                if (result == null) return;

                if (result.flag) {
                    Log.d(TAG, "result-> 登陆成功.");
                } else {
                    Log.d(TAG, "result-> " + result.message);
                }
            }
        }); 
 

combineLatest 的作用是将多个 Observable 发射的数据组装起来然后再发射出来。这里两个输入框只要内容发生变化,就会发送 Observable ,此时我们即可在 BiFunction 中利用验证方法去判断输入框中最新的内容,最终返 ValidationResult 对象。

2.5 获取验证码倒计时

用户注册账号时,一般需要获取验证码来验证手机号码

在等待验证码的过程中, App 界面上通常会有一个倒计时,提示我们剩余 xx 秒可以重新获取验证码。

final long MAX_COUNT_TIME = 60;

RxView.clicks(button5)
        .throttleFirst(MAX_COUNT_TIME, TimeUnit.SECONDS)
        .flatMap(new Function>() {
            @Override
            public ObservableSource apply(Object o) throws Exception {
                // 1. 更新发送按钮的状态,并初始化显现倒计时文字
                Log.d(TAG, "flatMap");
                // 2. 返回 n 秒内的倒计时观察者对象
                return Observable.interval(1, TimeUnit.SECONDS, Schedulers.io()).take(MAX_COUNT_TIME);
            }
        })
        // 将递增数字替换成递减的倒计时数字
        .map(new Function() {
            @Override
            public Long apply(Long aLong) throws Exception {
                Log.d(TAG, "map: " + aLong);
                return MAX_COUNT_TIME - (aLong - 1);
            }
        })
        // 切换到 Android 的主线程
        .observeOn(AndroidSchedulers.mainThread())

        .subscribe(new Consumer() {
            @Override
            public void accept(Long aLong) throws Exception {
                if (aLong == 0) {
                    Log.d(TAG, "倒计时完成.");
                } else {
                    Log.d(TAG, "倒计时剩余:" + aLong + " 秒");
                }
            }
        });

Observable.interval(1, TimeUnit.SECONDS, Schedulers.io()) 表示每 ls 发射一次数据。
take(MAX_COUNT_TIME) 和后面的操作表示按钮在 60s 内不可再次被点击,并且在这段时间每隔一秒发射一次数据用于更新 UI。 在实际使用中,需要在 flatMap 里做获取短信验证码络请求。

2.5 6. RecyclerView 的支持

Rx.Bindin 提供了一个 rxbinding-recyclerview 的库,专门用于对 RecyclerView 支持。

其中, RxRecyclerView 提供了几个状态的观察:

  • scrollStateChanges 观察 RecyclerView 的滚动状态
  • scrollEvents 观察 RecyclerView 的滚动事件
  • childAttachStateChangeEvents 观察 child view 的 detached 状态, LayoutManager 或者 RecyclerView 认为不再需要一个 child view 时,就会调用这个方法。 如果 child view 占用资源,则应当释放资源。
RxRecyclerView
        .scrollStateChanges(recyclerView)
        .subscribe(new Consumer() {
            @Override
            public void accept(Integer scrollState) throws Exception {
                Log.d(TAG, "scrollState: " + scrollState);
            }
        });

scrollState 表示 RecyclerView 中定义的滚动状态。

// RecyclerView 当前没有滚动
public static final int SCROLL_STATE_IDLE = 0;

// RecyclerView 正在被拖动
public static final int SCROLL_STATE_DRAGGING = 1;

// 手指已经离开屏幕,RecyclerView 正在做动画移动到最终位置
public static final int SCROLL_STATE_SETTLING = 2;

除可以对 RecyclerView 状态的进行监听外,还能对点击事件进行监听

在 Adapter 的 onBindViewHolder() 中,可以使用 clicks() 来绑定 itemView 的点击事件。

2.7 对 UI 控件进行多次监听

可以利用 RxJava 的操作符,例如 publish、share、replay ,实现对 UI 控件的多次监听

Observable observable = RxView.clicks(button5)
        .compose(RxUtils.useRxViewTransformer(this))
        .share();
observable.subscribe(new Consumer() {
    @Override
    public void accept(Object o) throws Exception {
        Log.d(TAG, "第一次监听");
    }
});

observable.subscribe(new Consumer() {
    @Override
    public void accept(Object o) throws Exception {
        Log.d(TAG, "第二次监听");
    }
});

// 执行结果
 第一次监听
 第二次监听

使用了 share 操作符,随后做了两次监监听,点击该控件打印两次

3. RxBinding 结合 RxPermissions 的使用

3.1 Android 6.0 之后权限的改变

https://developer.android.goo...
https://developer.android.goo...

Android 6.0 带来一个很大变化就是权限机制的改变,特别是运行时权限。

Android 6.0+添加的运行时权限可分为两类:

  • Normal Permissions: 这类权限不涉及个人隐私,不需要用户授权,比如手机震动、访问网络等。
  • Dangerous Permissions :这类权限涉及个人隐私,需要用户授权,比如读取 SD 卡、访问通讯录等

Dangerous Permissions 是有分组的。App 运行在 Android 6.0+的手机之上,如果用户申请了某个 Dangerous Permissions ,而该用户己经授权了一个与他现在申请的是同一组的 Dangerous Permissions ,那么系统会自动授权,无须用户再次授权。
RxJava(十二):RxBinding_第1张图片

对于 Android 6.0 以下的手机,用户在安装 App 的时候可以看到权限声明产生一个权限列表,用户只有在同意之后才能完成 App 的安装。如果用户想要使用某个 App,就需要忍受其一些不必要的权限(例如访问通讯录、短信的权限等)。从 Android 6.0 以后我们可以直接安装 App,当 App 需要我们授予不恰当的权限的时候,我们可以予以拒绝。当然作为用户也可以在手机的设置界面里对每个 App 的权限进行查看,井对单个权限进行授权或者解除授权。

App 的 targetSdkVersion 是 23 及以上,并且 App 运行在 Android 6.0 及以上的设备时,需同时满足这两个条件才需要动态地请求危险权限。

3.2 RxPermissions 的介绍

在处理运行时权限时,通常需要两步:

  • 申请权限
  • 处理权限回调,根据授权的情况进行回调

RxPermissions 的出现可以简化这些步骤,它是基于 RxJava 开发的 Android 框架,帮助 Android 6.0 之后处理运行时权限的检测。

RxPermissions GitHub 地址:https://github.com/tbruyelle/...

RxPermissions 的下载:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

dependencies {
    implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
}

3.3 RxBinding 结合 RxPermissions

在 RxPermissions 使用之前,需要先创建 RxPermissions 的实例。可以在 Activity 的 onCreate() 中进行创建,创建之后才能使用它。

RxPermissions rxPermissions = new RxPermissions(this);

3.3.1 在 RxBinding 中使用 RxPermissions

举一个拨打电话的例子,CALL_PHONE 在 Android 6.0 之后是一个 Dangerous Permissions, 第一次使用时需要动态申请该权限,只有得到允许才能完成后面打电话的动作。

RxView.clicks(findViewById(R.id.button))
        .subscribe(new Consumer() {
            @Override
            public void accept(Object object) throws Exception {

                rxPermissions.request(Manifest.permission.CALL_PHONE)
                        .subscribe(new Consumer() {
                            @Override
                            public void accept(Boolean granted) throws Exception {
                                if (granted) {
                                    Log.d(TAG, "授权成功.");
                                } else {
                                    Log.d(TAG, "授权失败");
                                }
                            }
                        });
            }
        }); 
 

3.3.2 RxBinding 结合 compose,使用 RxPermissions

RxBinding 可以结合 compose 操作符来使用 RxPermissions 。

RxView.clicks(findViewById(R.id.button))
        .compose(rxPermissions.ensure(Manifest.permission.CALL_PHONE))
        .subscribe(new Consumer() {
            @Override
            public void accept(Boolean granted) throws Exception {
                if (granted) {
                    Log.d(TAG, "授权成功.");
                } else {
                    Log.d(TAG, "授权失败");
                }
            }
        });

3.3.3 使用多个权限的用法

RxPermissions 也支持申请多个权限,如同时申请 CAMERA 和 WRITE_EXTERNAL_STORAGE 的权限。单击按钮之后,需要授权两次,任何一次授权的失败都会导致“打开相机失败”。只有两次申请权限都成功,才能“打开相机成功”。

RxView.clicks(findViewById(R.id.button))
        .compose(rxPermissions.ensure(Manifest.permission.CALL_PHONE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE))
        .subscribe(new Consumer() {
            @Override
            public void accept(Boolean granted) throws Exception {
                if (granted) {
                    Log.d(TAG, "打开相机成功.");
                } else {
                    Log.d(TAG, "打开相机失败");
                }
            }
        });

4. RxBinding 使用的注意点

trello 的 RxLifecycle

https://github.com/trello/RxLifecycle

知乎的 RxLifecycle

https://github.com/zhihu/RxLifecycle

其它的 RxLifecycle,这个与知乎的 RxLifecycle 的区别是, LifecycleTransformer 实现了多个 Transformer 接口。

https://github.com/fengzhizi715/SAF/tree/master/saf-rxlifecycle

如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)

你可能感兴趣的:(java,android,rxjava,rxandroid)