扩展 Rxbinding,打造自定义监听多控件接口

原创 2017年07月29日 16:31:44

使用 Rxbinding 可以优雅的令我们完成一对一的控件监听效果,但是我们时常会在项目中遇到类似如下的需求:只有当两个控件满足同时满足一定的需求时才使得另一个控件状态改变,也就是一个控件需要同时监听两个控件的状态,例如在登录注册的界面,我们要求只有在用户名和密码 EditText 同时不为空的时候,按钮才可以点击。

在此之前我们的思路可能是,创建两个本地变量 String,在 EditText 的 textChange 事件中不停对 String 进行赋值,并在其中判断两个 String 是否都不为空,如果都不为空,则按钮属性为 enable,代码类似如下:

// 用户名 EditText 监听
RxTextView.textChanges(mNameEditText)
        .subscribe(new Consumer<CharSequence>() {
            @Override
            public void accept(CharSequence charSequence) throws Exception {
                name = mNameEditText.getText().toString();
                if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(password)) {
                    mEnsureButton.setEnabled(true);
                } else {
                    mEnsureButton.setEnabled(false);
                }
            }
        });

// 密码 EditText 监听
RxTextView.textChanges(mPasswordEditText)
        .subscribe(new Consumer<CharSequence>() {
            @Override
            public void accept(CharSequence charSequence) throws Exception {
                password = mPasswordEditText.getText().toString();
                if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(password)) {
                    mEnsureButton.setEnabled(true);
                } else {
                    mEnsureButton.setEnabled(false);
                }
            }
        });

但是这样很不优雅,我希望现在能有一个接口满足以下两点:

  • 支持监听两个或以上数量的控件
  • 返回一个 boolean 类型,此值代表是否两个控件内容同时都为空

这就需要我们对这块儿的源码进行阅读了,幸运的是关于这块的代码简单的不能再简单了,我们直接点进 textChanges() 方法进行查看 ——

@CheckResult @NonNull
  public static InitialValueObservable<CharSequence> textChanges(@NonNull TextView view) {
    checkNotNull(view, "view == null");
    return new TextViewTextObservable(view);
  }

我们看到实际就两行代码,第一行是对 view 进行判断是否为空,第二行的 TextViewTextObservable(View) 才是我们真正需要查看的地方,我们不妨再点进去看下 ——

final class TextViewTextObservable extends InitialValueObservable<CharSequence> {
    private final TextView view;

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

    @Override
    protected void subscribeListener(Observer<? super CharSequence> observer) {
        Listener listener = new Listener(view, observer);
        observer.onSubscribe(listener);
        view.addTextChangedListener(listener);
    }

    @Override
    protected CharSequence getInitialValue() {
        return view.getText();
    }

    final static class Listener extends MainThreadDisposable implements TextWatcher {
        private final TextView view;
        private final Observer<? super CharSequence> observer;

        Listener(TextView view, Observer<? super CharSequence> observer) {
            this.view = view;
            this.observer = observer;
        }

        @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 (!isDisposed()) {
                observer.onNext(s);
            }
        }

        @Override
        public void afterTextChanged(Editable s) {
        }

        @Override
        protected void onDispose() {
            view.removeTextChangedListener(this);
        }
    }
}

看到这里简直就该感叹,竟然源码这么简洁,话不多说,我们一行行来分析——

1.我们可以看到 TextViewTextObservable 所继承的 InitialValueObservable 类中的泛型是 CharSequence 类型,大胆的小伙伴已经猜出了,这个其实就是传给下游的数据类型,也同样是类中 getInitialValue() 方法的返回值,此方法顾名思义,就是初始化时候的初始值。所以如果我们想给下游传送什么值,就可以更改此处的泛型。

2.接下来是构造函数,我们可以看到构造函数中实际上就只是传入了一个 TextView,也就是我们在上例中传入的参数,所以我们可以在此处使用可变参数或者使用一个 TextView 数组,然后在构造函数中将其传给本地变量。

3.然后是 subscribeListener() 方法,此方法也是甚为简单,我们可以看到实际上我们是创建了一个我们自定义内部类 Listener 的对象,然后使用方法中传入的 Observer<? super CharSequence> 对象进行订阅,最后我们当然要为构造函数中传入的 TextView 对象添加这里的监听器了。所以我们在打造的 RxBinding 扩展接口中需要对传入的 TextView 进行遍历,将每一个 TextView 都加上 Listener 对象。

4.第四步是我们上面提到的 getInitialValue() 函数,假设如我们上面所需求的,需要返回一个 boolean 值,此值的意义是为 true 时代表所有 TextView 值都不为空。那么其实我们就是对所传入的 TextView 数组进行一个遍历,如果有任一为空,那么我们返回 false,否则返回为 true。

5.接下来就是我们自定义的 Listener 类,由于该函数只是对包内可见的,所以理论上我们在业务处理的地方不直接针对这里的 Listener 类进行操作,也就是说,关于 Listener 的初始化等操作,仅仅是发生在 TextViewTextObservable 这个类中。这里指的提出的一个是 onTextChanged() 函数,因为此处是我们每次向下游发送数据的地方,我们可以看到函数中是 observer.onNext(s);,也就是说实际上每次向下游传输的是 CharSequence 类型,所以这里也是我们需要更改的地方,我们在这里应该对所有传入的 TextView 数组进行判断,如果有任一为空,那么我们返回 false,否则返回为 true。最后就是需要在 onDispose() 函数中取消所有的订阅了!

代码如下:

public class TextViewEmptyObservable extends InitialValueObservable<Boolean> {
    private TextView[] textViews;

    public TextViewEmptyObservable(TextView... view) {
        textViews = view;
    }

    @Override
    protected void subscribeListener(Observer<? super Boolean> observer) {
        Listener listener = new Listener(textViews, observer);
        observer.onSubscribe(listener);
        addListener(listener);
    }

    private void addListener(Listener listener) {
        for (TextView textView : textViews) {
            textView.addTextChangedListener(listener);
        }
    }

    @Override
    protected Boolean getInitialValue() {
        return isAllNotEmpty();
    }

    private boolean isAllNotEmpty() {
        for (TextView textView : textViews) {
            if (TextUtils.isEmpty(textView.getText().toString().trim())) {
                return false;
            }
        }
        return true;
    }

    final static class Listener extends MainThreadDisposable implements TextWatcher {
        private final Observer<? super Boolean> observer;
        private TextView[] textViews;

        Listener(TextView[] view, Observer<? super Boolean> observer) {
            this.textViews = view;
            this.observer = observer;
        }

        @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 (!isDisposed()) {
                observer.onNext(isAllNotEmpty());
            }
        }

        @Override
        public void afterTextChanged(Editable s) {
        }

        @Override
        protected void onDispose() {
            removeAllListener();
        }

        private void removeAllListener() {
            for (TextView textView : textViews) {
                textView.removeTextChangedListener(this);
            }
        }

        private boolean isAllNotEmpty() {
            for (TextView textView : textViews) {
                if (TextUtils.isEmpty(textView.getText().toString().trim())) {
                    return false;
                }
            }
            return true;
        }
    }
}

public class RxBindingOperator {
    /**
     * 对多个 TextView/EditText 的输入内容进行监听
     * @param view TextView
     * @return 返回当前两者 TextView 是否同时为空
     */
    @CheckResult
    @NonNull
    public static InitialValueObservable<Boolean> textChanges(@NonNull TextView... view) {
        return new TextViewEmptyObservable(view);
    }
}

这里使用了可变参数,可传入任意数量的 TextView,最后实际调用代码如下:

RxBindingOperator
    .textChanges(mFirstEditText, mSecondEditText)
    .subscribe(new Consumer<Boolean>() {
            @Override
            public void accept(@NonNull Boolean notAllEmpty) throws Exception {
                mNetButton.setEnabled(notAllEmpty);
            }
        });
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Android 开发常见问题汇总

Dex分包,解决64K限制的问题:参考链接:https://developer.android.com/studio/build/multidex.html#mdex-gradle问题1:Androi...

android 开发常见问题心得

最近做了一个android的项目,比起一般的学习,感觉做项目更能对问题产生深入的了解,但这了解不在于原理,而在于实用。 最近做了一个android的项目,比起一般的学习,感觉做项目更能对问题产生...

Android 自定义View 环绕六边形控件及其TouchEvent事件的监听

六边形菜单自定义,点击六边形能执行不同的操作,
  • LJYBQ
  • LJYBQ
  • 2016年08月23日 12:27
  • 1261

接口与自定义控件

  • 2013年11月20日 17:09
  • 27KB
  • 下载

Android完全自定义控件并且实现监听事件

本篇文章带来Android的完全自定义控件。载体是自定义一个开关的控件,并且能够响应事件,首先我们先创一个项目,名字就叫ToggleView,修改MainActivity/** * 自定义开关 *...

Android之自定义AlertDialog无法监听控件

要做一个自定义的弹出框,以前都是用一个Activity来实现,总觉得不是很好看,弹出的框有时候感觉有点大,所以上网查资料说,可以给AlertDialog自定义一个view就行了,代码如下 ...

自定义ListView【通用】适配器并实现监听控件!

ListView :在Android应用开发过程中属于最常用的系统组件之一,当然可能童鞋们问为什么会突然游戏开发中讲这个,呵呵,其实在游戏开发中,也会常常使用到系统组件,比如游戏排行榜,简单的游戏关卡...
  • shimiso
  • shimiso
  • 2011年11月08日 22:52
  • 1937
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:扩展 Rxbinding,打造自定义监听多控件接口
举报原因:
原因补充:

(最多只允许输入30个字)