一、緣由
Flutter彈窗里面有輸入框,就會(huì)導(dǎo)致鍵盤頂起輸入框.鍵盤彈起速度比較慢,所以看起來就是彈窗卡了.
解決這個(gè)問題有兩個(gè)思路:
- 彈窗給一個(gè)bottomPadding,這個(gè)值跟隨系統(tǒng)鍵盤高度(MediaQueryData.fromWindow(window).viewInsets.bottom)變化
- 在登錄頁面提前獲取鍵盤高度,然后在需要彈窗的頁面直接加上鍵盤高度
我試了下方案一,其實(shí)還是有些卡頓,準(zhǔn)備使用方案二解決.
二、實(shí)踐思路
目前Flutter沒有直接提供鍵盤高度和打開關(guān)閉的監(jiān)聽,所以我們需要自己封裝一下.
主要思路是根據(jù)WidgetsBindingObserver中的didChangeMetrics方法不斷監(jiān)聽鍵盤的高度.記錄下,取出最大值即可.如果我們想要監(jiān)聽鍵盤完全打開或者關(guān)閉.就需要自己記錄鍵盤高度的狀態(tài),bottom == 0時(shí)候鍵盤沒展示,bottom == keyboardHeight表示鍵盤在最高位置.剩下都是就是彈出或者關(guān)閉的過程中.
用戶可以手動(dòng)改變鍵盤模式,例如改成手寫,就會(huì)導(dǎo)致鍵盤變高,再從手寫改成九鍵,會(huì)導(dǎo)致鍵盤變矮.變高我們?nèi)稳皇怯?jì)算彈出最大值即可.變低這種情況不太好處理.我是判斷了之前鍵盤是打開狀態(tài),然后在鍵盤變化的時(shí)候去延遲一下獲取.由于鍵盤從手寫變成九鍵很快,只需要稍微延遲,就能拿到變化后的值.
除了鍵盤高度計(jì)算,獲取到鍵盤高度后保存本地,方便其他界面不去WidgetsBinding.instance.addObserver也能獲取鍵盤高度.我還做了下鍵盤完全展開和關(guān)閉的監(jiān)聽.由于KeyBoardObserver是單列,也可以直接通過keyboardHeight字段獲取鍵盤高度.
keyboardHeight 是鍵盤實(shí)時(shí)高度,直接放到布局的padding中會(huì)抖動(dòng),這種情況直接使用getKeyBordHeight()獲取上一次鍵盤高度。
三、代碼實(shí)現(xiàn)
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);
/// 這個(gè)是實(shí)時(shí)變化的鍵盤高度
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;
///計(jì)算回調(diào)次數(shù)
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), () {
///重點(diǎn)檢測(cè)鍵盤變手寫
if (bottom != 0 && times < 3 && bottom == preBottom) {
keyboardHeight = bottom;
listener?.call(bottom == 0);
saveHeightToSp();
debugPrint("didChangeMetrics 鍵盤直走一次的時(shí)候記錄高度 keyboardHeight:$keyboardHeight");
times = 0;
// state.setState(() {});
}
});
}
// 鍵盤存在中間態(tài),回調(diào)是鍵盤冒出來的高度
keyboardHeight = max(keyboardHeight, bottom);
// if (bottom != 0 && preBottom == bottom) {
// keyboardHeight = bottom;
// debugPrint("didChangeMetrics 鍵盤展開 preBottom == bottom keyboardHeight $keyboardHeight");
// isKeyboardShow = true;
// }
///折疊屏幕鍵盤高度會(huì)變化 折疊屏幕的適配 有些問題todo
// if (bottom != 0 && preBottom == bottom && isKeyboardShow == null && bottom > 250) {
// keyboardHeight = bottom;
// debugPrint("didChangeMetrics 鍵盤展開 preBottom == bottom keyboardHeight $keyboardHeight");
// //isKeyboardShow = true; todo 先不改變狀態(tài),看下后面是否有問題
// }
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 {
///鍵盤打開和收起,都會(huì)走這里。
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 鍵盤正在關(guān)閉");
}
///當(dāng)鍵盤之前處于打開的時(shí)候才需要獲取 todo 有了次數(shù),這個(gè)應(yīng)該可以去掉
// if (_preKeyboardShow == true) {
// ///從手寫切換到拼音,鍵盤會(huì)變小,這個(gè)時(shí)候無法判斷,延遲一下,如果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();
// }
// });
// }
///當(dāng)鍵盤正在打開的時(shí)候才需要
if (isOpening == true) {
///記住當(dāng)前值,如果大于250,延時(shí)50ms,如果當(dāng)前值和之前值是一樣的,則表示鍵盤最大值,
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;
///改變鍵盤為手寫模式也會(huì)走這里,但是如果是手寫改成9按鍵(鍵盤由高變?。?,不會(huì)走這里。
debugPrint("didChangeMetrics 鍵盤完全收起或展開再刷新頁面 bottom $bottom keyboardHeight $keyboardHeight isKeyboardShow $isKeyboardShow _preKeyboardShow $_preKeyboardShow");
times = 0;
// listener?.call(bottom == 0);
listener?.call(isKeyboardShow == true);
///收起時(shí)候保存鍵盤高度
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 ");
}
}
///提供給布局直接使用 雖然你不是實(shí)時(shí)高度,但是不會(huì)出現(xiàn)抖動(dòng)問題
Future<double> getKeyBordHeight() async {
final instance = await SharedPreferences.getInstance();
return instance.getDouble(KEYBOARD_MAX_HEIGHT) ?? 0;
}
}