跳转至

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 _keyboardHeightCallbacks = [];

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