Flutter 計(jì)算鍵盤高度以及打開關(guān)閉監(jiān)聽

一、緣由

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;
  }
}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容