一、缘由
Flutter弹窗里面有输入框,就会导致键盘顶起输入框.键盘弹起速度比较慢,所以看起来就是弹窗卡了.
解决这个问题有两个思路:
- 弹窗给一个bottomPadding,这个值跟随系统键盘高度(MediaQueryData.fromWindow(window).viewInsets.bottom)变化
- 在登录页面提前获取键盘高度,然后在需要弹窗的页面直接加上键盘高度
我试了下方案一,其实还是有些卡顿,准备使用方案二解决.
二、实践思路
目前Flutter没有直接提供键盘高度和打开关闭的监听,所以我们需要自己封装一下.
主要思路是根据WidgetsBindingObserver中的didChangeMetrics方法不断监听键盘的高度.记录下,取出最大值即可.如果我们想要监听键盘完全打开或者关闭.就需要自己记录键盘高度的状态,bottom == 0时候键盘没展示,bottom == keyboardHeight表示键盘在最高位置.剩下都是就是弹出或者关闭的过程中.
用户可以手动改变键盘模式,例如改成手写,就会导致键盘变高,再从手写改成九键,会导致键盘变矮.变高我们任然是计算弹出最大值即可.变低这种情况不太好处理.我是判断了之前键盘是打开状态,然后在键盘变化的时候去延迟一下获取.由于键盘从手写变成九键很快,只需要稍微延迟,就能拿到变化后的值.
除了键盘高度计算,获取到键盘高度后保存本地,方便其他界面不去WidgetsBinding.instance.addObserver也能获取键盘高度.我还做了下键盘完全展开和关闭的监听.由于KeyBoardObserver是单列,也可以直接通过keyboardHeight字段获取键盘高度.
keyboardHeight 是键盘实时高度,直接放到布局的padding中会抖动,这种情况直接使用getKeyBordHeight()获取上一次键盘高度。
三、代码实现
import 'dart:math';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// initState中添加 WidgetsBinding.instance.addObserver(KeyBoardObserver.instance);
/// dispose中添加 WidgetsBinding.instance.removeObserver(KeyBoardObserver.instance);
/// 这个是实时变化的键盘高度
typedef FunctionType = void Function(bool isKeyboardShow);
class KeyBoardObserver extends WidgetsBindingObserver {
double keyboardHeight = 0;
bool? isKeyboardShow;
bool? _preKeyboardShow;
bool? isOpening;
static const KEYBOARD_MAX_HEIGHT = "keyboard_max_height";
double preBottom = -1;
double lastBottom = -1;
///计算回调次数
int times = 0;
KeyBoardObserver._() {
_getHeightToSp();
}
static final KeyBoardObserver _instance = KeyBoardObserver._();
static KeyBoardObserver get instance => _instance;
FunctionType? listener;
void addListener(FunctionType listener) {
this.listener = listener;
}
@override
void didChangeMetrics() {
times++;
debugPrint("didChangeMetrics times $times");
final bottom = MediaQueryData.fromWindow(window).viewInsets.bottom;
if (times == 1) {
Future.delayed(const Duration(milliseconds: 50), () {
///重点检测键盘变手写
if (bottom != 0 && times < 3 && bottom == preBottom) {
keyboardHeight = bottom;
listener?.call(bottom == 0);
saveHeightToSp();
debugPrint("didChangeMetrics 键盘直走一次的时候记录高度 keyboardHeight:$keyboardHeight");
times = 0;
// state.setState(() {});
}
});
}
// 键盘存在中间态,回调是键盘冒出来的高度
keyboardHeight = max(keyboardHeight, bottom);
// if (bottom != 0 && preBottom == bottom) {
// keyboardHeight = bottom;
// debugPrint("didChangeMetrics 键盘展开 preBottom == bottom keyboardHeight $keyboardHeight");
// isKeyboardShow = true;
// }
///折叠屏幕键盘高度会变化 折叠屏幕的适配 有些问题todo
// if (bottom != 0 && preBottom == bottom && isKeyboardShow == null && bottom > 250) {
// keyboardHeight = bottom;
// debugPrint("didChangeMetrics 键盘展开 preBottom == bottom keyboardHeight $keyboardHeight");
// //isKeyboardShow = true; todo 先不改变状态,看下后面是否有问题
// }
if (bottom == 0 && keyboardHeight != 0) {
debugPrint("didChangeMetrics bottom == 0 keyboardHeight $keyboardHeight");
isKeyboardShow = false;
} else if (bottom == keyboardHeight) {
debugPrint("didChangeMetrics bottom == keyboardHeight bottom $bottom keyboardHeight $keyboardHeight");
isKeyboardShow = true;
} else {
///键盘打开和收起,都会走这里。
debugPrint("didChangeMetrics isKeyboardShow == null _preKeyboardShow $_preKeyboardShow bottom $bottom keyboardHeight $keyboardHeight");
isKeyboardShow = null;
if (_preKeyboardShow == false) {
isOpening = true;
debugPrint("didChangeMetrics 键盘正在打开");
}
if (_preKeyboardShow == true) {
isOpening = false;
debugPrint("didChangeMetrics 键盘正在关闭");
}
///当键盘之前处于打开的时候才需要获取 todo 有了次数,这个应该可以去掉
// if (_preKeyboardShow == true) {
// ///从手写切换到拼音,键盘会变小,这个时候无法判断,延迟一下,如果bottom 大于200,那么就是键盘高度。
// Future.delayed(const Duration(milliseconds: 100), () {
// final height = MediaQueryData.fromWindow(window).viewInsets.bottom;
// if (height > 200) {
// keyboardHeight = height;
// debugPrint("didChangeMetrics 掩饰200ms keyboardHeight $keyboardHeight 键盘高度 bottom $height");
// saveHeightToSp();
// }
// });
// }
///当键盘正在打开的时候才需要
if (isOpening == true) {
///记住当前值,如果大于250,延时50ms,如果当前值和之前值是一样的,则表示键盘最大值,
if (bottom > 250) {
lastBottom = bottom;
Future.delayed(const Duration(milliseconds: 100), () {
final bottom = MediaQueryData.fromWindow(window).viewInsets.bottom;
if (lastBottom == bottom && isKeyboardShow == null) {
keyboardHeight = bottom;
debugPrint("didChangeMetrics 键盘展开 preBottom == bottom keyboardHeight $keyboardHeight");
isKeyboardShow = true;
}
});
}
}
}
///展开和收起 第一次
if (isKeyboardShow != null && _preKeyboardShow != isKeyboardShow && keyboardHeight != 0) {
// double bottom = MediaQueryData
// .fromWindow(window)
// .viewInsets
// .bottom;
///改变键盘为手写模式也会走这里,但是如果是手写改成9按键(键盘由高变小),不会走这里。
debugPrint("didChangeMetrics 键盘完全收起或展开再刷新页面 bottom $bottom keyboardHeight $keyboardHeight isKeyboardShow $isKeyboardShow _preKeyboardShow $_preKeyboardShow");
times = 0;
// listener?.call(bottom == 0);
listener?.call(isKeyboardShow == true);
///收起时候保存键盘高度
if (bottom == 0 && keyboardHeight != 0) {
saveHeightToSp();
}
}
_preKeyboardShow = isKeyboardShow;
preBottom = bottom;
}
Future<void> saveHeightToSp() async {
final instance = await SharedPreferences.getInstance();
instance.setDouble(KEYBOARD_MAX_HEIGHT, keyboardHeight);
}
void _getHeightToSp() async {
if (keyboardHeight == 0) {
final instance = await SharedPreferences.getInstance();
keyboardHeight = instance.getDouble(KEYBOARD_MAX_HEIGHT) ?? 0;
debugPrint(" KeyBoardObserver._() keyboardHeight : $keyboardHeight ");
}
}
///提供给布局直接使用 虽然你不是实时高度,但是不会出现抖动问题
Future<double> getKeyBordHeight() async {
final instance = await SharedPreferences.getInstance();
return instance.getDouble(KEYBOARD_MAX_HEIGHT) ?? 0;
}
}