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

Flutter中TextFormField点击后键盘弹出立即消失问题求助

聊天模块TextField点击后键盘弹出即消失,且initState重复调用问题

问题描述

开发聊天模块时,顶部用ListView展示对话内容,底部使用TextField发送消息。遇到以下问题:

  • 点击TextField时,键盘弹出后立刻消失
  • 点击TextField时initState会被重新调用(已通过打印日志确认),该方法中包含Firestore数据流的监听
  • 已尝试网络及ChatGPT提供的多种解决方案,均无效

关键观察

注释掉ListView.Builder所在的Expanded代码段后:

// Expanded(
//   child: _buildChatSection(),
// ),

点击TextField仍然出现键盘弹出即消失的情况,说明问题根源在TextField本身。

完整代码

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '../../constants/constant.dart';
import '../../controller/controller.dart';
import '../../models/feed_model.dart';
import '../../service/FirebaseService/form_service.dart';

class FormPage extends StatefulWidget {
  FormPage({super.key});

  @override
  State<FormPage> createState() => _FormPageState();
}

class _FormPageState extends State<FormPage> {
  final messagesCollection = FirebaseFirestore.instance.collection('Form');
  StreamSubscription<QuerySnapshot>? _streamSubscription;
  List<DocumentSnapshot> messages = [];

  FormService fs = FormService();

  TextEditingController textController = TextEditingController();

  final FocusNode focus = FocusNode();
  @override
  void initState() {
    super.initState();
    _streamSubscription = messagesCollection
        .orderBy('createdDateTime')
        .snapshots()
        .listen((QuerySnapshot snapshot) {
      if (mounted) {
        setState(() {
          messages = snapshot.docs;
        });
      }
    });
  }

  Widget _buildChatSection() {
    return messages.isEmpty
        ? Center(
            child: text("No messages yet...", fontSize: Get.height * 0.03),
          )
        : Padding(
            padding: const EdgeInsets.symmetric(horizontal: 20.0),
            child: ListView.builder(
              itemCount: messages.length,
              itemBuilder: (BuildContext context, int index) {
                FeedModel model = fs.getFeedModel(messages[index]);
                bool isCurrentUser = model.uid == Controller.uid;
                return Padding(
                  padding: const EdgeInsets.only(top: 10.0),
                  child: Align(
                    alignment: isCurrentUser
                        ? Alignment.centerRight
                        : Alignment.centerLeft,
                    child: Container(
                        decoration: BoxDecoration(
                          border: Border.all(
                            color: isCurrentUser
                                ? accentColor
                                : primarySubtitleColor,
                          ),
                          borderRadius: BorderRadius.circular(20),
                        ),
                        child: Padding(
                          padding: const EdgeInsets.all(10.0),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.end,
                            children: [
                              text('~${model.name}'),
                              text(model.body!)
                            ],
                          ),
                        )),
                  ),
                );
              },
            ),
          );
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Expanded(
              child: _buildChatSection(),
            ),
            TextField(
              controller: textController,
              focusNode: focus,
              decoration: InputDecoration(
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(30),
                  borderSide: BorderSide(
                    color: accentColor,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

调试控制台输出

D/InputMethodManager(26484): showSoftInput() view=io.flutter.embedding.android.FlutterView{2a1a128 VFE...... .F...... 0,0-1080,2268 #1 aid=1073741824} flags=0 reason=SHOW_SOFT_INPUT
W/OnBackInvokedCallback(26484): OnBackInvokedCallback is not enabled for the application.
W/OnBackInvokedCallback(26484): Set 'android:enableOnBackInvokedCallback="true"' in the application manifest.
D/InsetsController(26484): show(ime(), fromIme=true)

解决方案

1. 正确管理资源生命周期

当前代码未释放FocusNode、TextEditingController和StreamSubscription,可能导致焦点异常。添加dispose方法:

@override
void dispose() {
  focus.dispose();
  textController.dispose();
  _streamSubscription?.cancel();
  super.dispose();
}

2. 避免Widget重复重建

initState重复调用说明FormPage被频繁重建。检查父Widget是否在build方法中直接创建FormPage实例(如FormPage()),改为将FormPage实例保存在父Widget的状态中,或使用const构造(如果允许)。

3. 手动强制请求焦点

在TextField的onTap方法中主动请求焦点,避免焦点丢失:

TextField(
  controller: textController,
  focusNode: focus,
  onTap: () {
    if (!focus.hasFocus) {
      focus.requestFocus();
    }
  },
  decoration: InputDecoration(
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(30),
      borderSide: BorderSide(
        color: accentColor,
      ),
    ),
  ),
)

4. 改用StreamBuilder管理Firestore数据流

替换手动订阅和setState,使用StreamBuilder自动监听数据流,减少不必要的setState调用:

Widget _buildChatSection() {
  return StreamBuilder<QuerySnapshot>(
    stream: messagesCollection.orderBy('createdDateTime').snapshots(),
    builder: (context, snapshot) {
      if (snapshot.hasError) {
        return Center(child: text('加载出错'));
      }
      if (!snapshot.hasData) {
        return Center(child: text('No messages yet...', fontSize: Get.height * 0.03));
      }
      final messages = snapshot.data!.docs;
      return Padding(
        padding: const EdgeInsets.symmetric(horizontal: 20.0),
        child: ListView.builder(
          itemCount: messages.length,
          itemBuilder: (BuildContext context, int index) {
            FeedModel model = fs.getFeedModel(messages[index]);
            bool isCurrentUser = model.uid == Controller.uid;
            return Padding(
              padding: const EdgeInsets.only(top: 10.0),
              child: Align(
                alignment: isCurrentUser
                    ? Alignment.centerRight
                    : Alignment.centerLeft,
                child: Container(
                    decoration: BoxDecoration(
                      border: Border.all(
                        color: isCurrentUser
                            ? accentColor
                            : primarySubtitleColor,
                      ),
                      borderRadius: BorderRadius.circular(20),
                    ),
                    child: Padding(
                      padding: const EdgeInsets.all(10.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.end,
                        children: [
                          text('~${model.name}'),
                          text(model.body!)
                        ],
                      ),
                    )),
              ),
            );
          },
        ),
      );
    },
  );
}

同时删除initState中的手动订阅代码,无需再管理_streamSubscription和messages列表。

5. 配置AndroidManifest解决回调警告

按调试日志提示,在AndroidManifest.xml的application标签中添加配置,消除潜在交互异常:

android:enableOnBackInvokedCallback="true"

内容的提问来源于stack exchange,提问作者Shashank Deepak

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 14:24:57