前言
目前市场上的很多app都会有点击两次返回键, 才退出app的功能, 以此来避免用户的误操作点击返回键退出, 大致的逻辑便是用户在规定的时间内点击两次返回,第一次点击提示在点击一次
最初的解决思路
很自然的会想到用下列代码来实现(查了下网上大部分实现也是类似下列代码的思路)
// 这里规定时间再2秒内, 点击两次返回
private long lastTime = 0L;
@Override
public void onBackPressed() {
long currentTime = System.currentTimeMillis();
if ((currentTime - lastTime) > 2000) {
// 两次点击间隔超过2秒
Toast.makeText(this, "在点击一次返回", Toast.LENGTH_SHORT).show();
// 记录时间
lastTime = currentTime;
} else {
// 两次点击再2秒内, 即退出
super.onBackPressed();
}
}
上述代码虽然容易想到, 但是实现起来不够优雅, 需要自己去记录时间, 自己去做各种判断, 如果换个需求要求2秒内点击三次才退出(只是随便说说, 也不大可能会有这种需求), 那上述代码基本没法复用, 且会出现各种繁琐的逻辑判断.
但是如果使用RxJava
来实现该功能的话, 可以很轻松很优雅地解决该问题:
使用RxJava来解决
上述代码繁琐的原因在于需要定义一个变量来判断当前点击与上一次点击的时间差, 而当我们把点击事件变成事件流时, 很自然地就会相到使用debounce()
转换符来发射前后两次点击大于规定时间差的事件, 再将两个事件流合并起来处理就很简单了
// 返回事件流
private Subject<Integer> mBackClick = PublishSubject.create();
@Override
public void onBackPressed() {
mBackClick.onNext(1)
}
public void onCreate(){
// ... 省略代码
// 返回事件流(1 -> 1 -> 1 -> 1 -> 1 -> 1)与两次点击返回间隔大于2s的事件流(0 -> 0)合并
// 变成 (1 -> 0 -> 1 -> 0 -> 1 -> 1 -> 1 -> ...)
mBackClick.mergeWith(mBackClick.debounce(2000, TimeUnit.MILLISECONDS).map(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer i) throws Exception {
// 两次点击返回间隔大于2s的事件, 用0表示, 区分正常的点击
return 0;
}
}))
// 这一步转化是关键
// 使用一个scan转换符去做处理, 如何当前事件是0, 则返回0
// 否则则是上一个事件加一(这个就可以将所有前后间隔小于2s的事件标上序号, 方便后序订阅处理,
// 拿到序号1的事件弹出提示, 拿到序号2的序列做返回, 甚至于可以处理3号, 4号),
// 这样也就可以毫不费力地解决上诉说的要求2秒内点击三次才退出
// 变成 (1 -> 0 -> 1 -> 0 -> 1 -> 2 -> 3 -> ...)
.scan((prev, cur) -> {
if (cur == 0) return 0
return prev + 1
})
// 过滤掉0, 后面我们只关心拿到的是几号的时间
.filter(v -> v > 0)
.subscribe(v -> {
if (v == 1) {
// 弹出提示
} else if (v == 2) {
// 返回
} else if (v == 3) {
..
}
...
})
}
// 真实实现里记得取消订阅
以上即可处理各种在前后间隔n秒内, 同一个操作, 根据顺序采取不同的处理方式
总结
上诉关键的核心就在于使用debounce
生成一个新的流与原返回流合并, 将所有前后间隔事件小于2s的事件分隔开来, 并使用scan
转化符标记上序号, 方便后续订阅的处理, 可能画几张事件流程图更容易理解
DEMO
TODO
- [ ] 有空把事件流示意图画一下