HarmonyOS Flutter 键盘高度监听插件开发:从基础到实战完全解析
原文地址: https://88box.top 生成时间: 2026-05-19 16:33:19
HarmonyOS Flutter 键盘高度监听插件开发完全指南 - hey99 知识搜索引擎
精选文章
HarmonyOS Flutter 键盘高度监听插件开发完全指南
在 Flutter 聊天应用开发中,实现“键盘-面板平滑切换”是一个常见需求——用户点击表情按钮时,键盘收起,表情面板以相同高度弹出,过渡自然流畅。这一体验的核心技术点,正是准确获取键盘高度。 在 A
更新于 2026-05-19 08:17
前端
在 Flutter 聊天应用开发中,实现“键盘-面板平滑切换”是一个常见需求——用户点击表情按钮时,键盘收起,表情面板以相同高度弹出,过渡自然流畅。这一体验的核心技术点,正是准确获取键盘高度。
在 Android 和 iOS 平台,Flutter 已有成熟的键盘监听方案(如 MediaQuery 的 viewInsets)。但在鸿蒙(HarmonyOS)平台上,由于 Flutter 官方尚未提供直接支持,开发者需要借助 Platform Channel 机制,通过原生代码(ArkTS)监听键盘事件,再将高度回传给 Flutter 端。
然而,HarmonyOS 的 Flutter 插件开发存在一些特殊问题:
窗口获取时机:在 onAttachedToEngine 中直接获取窗口会抛出 1300002 错误;
安全区域获取:使用 TYPE_SYSTEM 获取底部安全区域可能返回 0;
单位转换:原生键盘高度单位为物理像素(px),需转换为 Flutter 使用的虚拟像素(vp)。
本文将手把手带你实现一个完整的 HarmonyOS 键盘高度监听插件,涵盖技术难点分析、完整代码实现、生命周期管理以及常见问题解决方案。无论你是 Flutter 插件开发者,还是需要将 IM 应用移植到鸿蒙的工程师,本文都能为你提供直接可用的参考。
二、技术难点与解决方案
2.1 窗口获取时机问题
问题描述:
最初的实现方案是在
onAttachedToEngine
中直接获取窗口:
// ❌ 错误方案
onAttachedToEngine(binding: FlutterPluginBinding): void {
const win = await window.getLastWindow(binding.getApplicationContext());
// 报错:{
"code"
:1300002} - 窗口状态异常
}
错误码
1300002
表示窗口状态异常,因为此时窗口尚未创建完成。
解决方案:
实现
AbilityAware
接口,通过
WindowFocusChangedListener
监听窗口焦点变化,在窗口获得焦点后再设置监听:
// ✅ 正确方案
onAttachedToAbility(binding: AbilityPluginBinding): void {
this.ability = binding.getAbility();
binding.addOnWindowFocusChangedListener(this.windowFocusChangedListener);
}
private windowFocusChangedListener: WindowFocusChangedListener = {
onWindowFocusChanged: (hasFocus: boolean) => {
if
(hasFocus && !this.isKeyboardListenerSetup) {
this.setupKeyboardListener();
}
}
};
2.2 安全区域获取
问题描述:
使用
TYPE_SYSTEM
获取底部安全区域始终返回 0。
解决方案:
优先使用
TYPE_NAVIGATION_INDICATOR
获取导航指示器(底部小白条)区域:
private getSafeAreaBottom(win: window.Window): number {
const uiContext = win.getUIContext();
// 优先获取导航指示器区域
try {
const navigationArea = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
if
(navigationArea.bottomRect.height > 0) {
return
uiContext.px2vp(navigationArea.bottomRect.height);
}
} catch (e) {}
// 降级获取系统避让区域
try {
const systemArea = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
if
(systemArea.bottomRect.height > 0) {
return
uiContext.px2vp(systemArea.bottomRect.height);
}
} catch (e) {}
return
0;
}
三、完整实现
3.1 插件架构
chat_keyboard_panel/
├── lib/
│ └── src/
│ └── keyboard_height.dart
Flutter 端实现
├── ohos/
│ ├── index.ets
插件导出
│ └── src/main/ets/components/plugin/
│ └── ChatKeyboardPanelPlugin.ets
原生实现
3.2 原生端实现 (ArkTS)
import {
AbilityAware,
AbilityPluginBinding,
FlutterPlugin,
FlutterPluginBinding,
MethodCall,
MethodCallHandler,
MethodChannel,
MethodResult,
WindowFocusChangedListener,
} from
'@ohos/flutter_ohos'
;
import { window } from
'@kit.ArkUI'
;
import UIAbility from
'@ohos.app.ability.UIAbility'
;
export
default class ChatKeyboardPanelPlugin implements FlutterPlugin, MethodCallHandler, AbilityAware {
private channel: MethodChannel | null = null;
private abilityBinding: AbilityPluginBinding | null = null;
private ability: UIAbility | null = null;
private mainWindow: window.Window | null = null;
private safeAreaBottom: number = 0;
private isKeyboardListenerSetup: boolean =
false
;
getUniqueClassName(): string {
return
"ChatKeyboardPanelPlugin"
}
// FlutterPlugin 生命周期
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.channel = new MethodChannel(binding.getBinaryMessenger(),
"chat_keyboard_panel"
);
this.channel.setMethodCallHandler(this);
}
onDetachedFromEngine(_: FlutterPluginBinding): void {
if
(this.channel != null) {
this.channel.setMethodCallHandler(null);
this.channel = null;
}
}
// AbilityAware 生命周期
onAttachedToAbility(binding: AbilityPluginBinding): void {
this.abilityBinding = binding;
this.ability = binding.getAbility();
binding.addOnWindowFocusChangedListener(this.windowFocusChangedListener);
}
onDetachedFromAbility(): void {
if
(this.abilityBinding != null) {
this.abilityBinding.removeOnWindowFocusChangedListener(this.windowFocusChangedListener);
this.abilityBinding = null;
}
this.removeKeyboardListener();
this.ability = null;
this.mainWindow = null;
this.isKeyboardListenerSetup =
false
;
}
onMethodCall(call: MethodCall, result: MethodResult): void {
if
(call.method ==
"getPlatformVersion"
) {
result.success(
"Harmony"
);
}
else
{
result.notImplemented();
}
}
// 窗口焦点监听器
private windowFocusChangedListener: WindowFocusChangedListener = {
onWindowFocusChanged: (hasFocus: boolean) => {
if
(hasFocus && !this.isKeyboardListenerSetup) {
this.setupKeyboardListener();
}
}
};
// 设置键盘监听
private async setupKeyboardListener(): Promise
if
(this.isKeyboardListenerSetup || this.ability == null)
return
;
try {
const win = await window.getLastWindow(this.ability.context);
if
(win == null)
return
;
this.mainWindow = win;
const uiContext = win.getUIContext();
this.safeAreaBottom = this.getSafeAreaBottom(win);
// 监听键盘高度变化
win.on(
'keyboardHeightChange'
, (height: number) => {
const keyboardHeight = uiContext.px2vp(height);
this.notifyKeyboardHeight(keyboardHeight);
});
this.isKeyboardListenerSetup =
true
;
// 移除窗口焦点监听器
if
(this.abilityBinding != null) {
this.abilityBinding.removeOnWindowFocusChangedListener(this.windowFocusChangedListener);
}
} catch (error) {
console.error(`setupKeyboardListener error:
${JSON.stringify(error)}
`);
}
}
// 获取底部安全区域
private getSafeAreaBottom(win: window.Window): number {
const uiContext = win.getUIContext();
try {
const area = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
if
(area.bottomRect.height > 0) {
return
uiContext.px2vp(area.bottomRect.height);
}
} catch (e) {}
return
0;
}
// 移除键盘监听
private removeKeyboardListener(): void {
if
(this.mainWindow != null) {
try {
this.mainWindow.off(
'keyboardHeightChange'
);
} catch (error) {}
}
}
// 通知 Flutter 端
private notifyKeyboardHeight(keyboardHeight: number): void {
if
(this.channel != null) {
const args: Record
'height'
: keyboardHeight,
'safeArea'
: this.safeAreaBottom
};
this.channel.invokeMethod(
'height'
, args);
}
}
}
3.3 Flutter 端实现
import
'package:flutter/services.dart'
;
class ChatKeyboardHeight {
MethodChannel channel = const MethodChannel(
'chat_keyboard_panel'
);
static ChatKeyboardHeight? _instance;
static ChatKeyboardHeight get instance =>
instance ??= ChatKeyboardHeight.();
final List
ChatKeyboardHeight.
_
() {
channel.setMethodCallHandler((call) async {
if (call.method == 'height') {
Map map = call.arguments;
double height = (map["height"] ??
0
).toDouble();
double safeArea = (map["safeArea"] ??
0
).toDouble();
for (var element in _keyboardHeightCallbacks) {
element(height, safeArea);
}
}
});
}
void addKeyboardHeightCallback(KeyboardHeightCallback callback) {
_keyboardHeightCallbacks.add(callback);
}
void removeKeyboardHeightCallback(KeyboardHeightCallback callback) {
_keyboardHeightCallbacks.remove(callback);
}
}
typedef KeyboardHeightCallback = void Function(double height, double safeArea);
四、使用方式
4.1 Flutter 端使用
@override
void
initState
() {
super.initState();
ChatKeyboardHeight.instance.addKeyboardHeightCallback(_onKeyboardHeight);
}
@override
void
dispose
() {
ChatKeyboardHeight.instance.removeKeyboardHeightCallback(_onKeyboardHeight);
super.dispose();
}
void _onKeyboardHeight(double height, double safeArea) {
setState(() {
_keyboardHeight = height;
_safeAreaBottom = safeArea;
});
}
4.2 宿主应用配置
使用此插件的应用只需保持标准的
EntryAbility
配置即可,无需额外代码:
import { FlutterAbility, FlutterEngine } from
'@ohos/flutter_ohos'
;
import { GeneratedPluginRegistrant } from
'../plugins/GeneratedPluginRegistrant'
;
export
default class EntryAbility extends FlutterAbility {
configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine);
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}
五、关键点总结
问题
解决方案
窗口获取时机
实现
AbilityAware
接口,在窗口获得焦点后获取
窗口状态异常 (1300002)
使用
WindowFocusChangedListener
延迟初始化
安全区域为 0
优先使用
TYPE_NAVIGATION_INDICATOR
单位转换
使用
uiContext.px2vp()
将 px 转为 vp
资源清理
在
onDetachedFromAbility
中移除所有监听器
六、生命周期流程图
FlutterEngine 创建
↓
onAttachedToEngine (创建 MethodChannel)
↓
onAttachedToAbility (注册 WindowFocusChangedListener)
↓
窗口获得焦点 (onWindowFocusChanged:
true
)
↓
setupKeyboardListener (获取窗口、设置键盘监听)
↓
键盘弹出/收起 (keyboardHeightChange)
↓
notifyKeyboardHeight → Flutter 端回调
↓
onDetachedFromAbility (清理资源)
↓
onDetachedFromEngine (释放 MethodChannel)
通过以上实现,插件能够在 HarmonyOS 平台上准确监听键盘高度变化,为 Flutter 应用提供流畅的键盘面板切换体验。
参考文档:
注册环信即时通讯IM
环信IM集成文档
查看原文
🏷 标签: HarmonyOS, Flutter插件开发, ArkTS, 键盘监听, Platform Channel