RxBinding-讓UI控件事件化身數(shù)據(jù)流

0. 目錄

  • 簡(jiǎn)介
  • 好處
  • GitHub 地址
  • 依賴
  • 示例

1. 簡(jiǎn)介

RxBinding 是 Jake Wharton 寫的框架,它的 Api 能夠把 Android 平臺(tái)和兼容包內(nèi)的 UI 控件變?yōu)?Observable 對(duì)象,這樣就可以把 UI 控件的事件當(dāng)做 Rxjava 中的數(shù)據(jù)流來使用了.

2. 好處

  • 能對(duì) View 事件使用 Rxjava 的各種操作.
  • 提供了與 Rxjava 一致的回調(diào),代碼簡(jiǎn)潔明了.
  • 幾乎支持所有的常用控件和事件.
  • 每個(gè)庫(kù)還有對(duì)應(yīng)的 Kotlin 支持庫(kù).

3. GitHub 地址

https://github.com/JakeWharton/RxBinding/

4. 依賴

implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'

5. 示例

用標(biāo)準(zhǔn)簡(jiǎn)潔的實(shí)現(xiàn)方式,作用于整個(gè)App的所有UI事件.

  • 點(diǎn)擊事件:
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO
            }
        });
        RxView.clicks(button)
                .subscribe(new Consumer<Object>() {
                    @Override
                    public void accept(Object o) throws Exception {
                        // TODO
                    }
                });
  • 文本輸入
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) {
                    // TODO
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });
        RxTextView.textChanges(editText)
                .subscribe(new Consumer<CharSequence>() {
            @Override
            public void accept(CharSequence charSequence) throws Exception {
                // TODO
            }
        });
  • 長(zhǎng)點(diǎn)擊事件:
 RxView.longClicks(button).subscribe(new Consumer<Object>() {
            @Override
            public void accept(Object o) throws Exception {
                
            }
            // TODO
        });
  • 防止重復(fù)點(diǎn)擊(弱網(wǎng)絡(luò)環(huán)境下):
加入依賴:
implementation 'com.safframework:saf-rxlifecycle:1.0.0'
RxView.clicks(button)
                .compose(useRxViewTransformer(this))
                .subscribe(new Consumer<Object>() {
                    @Override
                    public void accept(Object o) throws Exception {
                        // TODO
                    }
                });
    /**
     * 綁定內(nèi)存泄漏,解決Rxjava內(nèi)存泄漏的問題
     * @param targetActivity
     * @return
     */
    public ObservableTransformer useRxViewTransformer(final AppCompatActivity targetActivity) {
        return new ObservableTransformer() {

            @Override
            public ObservableSource apply(Observable upstream) {
                return upstream.compose(preventDuplicateClicksTransformer())
                        .compose(RxLifecycle.bind(targetActivity).toLifecycleTransformer());
            }
        };
    }

    /**
     * 防止重復(fù)點(diǎn)擊
     *
     * @return
     */
    public static ObservableTransformer preventDuplicateClicksTransformer() {
        return new ObservableTransformer() {
            @Override
            public ObservableSource apply(Observable upstream) {
                return upstream.throttleFirst(1000, TimeUnit.MICROSECONDS);
            }
        };
    }

說明:
① Android App 使用Rxjava 存在一個(gè)缺點(diǎn): 不完整的訂閱會(huì)導(dǎo)致內(nèi)存泄漏.
② 當(dāng)Observable 正在運(yùn)行,Activity或Fragment 銷毀后,其觀察者仍然會(huì)持有對(duì)它的引用,系統(tǒng)無法對(duì)此Activity/Fragment 進(jìn)行垃圾回收.
③ RxLifecycle 可以通過綁定聲明周期的方式,來解決內(nèi)存泄漏的問題.

  • 驗(yàn)證碼倒計(jì)時(shí):
RxView.clicks(button)
                .throttleFirst(MAX_COUNT_TIME, TimeUnit.SECONDS)
                .flatMap(new Function<Object, ObservableSource<Long>>() {
                    @Override
                    public ObservableSource<Long> apply(Object o) throws Exception {
                        // 更新發(fā)送按鈕的狀態(tài),并初始化顯示倒計(jì)時(shí)文字
                        RxView.enabled(button).accept(false);
                        RxTextView.text(button).accept("剩余" + MAX_COUNT_TIME + " 秒");
                        // 返回 n 秒內(nèi)的倒計(jì)時(shí)觀察者對(duì)象
                        return Observable.interval(1, TimeUnit.SECONDS, Schedulers.io())
                              .take(MAX_COUNT_TIME);
                    }
                })
                // 將遞增數(shù)字替換成遞減的倒計(jì)時(shí)數(shù)字
                .map(new Function<Long, Long>() {
                    @Override
                    public Long apply(Long aLong) throws Exception {
                        return MAX_COUNT_TIME - (aLong + 1);
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(Long aLong) throws Exception {
                        if (aLong == 0){
                            RxView.enabled(button).accept(true);
                            RxTextView.text(button).accept("獲取驗(yàn)證碼");
                        }else{
                            RxTextView.text(button).accept("剩余"+aLong +" 秒");
                        }
                    }
                });
  • RecyclerView:
implementation 'com.jakewharton.rxbinding2:rxbinding-recyclerview-v7:2.1.1'
    // recycerview 當(dāng)前沒有滾動(dòng)
    private static final int SCROLL_STATE_IDLE = 0;  
    // recyclerview 正在被拖動(dòng)
    private static final int SCROLL_STATE_DRAGGING = 1;
     // 手已經(jīng)離開屏幕,recyclerview正在做動(dòng)畫移動(dòng)到最終位置
    private static final int SCROLL_STATE_SETTLING = 2;
    RxRecyclerView.scrollStateChanges(recyclerView)
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(Integer scrollState) throws Exception {
                        Log.d("state",scrollState.toString());
                    }
                });

說明:

  1. RecyclerView 提供幾個(gè)狀態(tài)的觀察.
    ① scrollStateChanges 觀察 RecyclerView 的滾動(dòng)狀態(tài).
    ② scrollEvents 觀察 RecyclerView 的滾動(dòng)事件.
    ③ childAttachStateChangeEvents 觀察 child view 的 detached 狀態(tài) ,當(dāng) LayoutManager 或者 RecyclerView 認(rèn)為不再需要一個(gè) child view 時(shí),就會(huì)調(diào)用這個(gè)方法. 如果child view 占用資源,則應(yīng)當(dāng)釋放資源.
    ④ 以上的例子是 對(duì) 滾動(dòng)狀態(tài)的監(jiān)聽.
  2. 以下是對(duì)RecyclerView 點(diǎn)擊事件的監(jiān)聽:
@Override
    protected void convert(ViewHolder holder, final String s, int position) {
        holder.setText(R.id.text, s);
        RxView.clicks(holder.itemView)
                .subscribe(new Consumer<Object>() {

                    @Override
                    public void accept(Object o) throws Exception {
                        Toast.makeText(mContext, s, Toast.LENGTH_SHORT).show();
                    }
                });
    }
  • 表單驗(yàn)證:
public class ValidationResult {
    public boolean flag;
    public String message;

    public ValidationResult() {
        flag = true;
        message = "";
    }
}
 final Button button = findViewById(R.id.button);
        final EditText edit02 = findViewById(R.id.editText2);
        final EditText edit03 = findViewById(R.id.editText3);
        Observable<CharSequence> observable_phone = RxTextView.textChanges(edit02);
        Observable<CharSequence> observable_password = RxTextView.textChanges(edit03);
        Observable.combineLatest(observable_phone, observable_password, 
new BiFunction<CharSequence, CharSequence, ValidationResult>() {
            @Override
            public ValidationResult apply(CharSequence phone, CharSequence passwrod) throws Exception {
                if (phone.length() > 0 && passwrod.length() > 0) {
                    button.setBackground(getResources().getDrawable(R.drawable.shape_login_pressed));
                } else {
                    button.setBackground(getResources().getDrawable(R.drawable.shape_login_normal));
                }

                ValidationResult validationResult = new ValidationResult();
                if (phone.length() == 0) {
                    validationResult.flag = false;
                    validationResult.message = "手機(jī)號(hào)碼不能為空";
                } else if (phone.length() != 11) {
                    validationResult.flag = false;
                    validationResult.message = "手機(jī)號(hào)碼需要11位";
                } else if (passwrod.length() == 0) {
                    validationResult.flag = false;
                    validationResult.message = "密碼不能為空";
                }
                return validationResult;
            }
        }).subscribe(new Consumer<ValidationResult>() {
            @Override
            public void accept(ValidationResult validationResult) throws Exception {
                mReslut = validationResult;
            }
        });
        RxView.clicks(button)
                .compose(useRxViewTransformer(this))
                .subscribeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer() {
                    @Override
                    public void accept(Object o) throws Exception {
                        if (mReslut == null) return;
                        if (mReslut.flag) {
                            Toast.makeText(MainActivity.this, "success", Toast.LENGTH_SHORT).show();
                        } else {
                            Toast.makeText(MainActivity.this, mReslut.message, Toast.LENGTH_SHORT).show();
                        }
                    }
                });

說明:
① combineLatest 的作用就是將過個(gè)Observable發(fā)射的數(shù)據(jù)組裝起來然后再發(fā)射出來.
② 這里兩個(gè)輸入框只要內(nèi)容發(fā)生變化,就會(huì)發(fā)送 Observable , 此時(shí)我們即可在 BiFunction 中利用驗(yàn)證方法去判斷輸入框中最新的內(nèi)容,最終返回一個(gè)ValidationResult 對(duì)象.

6. 后續(xù)

如果大家喜歡這篇文章解恰,歡迎點(diǎn)贊;如果想看更多 rxjava 方面的技術(shù)涂佃,歡迎關(guān)注稻轨!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末灵莲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子殴俱,更是在濱河造成了極大的恐慌政冻,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粱挡,死亡現(xiàn)場(chǎng)離奇詭異赠幕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)询筏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門榕堰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嫌套,你說我怎么就攤上這事逆屡。” “怎么了踱讨?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵魏蔗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我痹筛,道長(zhǎng)莺治,這世上最難降的妖魔是什么廓鞠? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮谣旁,結(jié)果婚禮上床佳,老公的妹妹穿的比我還像新娘。我一直安慰自己榄审,他們只是感情好砌们,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著搁进,像睡著了一般浪感。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饼问,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天影兽,我揣著相機(jī)與錄音,去河邊找鬼匆瓜。 笑死赢笨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的驮吱。 我是一名探鬼主播茧妒,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼左冬!你這毒婦竟也來了桐筏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤拇砰,失蹤者是張志新(化名)和其女友劉穎梅忌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體除破,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牧氮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瑰枫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踱葛。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖光坝,靈堂內(nèi)的尸體忽然破棺而出尸诽,到底是詐尸還是另有隱情,我是刑警寧澤盯另,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布性含,位于F島的核電站,受9級(jí)特大地震影響鸳惯,放射性物質(zhì)發(fā)生泄漏商蕴。R本人自食惡果不足惜叠萍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望究恤。 院中可真熱鬧俭令,春花似錦、人聲如沸部宿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽理张。三九已至,卻和暖如春绵患,著一層夾襖步出監(jiān)牢的瞬間雾叭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工落蝙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留织狐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓筏勒,卻偏偏與公主長(zhǎng)得像移迫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子管行,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355