- Published on
Flutter 监听软键盘弹出 / 关闭:官方原生方案(替代 keyboard_visibility)
- Authors

- 作者
- kai
目录

Flutter 监听软键盘弹出/关闭:官方原生方案(替代 keyboard_visibility)
在 Flutter 开发中,监听软键盘的弹出与关闭是高频需求,但第三方插件 keyboard_visibility 已停止维护,存在编译报错(如 build.gradle 冲突)等问题,不推荐在新项目中使用。
推荐使用 Flutter 官方提供的 WidgetsBindingObserver 原生方案,无需依赖第三方库,稳定性更高、兼容性更强。以下是完整的实现步骤和注意事项。
核心方案:使用 WidgetsBindingObserver 监听
通过继承 WidgetsBindingObserver 并监听设备尺寸变化,间接判断软键盘的显示状态(软键盘弹出/关闭会改变屏幕可用区域)。
完整实现代码
import 'package:flutter/material.dart';
class KeyboardListenPage extends StatefulWidget {
const KeyboardListenPage({super.key});
State<KeyboardListenPage> createState() => _KeyboardListenPageState();
}
class _KeyboardListenPageState extends State<KeyboardListenPage>
with WidgetsBindingObserver { // 继承监听类
// 记录键盘是否显示
bool _isKeyboardVisible = false;
void initState() {
super.initState();
// 注册监听器
WidgetsBinding.instance.addObserver(this);
}
/// 监听设备尺寸变化(软键盘弹出/关闭会触发)
void didChangeMetrics() {
super.didChangeMetrics();
// 延迟执行,确保获取到最新的视图信息
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) { // 防止页面销毁后 setState
setState(() {
// 通过 viewInsets.bottom 判断键盘状态:>0 表示弹出,=0 表示关闭
_isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0;
});
// 执行自定义业务逻辑
if (_isKeyboardVisible) {
print("✅ 软键盘弹出");
// 键盘弹出时的操作:如滚动列表、隐藏底部组件等
} else {
print("❌ 软键盘关闭");
// 键盘关闭时的操作:如恢复页面状态、保存输入内容等
}
}
});
}
void dispose() {
// 移除监听器,避免内存泄漏
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
// 关键配置:必须设为 false,否则 viewInsets.bottom 始终为 0
resizeToAvoidBottomInset: false,
appBar: AppBar(title: const Text("键盘监听示例")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const TextField(
decoration: InputDecoration(
hintText: "输入内容触发键盘",
border: OutlineInputBorder()
),
),
const SizedBox(height: 20),
Text(_isKeyboardVisible ? "键盘已弹出" : "键盘已关闭"),
],
),
),
);
}
}
关键注意事项(必看)
1. 解决 viewInsets.bottom 始终为 0 的问题
核心原因:父组件 Scaffold 的 resizeToAvoidBottomInset 属性默认值为 true,会自动调整页面尺寸以避免输入框被键盘遮挡,导致无法获取键盘高度。
解决方案:将所有父级 Scaffold 的 resizeToAvoidBottomInset 统一设置为 false(包括嵌套页面的 Scaffold)。
2. 避免内存泄漏
- 初始化时通过
addObserver注册监听器,必须在dispose中通过removeObserver移除。 - 执行
setState前需判断mounted,防止页面销毁后仍触发状态更新。
3. 延迟执行的必要性
通过 addPostFrameCallback 延迟获取 viewInsets.bottom,确保在 Widget 构建完成后获取最新的视图数据,避免数据不准确。
拓展场景:获取键盘具体高度
若需要获取键盘的实际高度(而非仅判断显示状态),可通过计算屏幕尺寸变化实现:
double _keyboardHeight = 0;
// 记录初始屏幕高度
double _initialScreenHeight = 0;
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
// 初始化时获取屏幕高度
_initialScreenHeight = MediaQuery.of(context).size.height;
}
void didChangeMetrics() {
super.didChangeMetrics();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
final currentScreenHeight = MediaQuery.of(context).size.height;
final viewInsetsBottom = MediaQuery.of(context).viewInsets.bottom;
// 键盘高度 = 初始屏幕高度 - 当前屏幕高度(仅键盘弹出时有效)
_keyboardHeight = viewInsetsBottom > 0
? _initialScreenHeight - currentScreenHeight
: 0;
print("键盘高度:$_keyboardHeight");
}
});
}
方案优势
- 原生支持:基于 Flutter 官方 API,无需第三方依赖,兼容性覆盖所有 Flutter 版本。
- 稳定性高:无编译报错、版本冲突等问题,维护成本低。
- 功能灵活:可仅判断状态,也可获取具体高度,适配多种业务场景。
