监听键盘弹出是很常见的需求,但是键盘的状态并不由 Flutter 来控制,所以 Flutter 没有提供直接的方法来获取键盘的状态,不过我们可以通过其他方式来实现。

WidgetsBindingObserver

WidgetsBindingObserver 是一个比较常用的 mixin 类,通过名字就可以看出,这个类主要是用于监听 Widget 的各种状态,而键盘弹出会导致应用页面发生变化,所以我们可以使用 didChangeMetrics 这个方法来监听底部高度的变化。

使用实例

首先定义一个枚举类,表示键盘打开的状态。

enum KeyboardState { unknown, opening, closing, closed }

之后定义监听的逻辑,一般情况下都会选择监听 MediaQuery.viewInsetsOf(context).bottom 来计算底部的距离,因为底部的距离正好是键盘的高度,但是键盘的高度是一个变化的过程,并且就算是同一个键盘,最大高度也有可能发生变化,所以说无法知道键盘的最大高度。最好的方法是,监听高度的变化过程,如果是递增变化,那么就是正在打开的状态,如果是降序变化,那么就是正在关闭的状态,当底部距离变为 0 并且同时不是已经关闭的状态,那么就是关闭了。之所以不能单纯判断底部距离为 0 就是关闭状态是因为前面提到,键盘的高度是一个变化的过程,在键盘打开的前几帧,获取到的高度也可能是 0 。

有了逻辑就很容易了,在需要监听的类中混入 WidgetsBindingObserver 后重写 didChangeMetrics 方法。

List<double> heightList = [];

  @override
  void didChangeMetrics() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      var height = MediaQuery.viewInsetsOf(Get.context!).bottom;

      if (heightList.isNotEmpty && height != heightList.last) {
        if (height > heightList.last && state.keyboardState != KeyboardState.opening) {
          state.keyboardState = KeyboardState.opening;
          //正在打开
        } else if (height < heightList.last && state.keyboardState != KeyboardState.closing) {
          state.keyboardState = KeyboardState.closing;
          //正在关闭
        }
      }

      // 只在高度变化时记录高度
      if (heightList.isEmpty || height != heightList.last) {
        heightList.add(height);
      }

      // 当高度为0且键盘经历了开启关闭过程时,认为键盘已完全关闭
      if (height == 0 && state.keyboardState != KeyboardState.closed) {
        state.keyboardState = KeyboardState.closed;
        heightList.clear();
        //已经关闭
      }
    });
    super.didChangeMetrics();
  }

我这里使用了 getx 获取 context。注意不要忘记了 WidgetsBinding.instance.addObserver(this) 以及及时销毁 WidgetsBinding.instance.removeObserver(this)