Flutter - 聊天键盘与面板丝滑切换的强势升级 🍻

4,163 阅读5分钟

欢迎关注微信公众号:FSA全栈行动 👋

BiliBili: www.bilibili.com/video/BV1yT…

一、概述

距离 chat_bottom_container 首个可用版本 (0.0.2) 的发布已经过去了 1 个多月,在这期间根据大家的使用反馈,我们也做了一些优化调整,今天就来盘点一下到底做了哪些优化,新增了什么功能,以及一些常见操作。

请注意

开源不易,如果你也觉得这个库好用,请不吝给个 Star 👍 ,并多多支持!

github.com/LinXunFeng/…

二、使用

调整键盘高度监听管理逻辑

0.1.0 版本前,只考虑了页面栈这种常规情况,当键盘高度变化时只处理栈顶的监听。

但其实还有一种常见打破该规则的场景,就是悬浮聊天页,它会一直在页面上,可能为了能快速从悬浮小球展开聊天页面,收起时只是做了隐藏,而不会销毁页面,在这种情况下,它依旧在监听管理里的栈顶,所以在收起后,上一个聊天页的键盘高度监听就会失效。

这个在 0.1.0 版本中得到修复,内部会倒序遍历调用所有的监听回调。

不过你不用担心这一改动会导致其它聊天页面出现多余的视图刷新,因为在键盘高度监听回调里会先判断输入框是否有焦点,若无则直接返回了。

兼容外接键盘

当连接外接键盘时,软键盘会消失,高度会降为 0,这里可以用 iOS 模拟器结合 Toggle Software Keyboard (快捷键: cmd + k) 来模拟连接与断开外接键盘的效果。

隐藏面板

有小伙伴提出,不知道如何程序式的隐藏面板,其实很简单,就两步

  1. 让输入框失去焦点
  2. 更新内部状态为 ChatBottomPanelType.none
hidePanel() {
  // 0.2.0 前
  inputFocusNode.unfocus();
  if (ChatBottomPanelType.none == controller.currentPanelType) return;
  controller.updatePanelType(ChatBottomPanelType.none);
  
  // 0.2.0 后,可以这么写
  controller.updatePanelType(
    ChatBottomPanelType.none,
    forceHandleFocus: ChatBottomHandleFocus.unfocus,
  );
}

自定义底部安全区高度

在默认情况下,chat_bottom_container 在收起模式 (.none) 下会自动帮你添加底部安全区高度,但在一些场景下你可能不希望如此。比如:

  • 安卓的底部安全区的高度,很多小伙伴都是简单粗暴的设置个高度了事
  • App 首页有底部 BottomNavigationBar,不需要安全区高度

在此,你可以通过将 safeAreaBottom 参数来自定义这个高度,如下设置为 0

return ChatBottomPanelContainer<PanelType>(
  ...
  safeAreaBottom: 0,
  ...
);

调整键盘面板高度

如示例中位于首页的聊天页面

在键盘弹出时,如下图所示

实际期望

很明显,我们希望键盘容器高度能够减去外层底部固定的 BottomNavigationBar 高度。

ChatBottomPanelContainer 提供了 changeKeyboardPanelHeight 回调,在回调中可以拿到当前的键盘高度,经过计算后,将合适的键盘容器高度返回即可。

return ChatBottomPanelContainer<PanelType>(
  ...
  changeKeyboardPanelHeight: (keyboardHeight) {
    final renderObj = bottomNavigationBarKey.currentContext?.findRenderObject();
    if (renderObj is! RenderBox) return keyboardHeight;
    return keyboardHeight - renderObj.size.height;
  },
  ...
);

缓存键盘高度

先来看未做键盘高度缓存处理之前,会发生什么?

上图一共进入了三次聊天页

  • 第一次是先点击键盘,再切到表情面板,体验起来还是挺不错的。
  • 为了避免一闪而过,没有注意到,所以第二次和第三次的操作是一样的,先唤起表情面板,再切到键盘,可以看到在切到键盘时会抖动。

这是因为每次进入聊天页,键盘的高度为初始值 0,在 0.2.0 版本中对此进行了优化,加入了键盘高度缓存逻辑,从而尽量避免该抖动问题的出现。

❗️ 但需要注意的是,假如你卸载重装 App,该缓存会丢失,即你还是有可能会看到最多一次的抖动。

除此之外,你还可以使用这个缓存的键盘高度来实现表情面板与键盘高度保持一致的效果,这样在切换的时候体验上会更好些。😉

Widget _buildEmojiPickerPanel() {
  // 如果键盘高度还没有缓存过,则使用默认高度 300
  double height = 300;
  final keyboardHeight = controller.keyboardHeight;
  if (keyboardHeight != 0) {
    height = keyboardHeight;
  }

  return Container(
    padding: EdgeInsets.zero,
    height: height,
    color: Colors.blue[50],
    child: const Center(
      child: Text('Emoji Panel'),
    ),
  );
}

效果如下

支持表情面板与输入框焦点共存

这也是提升用户体验的重要一点,效果见上图。

先按如下设置你的输入框

bool readOnly = false;
TextEditingController textEditingController = TextEditingController();

...
TextField(
  controller: textEditingController,
  focusNode: inputFocusNode,
  // 为 true 时不显示键盘,默认为 false
  readOnly: readOnly,
  // 获取焦点后显示光标,设置为 true 才不受 readOnly 的影响
  showCursor: true,
),
...

接下来就是切换表情面板的操作

switchToEmojiPanel() {
  readOnly = true;
  // 这里你可以只刷新输入框
  setState(() {});

  // 等待下一帧
  WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
    controller.updatePanelType(
      // 内部切至 other 状态
      ChatBottomPanelType.other,
      // 关联外部的面板类型为表情面板
      data: PanelType.emoji,
      // 输入框获取焦点
      forceHandleFocus: ChatBottomHandleFocus.requestFocus,
    );
  });
}

updatePanelType 方法中,如果是切至 .other 状态,是会帮你执行失去焦点操作的,所以这里提供了一个 forceHandleFocus 参数,如果你对方法内部对焦点的处理不满意,你可以使用它来强制指定焦点的处理方式。

三、最后

好了,上述便是该库的更新内容, 惯例附上 GitHub 地址: github.com/LinXunFeng/… ,如果接入上有什么问题,可以在链接中查看 demo 演示代码。

开源不易,如果你也觉得这个库好用,请不吝给个 Star 👍 ,并多多支持!

本篇到此结束,感谢大家的支持,我们下次再见! 👋

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有 iOS 技术,还有 AndroidFlutterPython 等文章, 可能有你想要了解的技能知识点哦~