在t秒内n次点击后切换状态

时间:2016-12-16 06:00:28

标签: android rx-java

如果用户在t秒窗口内的某个按钮上单击n次,我想使用RxJava来打开和关闭菜单。经过t秒后,点击计数器应重置,循环重新开始。

我有一个解决方案,见下文:

    RxView
            .clicks(view.findViewById(R.id.title))
            .map(new Func1<Void, Integer>() {
                @Override
                public Integer call(Void aVoid) {
                    return 1;
                }
            })
            .publish(new Func1<Observable<Integer>, Observable<List<Integer>>>() {
                @Override
                public Observable<List<Integer>> call(final Observable<Integer> sourceObservable) {
                    return sourceObservable.buffer(sourceObservable, new Func1<Integer, Observable<Long>>() {
                        @Override
                        public Observable<Long> call(Integer aVoid) {
                            // we trigger after 2 seconds, or after MIN_CLICKS_TOGGLE clicks have been recorded
                            return Observable
                                    .merge(Observable.timer(2L, TimeUnit.SECONDS),
                                            sourceObservable.buffer(MIN_CLICKS_TOGGLE_MODE - 1)
                                                    .map(new Func1<List<Integer>, Long>() {
                                                        @Override
                                                        public Long call(List<Integer> integers) {
                                                            return 1L;
                                                        }
                                                    }))
                                    .first();
                        }
                    });
                }
            })
            .filter(new Func1<List<Integer>, Boolean>() {
                @Override
                public Boolean call(List<Integer> integerList) {
                    return integerList.size() == MIN_CLICKS_TOGGLE_MODE;
                }
            })
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Action1<List<Integer>>() {
                @Override
                public void call(List<Integer> integerList) {
                    toggleMode();
                }
            });

然而,这个解决方案有两个问题:

  • 关闭observable(计时器和缓冲区的合并以计算点击次数)仅当我从我想要检测的点击次数中减去1时才有效
  • 如果我点击n + 1次,则会调用切换功能两次,因此菜单会打开和关闭

有关如何改善这一点的任何建议?谢谢!

2 个答案:

答案 0 :(得分:1)

使用window with boundary provider运算符,这样只有在第一次点击后才会创建窗口。

    final int MARK = 0;
    final int TRIGGER = 1;

    final BehaviorSubject<Integer> boundaryObservable = BehaviorSubject.create();

    RxView.clicks(view.findViewById(R.id.title))
            .doOnNext(new Action1<Void>() {
                @Override
                public void call(Void ignore) {
                    // Checks if window is open and in proper state(MARK) 
                    if (boundaryObservable.hasValue() && boundaryObservable.getValue() == MARK) {
                        boundaryObservable.onNext(TRIGGER);
                    }
                }
            })
            .window(boundaryObservable.filter(new Func1<Integer, Boolean>() {
                @Override
                public Boolean call(Integer value) {
                    // Only use TRIGGER values as window boundary values
                    return value == TRIGGER;
                }
            }))
            .flatMap(new Func1<Observable<Void>, Observable<Notification<Void>>>() {
                @Override
                public Observable<Notification<Void>> call(Observable<Void> longObservable) {
                    return longObservable
                            .skip(CLICK_THRESHOLD-1)
                            .take(1)
                            .timeout(WINDOW_SIZE, TimeUnit.SECONDS)
                            // materialize is needed to pass information about 
                            //ending of the window but to not close the obserable 
                            //with an error if it timeouts
                            .materialize();
                }
            })
            .filter(new Func1<Notification<Void>, Boolean>() {
                @Override
                public Boolean call(Notification<Void> booleanNotification) {
                    return booleanNotification.isOnNext() || booleanNotification.isOnError();
                }
            })
            .doOnNext(new Action1<Notification<Void>>() {
                @Override
                public void call(Notification<Void> notification) {
                    Log.d("TTAG", "Got " + (notification.isOnNext() ? "good clicks" : "timeout") + ". ");
                    // Save information that new window is ready to be opened on next click
                    boundaryObservable.onNext(MARK);
                }
            })
            // filter only onNext events to prevent premature ending of the observable
            .filter(new Func1<Notification<Void>, Boolean>() {
                @Override
                public Boolean call(Notification<Void> booleanNotification) {
                    return booleanNotification.isOnNext();
                }
            })
            .dematerialize()
            .subscribe(new Action1<Object>() {
                @Override
                public void call(Object o) {
                    Log.d("TTAG", "Finally got the click. ");
                }
            });

之前的尝试:

您可以使用buffer运算符 所以你的代码看起来像是:

RxView.clicks(view.findViewById(R.id.title))
  .buffer(WINDOW_SIZE, 0, TimeUnit.SECONDS)
    
  .map { clickList -> clickList.size }
  .filter{ size -> size > CLICK_THRESHOLD}
  .subscribe { /*Your sucessfull "toggle" clicks*/ }

buffer会为WIDNOW_SIZE秒的片段“分割”源可观察对象,然后将生成的clickList映射到其大小,然后过滤那些size s大于CLICK_THRESHOLD

这个例子是用Kotlin编写的,但是可以直接翻译成Java。

其他方法是使用window运算符,它还允许您将observable分割成碎片,但这些碎片的排放可立即获得。

RxView.clicks(view.findViewById(R.id.title))
        .window(WINDOW_SIZE, TimeUnit.SECONDS)
        .flatMap(new Func1<Observable<Long>, Observable<Long>>() {
            @Override
            public Observable<Long> call(Observable<Long> longObservable) {
                return longObservable
                        .skip(CLICK_THRESHOLD)
                        .take(1);
            }
        })

跳过并取走操作符只允许您在达到阈值时发出信号。

答案 1 :(得分:0)

这就是我处理类似问题的方法,但我只需要点击2次,因此需要稍加修改。因此,代码未经过测试。

RxView.clicks(view.findViewById(R.id.title))
            .map(aVoid -> new Date().getTime())
            .buffer(n, 1) // every time upstream emits, it forwards last `n` emissions in a list to the downstream 
            .filter(aTimestamps -> (aTimestamps.get(aTimestamps.size() - 1) - aTimestamps.get(0)) < TIMEOUT)
            .throttleFirst(1000, TimeUnit.MILLISECONDS)

请注意,使用throttleFirst运算符过滤多次点击是 防弹,直到为其设置的超时大于或等于TIMEOUT const值。但无论如何,如果你只想摆脱意外的过度点击,它可能并不重要。