如何用RxJava实现矩阵键盘输入的最近4值联动七段显示屏并超时清屏?
这个需求刚好是RxJava擅长处理的场景——我们可以通过组合几个核心操作符来维护按键状态、处理超时,同时动态更新显示屏。先直接上代码,再拆解逻辑:
import io.reactivex.rxjava3.core.Observable; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; // 定义简单的事件类,区分按键事件和清屏事件 static class KeyEvent { final char character; KeyEvent(char c) { this.character = c; } } static class ClearScreenEvent {} public void setupKeypadAndDisplay() { KeypadController kbc = new KeypadController(); SevenSegmentController segment = new SevenSegmentController(); // 第一步:处理键盘输入,防抖+去重,确保每个按键只触发一次 Observable<KeyEvent> keyEvents = kbc.keypresses .debounce(250, TimeUnit.MILLISECONDS) .distinctUntilChanged() .map(KeyEvent::new); // 第二步:合并按键事件和超时清屏事件,超时后自动重启监听 Observable<Object> combinedEvents = keyEvents .timeout(1000, TimeUnit.MILLISECONDS, Observable.just(new ClearScreenEvent())) .repeatWhen(completed -> completed); // 超时后重新订阅,继续接收新按键 // 第三步:用scan维护当前显示的字符序列,处理清屏和超过4位的情况 combinedEvents .scan(new ArrayList<Character>(), (currentChars, event) -> { if (event instanceof ClearScreenEvent) { // 触发清屏,返回空序列 segment.clearScreen(); return new ArrayList<>(); } else if (event instanceof KeyEvent) { char newChar = ((KeyEvent) event).character; if (currentChars.size() >= 4) { // 已经满4位,清屏后开始新序列 segment.clearScreen(); ArrayList<Character> newSequence = new ArrayList<>(); newSequence.add(newChar); return newSequence; } else { // 添加新字符到序列末尾 ArrayList<Character> updatedSequence = new ArrayList<>(currentChars); updatedSequence.add(newChar); return updatedSequence; } } return currentChars; }) .filter(chars -> !chars.isEmpty()) // 过滤空序列,避免无效更新 .subscribe(displayChars -> { // 第四步:根据当前序列更新显示屏 // 序列顺序:[第一个输入的字符, 第二个, ..., 最新输入的字符] // 对应显示屏位置:长度1→位置0;长度2→位置1(旧)、0(新);以此类推 for (int i = 0; i < displayChars.size(); i++) { int displayPosition = (displayChars.size() - 1) - i; segment.writeDigit(displayPosition, displayChars.get(i)); } }); }
关键逻辑拆解
防抖与去重:
用debounce(250, TimeUnit.MILLISECONDS)过滤短时间内的重复按键触发,再加上distinctUntilChanged()防止长按导致的重复输出,确保每个按键只产生一次有效事件。超时清屏处理:
通过timeout()操作符,当1000ms内没有新按键时,自动发出ClearScreenEvent。repeatWhen()用来在超时后重新订阅按键流,保证后续按键还能被监听。状态维护(核心):
scan()操作符是这里的关键——它会持续维护一个状态(当前显示的字符序列),每次新事件进来时更新状态并输出。我们在这里处理两种情况:- 收到清屏事件:清空序列并调用显示屏清屏方法;
- 收到按键事件:如果序列已经满4位,先清屏再启动新序列;否则把新字符添加到序列末尾。
显示屏更新:
每次拿到更新后的字符序列,我们根据序列长度计算每个字符对应的显示屏位置:比如序列是[7,6],那么7对应位置1,6对应位置0,刚好实现你要的“新字符在右侧,旧字符左移”的效果。
关于你提到的ReplaySubject
你说的ReplaySubject.createWithTimeAndSize()确实能保留最近N个且在指定时间内的事件,但它的定位是给新订阅者回放历史事件,而不是持续输出当前的状态序列。我们的需求是每次新按键进来时,都要基于当前状态更新并输出新的序列,scan()才是更适合的工具——它能持续维护状态,并且每次都输出最新的状态供订阅者处理。
如果你的需求是要持续输出最近4个2000ms内的字符(比如每个字符单独输出,但只保留符合条件的),也可以用buffer(4, 1, TimeUnit.MILLISECONDS, 2000),但这种方式没法处理“满4位清屏”的自定义逻辑,还是scan()的灵活性更高。
内容的提问来源于stack exchange,提问作者Thomas

