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

Flutter中TextField与键盘之间存在间隙的问题求助

Flutter中TextField与键盘之间存在间隙的问题求助

大家好,我在开发Flutter聊天页面时遇到了一个困扰:当键盘弹出后,输入用的TextField和键盘之间会出现一段莫名的间隙。我试过设置resizeToAvoidBottomInset: false,结果反而更糟——键盘直接把TextField完全盖住了,根本没法正常输入。

有没有朋友能帮我看看问题出在哪里?以下是我的ChatPage完整代码:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:connectivity_app/core/constants/global_data.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../data/data_sources/chat_service.dart';
import '../widgets/chat_bubble.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

class ChatPage extends StatefulWidget {
  final receiverUserName = "Dr.";
  final String receiverUserId = GlobalData().currentPatientDoctorId ?? 'asfsaffas';

  ChatPage({Key? key}) : super(key: key);

  @override
  State<ChatPage> createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {
  final TextEditingController _messageController = TextEditingController();
  final ChatService _chatService = ChatService();
  late Stream<QuerySnapshot> _messageStream;
  late bool isOnline = false;
  late bool _isLoading = true; // Added for loading state
  late String doctorsName = 'Loading...';
  AssetImage senderUserImage = const AssetImage('lib/assets/profile_avatar.png');

  @override
  void initState() {
    super.initState();
    _initializeAsyncData();
  }

  Future<void> _initializeAsyncData() async {
    setState(() {
      _isLoading = true; // Set isLoading to true while fetching data
    });

    _messageStream = _chatService.getMessages(
        widget.receiverUserId ?? 'ASFASFSFA',
        GlobalData().currentPatientId ?? 'ASFSAF');

    ConnectivityResult connectivityResult =
        await Connectivity().checkConnectivity();
    setState(() {
      isOnline = connectivityResult == ConnectivityResult.wifi;
      _isLoading = false; // Set isLoading to false after data is fetched
    });

    final sharedPreferences = await SharedPreferences.getInstance();
    doctorsName = sharedPreferences.getString('lastName') ?? ' ';
    print("Async data initialized: isOnline = $isOnline");
  }

  void sendMessage() async {
    print("Sending message...");
    if (_messageController.text.isNotEmpty) {
      await _chatService.sendMessage(
        widget.receiverUserId,
        _messageController.text,
      );
      _messageController.clear();
    }
  }

  @override
  Widget build(BuildContext context) {
    print("Building ChatPage...");
    print("Current widget properties:");
    print("receiverUserName: ${widget.receiverUserName}");
    print("receiverUserId: ${widget.receiverUserId}");
    print("receiverUserLastName: $doctorsName}");

    return Scaffold(
      appBar: PreferredSize(
        preferredSize: const Size.fromHeight(65),
        child: SafeArea(
          child: AppBar(
            elevation: 1,
            backgroundColor: Colors.white,
            centerTitle: true,
            title: Container(
              padding: const EdgeInsets.only(right: 70),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Container(
                    width: 60,
                    height: 60,
                    decoration: const BoxDecoration(
                      shape: BoxShape.circle,
                      image: DecorationImage(
                        fit: BoxFit.cover,
                        image: AssetImage('lib/assets/doctor.png'),
                      ),
                    ),
                  ),
                  const SizedBox(height: 4),
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        '${widget.receiverUserName} $doctorsName',
                        style: const TextStyle(
                          fontFamily: "SF Pro",
                          fontSize: 20,
                          fontWeight: FontWeight.w500,
                          color: Color(0xff1a1a1a),
                          height: 18 / 15,
                        ),
                      ),
                      Row(
                        mainAxisAlignment: MainAxisAlignment.start,
                        children: [
                          Icon(
                            isOnline ? Icons.circle : Icons.circle,
                            color: isOnline ? Colors.green : Colors.red,
                            size: 15,
                          ),
                          const SizedBox(width: 4),
                          Text(
                            isOnline ? 'Online' : 'Offline',
                            style: TextStyle(
                              color: isOnline ? Colors.green : Colors.red,
                              fontFamily: "SF Pro",
                              fontSize: 11,
                              fontWeight: FontWeight.w400,
                              height: 13 / 11,
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Expanded(
                child: _buildMessageList(),
              ),
              const Divider(
                color: Colors.black12,
                height: 1,
              ),
              _buildMessageInput(),
            ],
          ),
        ),
      ),
    );
  }

  String _formatTimestamp(Timestamp timestamp) {
    DateTime dateTime = timestamp.toDate();
    String formattedTime = DateFormat('HH:mm a').format(dateTime);
    return formattedTime;
  }

  Widget _buildMessageInput() {
    print("Building MessageInput...");
    return SizedBox(
      height: 70,
      child: SizedBox(
        width: MediaQuery.of(context).size.width - 40,
        child: Row(
          children: [
            Expanded(
              child: TextField(
                controller: _messageController,
                decoration: InputDecoration(
                  labelText: 'Message',
                  suffixIcon: IconButton(
                    onPressed: () {},
                    icon: const Icon(Icons.attach_file),
                  ),
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(8.0),
                    borderSide:
                        const BorderSide(color: Colors.grey, width: 1.0),
                  ),
                ),
              ),
            ),
            IconButton(
              onPressed: sendMessage,
              icon: const Icon(Icons.send),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildMessageItem(DocumentSnapshot document) {
    print("Building MessageItem...");
    Map<String, dynamic> data = document.data() as Map<String, dynamic>;
    print("Message data: $data");

    var alignment = (data['senderId'] == GlobalData().currentPatientId)
        ? Alignment.centerRight
        : Alignment.centerLeft;
    var isCurrentUser = (data['senderId'] == GlobalData().currentPatientId);
    var bubbleColor = isCurrentUser ? Colors.blue : Colors.grey;

    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Container(
        alignment: alignment,
        child: Column(
          crossAxisAlignment:
              isCurrentUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
          children: [
            Row(
              mainAxisAlignment: (data['senderId'] == GlobalData().currentPatientId)
                  ? MainAxisAlignment.end
                  : MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: isCurrentUser
                  ? [
                      Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Container(
                            constraints: const BoxConstraints(
                              maxWidth: 280,
                            ),
                            child: ChatBubble(
                              message: data['message'],
                              color: bubbleColor,
                            ),
                          ),
                          Text(
                            _formatTimestamp(data['timestamp']),
                            style: const TextStyle(
                              fontSize: 10,
                              color: Colors.grey,
                            ),
                          ),
                        ],
                      ),
                      Padding(
                        padding: const EdgeInsets.all(10.0),
                        child: SizedBox(
                          width: 54.5,
                          height: 54.5,
                          child: CircleAvatar(
                            backgroundImage: senderUserImage,
                            radius: 27.0,
                          ),
                        ),
                      ),
                    ]
                  : [
                      Padding(
                        padding: const EdgeInsets.all(10.0),
                        child: Container(
                          width: 54.5,
                          height: 54.5,
                          decoration: const BoxDecoration(
                            shape: BoxShape.circle,
                            image: DecorationImage(
                              fit: BoxFit.cover,
                              image: AssetImage('lib/assets/doctor.png'),
                            ),
                          ),
                        ),
                      ),
                      Column(
                        crossAxisAlignment: CrossAxisAlignment.end,
                        children: [
                          Container(
                            constraints: const BoxConstraints(
                              maxWidth: 280,
                            ),
                            child: ChatBubble(
                              message: data['message'],
                              color: bubbleColor,
                            ),
                          ),
                          Text(
                            _formatTimestamp(data['timestamp']),
                            style: const TextStyle(
                              fontSize: 10,
                              color: Colors.grey,
                            ),
                          ),
                        ],
                      ),
                    ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildMessageList() {
    print("Building MessageList...");
    return _isLoading
        ? const Center(
            child: CircularProgressIndicator(
              color: Colors.orangeAccent,
              backgroundColor: Colors.blueGrey,
            ),
          )
        : StreamBuilder(
            stream: _messageStream,
            builder: (context, snapshot) {
              if (snapshot.hasError) {
                print("StreamBuilder error: ${snapshot.error}");
                return const Text('Something went wrong');
              }

              if (snapshot.connectionState == ConnectionState.waiting) {
                print("StreamBuilder loading...");
                return const Center(
                  child: CircularProgressIndicator(
                    color: Colors.orangeAccent,
                    backgroundColor: Colors.blueGrey,
                  ),
                );
              }

              return ListView(
                children: snapshot.data!.docs
                    .map((document) => _buildMessageItem(document))
                    .toList(),
              );
            },
          );
  }
}

我尝试过的方法

  • 设置resizeToAvoidBottomInset: false:结果键盘直接覆盖TextField,无法输入
  • 调整过输入框的padding和margin,但间隙依然存在

可能的解决方向及建议

我帮你梳理了几个可能的问题点和对应的修改建议,你可以逐一尝试:

  1. 简化输入框的嵌套布局
    你当前的_buildMessageInput用了两层嵌套的SizedBox,这种嵌套容器可能会干扰Flutter的键盘布局计算。可以尝试简化这部分代码,用Padding替代嵌套的SizedBox来控制边距:

    Widget _buildMessageInput() {
      print("Building MessageInput...");
      return Padding(
        padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
        child: Row(
          children: [
            Expanded(
              child: TextField(
                controller: _messageController,
                decoration: InputDecoration(
                  labelText: 'Message',
                  suffixIcon: IconButton(
                    onPressed: () {},
                    icon: const Icon(Icons.attach_file),
                  ),
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(8.0),
                    borderSide: const BorderSide(color: Colors.grey, width: 1.0),
                  ),
                ),
              ),
            ),
            const SizedBox(width: 8),
            IconButton(
              onPressed: sendMessage,
              icon: const Icon(Icons.send),
            ),
          ],
        ),
      );
    }
    
  2. 调整SafeArea的设置
    当前body里的SafeArea会默认给底部预留空间,这可能和键盘弹出时的布局冲突。可以尝试给SafeArea添加bottom: false参数,让它不处理底部的安全区域:

    body: SafeArea(
      bottom: false, // 添加这一行
      child: Padding(
        padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0),
        child: Column(
          // ... 原有代码
        ),
      ),
    ),
    
  3. 用SingleChildScrollView包裹body内容
    如果页面布局比较复杂,Flutter的自动布局调整可能会出现偏差。可以把Column放在SingleChildScrollView里,让它能随键盘滚动自动调整位置:

    body: SafeArea(
      child: SingleChildScrollView(
        physics: const AlwaysScrollableScrollPhysics(),
        child: Padding(
          padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              SizedBox(
                height: MediaQuery.of(context).size.height - 150, // 估算高度适配布局
                child: _buildMessageList(),
              ),
              const Divider(color: Colors.black12, height: 1),
              _buildMessageInput(),
            ],
          ),
        ),
      ),
    ),
    
  4. 检查ChatBubble组件的布局
    虽然问题出在输入框,但如果ChatBubble有额外的margin或padding,也可能间接影响整体布局的计算。可以暂时替换成普通的Container测试一下,看间隙是否消失。

你可以先从第一个建议开始尝试,简化输入框的布局,这是最可能解决问题的点。如果还是不行,再依次尝试其他方法。

备注:内容来源于stack exchange,提问作者Andi Alidema

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.21 14:33:12