You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter iOS端键盘弹出时ListView.builder不收缩如何显示最新消息

Flutter iOS端键盘弹出时消息列表遮挡最新消息修复

问题表现

  • 基于Flutter开发聊天功能时,iOS端键盘弹出后ListView.builder不会自动收缩适配高度,最新发送的消息被键盘遮挡,该异常在Android端不会出现。

问题复现代码

Scaffold(
  resizeToAvoidBottomInset: true,
  body: Stack(
    children: <Widget>[
      StreamBuilder(
        stream: _chats,
        builder: (context, snapshot) {
          if (!snapshot.hasData) return Container();
          return snapshot.hasData
              ? GestureDetector(
                  onPanDown: (_) {
                    FocusScope.of(context).requestFocus(FocusNode());
                  },
                  child: ListView.builder(
                      shrinkWrap: true,
                      controller: _scrollController,
                      padding: EdgeInsets.only(top: 10, bottom: 100),
                      itemCount: snapshot.data.docs.length,
                      itemBuilder: (context, index) {
                        return MessageWidget(
                          tripId: widget.docId,
                          uid: snapshot.data.docs[index].data()["uid"],
                          messageId: snapshot.data.docs[index].id,
                          message: snapshot.data.docs[index].data()["message"],
                          sender: snapshot.data.docs[index].data()["senderName"],
                          sentByMe: widget.uid ==
                              snapshot.data.docs[index].data()["uid"],
                          mediaFileUrl: snapshot.data.docs[index]
                              .data()["mediaFileUrl"],
                        );
                      }),
                )
              : Container();
        },
      );
    ]
  )
)

效果参考

  • 键盘弹出状态:Keyboard Opened
  • 键盘收起状态:Keyboard Closed

问题根因

  1. Stack布局默认子组件撑满可用空间,不会自动跟随Scaffold的底部安全区(键盘高度)调整子组件布局边界,iOS键盘弹出的布局刷新逻辑和Android存在差异,不会主动触发Stack内列表的高度重算。
  2. ListView.builder写死了bottom: 100的固定内边距,键盘弹出后没有为键盘占用区域预留额外空间,最后一条消息会直接被键盘遮挡。
  3. 键盘弹出后没有主动触发列表滚动到底部的逻辑,列表停留在原位置无法展示最新内容。

修复方案

1. 动态调整列表底部内边距

移除固定的100底部边距,通过MediaQuery实时获取键盘高度,动态计算列表需要预留的底部空间:

ListView.builder(
  shrinkWrap: true,
  controller: _scrollController,
  // 基础预留100是输入栏高度,加上实时键盘高度
  padding: EdgeInsets.only(
    top: 10,
    bottom: 100 + MediaQuery.of(context).viewInsets.bottom,
  ),
  // 其余itemBuilder、itemCount逻辑保持原有代码不变
  // ...
)

2. 监听键盘状态自动滚动到底部

在页面State中添加布局变化监听,键盘弹出/收起布局刷新完成后,自动将列表滚动到最底部:

@override
void initState() {
  super.initState();
  // 注册布局变化监听
  WidgetsBinding.instance.addObserver(this);
}

@override
void didChangeMetrics() {
  super.didChangeMetrics();
  // 等待当前帧布局绘制完成后执行滚动,避免位置计算错误
  WidgetsBinding.instance.addPostFrameCallback((_) {
    if (_scrollController.hasClients) {
      _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
    }
  });
}

@override
void dispose() {
  // 移除监听、释放控制器
  WidgetsBinding.instance.removeObserver(this);
  _scrollController.dispose();
  super.dispose();
}

3. 新消息发送时触发滚动

在发送消息的逻辑中,消息入库成功后追加滚动逻辑,保证新发送的消息始终可见:

// 消息发送成功后执行
WidgetsBinding.instance.addPostFrameCallback((_) {
  if (_scrollController.hasClients) {
    _scrollController.animateTo(
      _scrollController.position.maxScrollExtent,
      duration: const Duration(milliseconds: 200),
      curve: Curves.easeOut,
    );
  }
});

注意事项

不要在Scaffold外层嵌套会拦截底部安全区的组件,不要随意修改resizeToAvoidBottomInset为false,否则MediaQuery无法获取正确的键盘高度。

内容的提问来源于stack exchange,提问作者Edgar J.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 21:09:30