diff --git a/entry/src/main/ets/pages/StreamPage.ets b/entry/src/main/ets/pages/StreamPage.ets index 2d9a749..d0768db 100644 --- a/entry/src/main/ets/pages/StreamPage.ets +++ b/entry/src/main/ets/pages/StreamPage.ets @@ -2146,8 +2146,17 @@ struct StreamPage { if (result === ThreeFingerImeGestureResult.TRIGGERED) { this.touchInputHandler.cancelActiveInput(); this.panZoomHandler.cancelActiveGesture(); - this.showVirtualKeyboard = false; - this.imeManager.show(); + if (this.showKeyboardPanel) { + // 三指再次触发时主动关闭;hide() 会同步解除锁定。 + this.imeManager.hide(); + } else { + this.showVirtualKeyboard = false; + // 三指呼出的 IME 默认锁定:画面失焦事件只能触发重新显示, + // 仅再次三指或工具条关闭按钮可以主动关闭。 + this.imePanelLocked = true; + this.imeManager.setLocked(true); + this.imeManager.show(); + } } return result !== ThreeFingerImeGestureResult.PASS_THROUGH; } diff --git a/entry/src/main/ets/service/streaming/StreamIMEManager.ets b/entry/src/main/ets/service/streaming/StreamIMEManager.ets index 5cb3220..5a49e93 100644 --- a/entry/src/main/ets/service/streaming/StreamIMEManager.ets +++ b/entry/src/main/ets/service/streaming/StreamIMEManager.ets @@ -32,6 +32,8 @@ export interface IMECallbacks { export class StreamIMEManager { // ── 输入法控制器 ── private imeController: inputMethod.InputMethodController | null = null; + private showInProgress: boolean = false; + private showRequestId: number = 0; private imeReadyTime: number = 0; /** 锁定模式:开启后点击其他区域不会收起 IME / IME bar,HIDE 事件会被拦截并重新唤起软键盘 */ private locked: boolean = false; @@ -60,13 +62,6 @@ export class StreamIMEManager { return this.imeReadyTime; } - /** - * 当前 IME 是否正在显示 - */ - get isVisible(): boolean { - return this.imeController !== null; - } - /** * 当前是否处于锁定模式 */ @@ -88,17 +83,22 @@ export class StreamIMEManager { */ async show(): Promise { const session = this.viewModel?.getSession(); - if (!session) return; + if (!session || this.showInProgress || this.imeController) return; + + const requestId = ++this.showRequestId; + this.showInProgress = true; + let win: window.Window | null = null; + let controller: inputMethod.InputMethodController | null = null; try { // 监听键盘高度变化 - const win = await window.getLastWindow(this.context); + win = await window.getLastWindow(this.context); + if (requestId !== this.showRequestId) return; win.on('keyboardHeightChange', (height: number) => { this.callbacks?.onKeyboardHeightChanged(px2vp(height)); }); - const controller = inputMethod.getController(); - this.imeController = controller; + controller = inputMethod.getController(); // 先绑定输入法(必须先 attach 才能注册回调) const textConfig: inputMethod.TextConfig = { @@ -108,6 +108,11 @@ export class StreamIMEManager { } }; await controller.attach(true, textConfig); + if (requestId !== this.showRequestId) { + controller.detach(); + win.off('keyboardHeightChange'); + return; + } // attach 成功后注册输入回调 controller.on('insertText', (text: string) => { @@ -143,10 +148,32 @@ export class StreamIMEManager { } }); + // 完成 attach 和全部回调注册后再发布 controller,避免暴露半初始化状态。 + this.imeController = controller; this.callbacks?.onKeyboardVisibilityChanged(true); this.imeReadyTime = Date.now(); } catch (err) { console.error(`[StreamPage] Failed to show IME: ${JSON.stringify(err)}`); + if (controller) { + try { + controller.detach(); + } catch (cleanupErr) { + console.warn(`[StreamIME] Failed to detach after show error: ${JSON.stringify(cleanupErr)}`); + } + } + if (win) { + try { + win.off('keyboardHeightChange'); + } catch (cleanupErr) { + console.warn(`[StreamIME] Failed to remove height listener: ${JSON.stringify(cleanupErr)}`); + } + } + this.imeController = null; + this.locked = false; + this.callbacks?.onKeyboardVisibilityChanged(false); + this.callbacks?.onKeyboardHeightChanged(0); + } finally { + this.showInProgress = false; } } @@ -155,6 +182,7 @@ export class StreamIMEManager { */ hide(): void { // 主动 hide 时释放锁定状态,避免下次 show 时锁定误传递 + this.showRequestId++; this.locked = false; try { if (this.imeController) {