如果用户在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();
}
});
然而,这个解决方案有两个问题:
有关如何改善这一点的任何建议?谢谢!
答案 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值。但无论如何,如果你只想摆脱意外的过度点击,它可能并不重要。