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

Flutter TextFormField异常:平板模拟器中键盘弹出后立即消失

Flutter登录界面部分平板模拟器键盘弹出后立即消失问题

问题描述

开发登录界面时,使用TextFormField实现邮箱和密码输入功能,在部分平板模拟器中点击输入框后键盘会立即消失,但真机及部分模拟器运行正常,另外三款模拟器存在此问题。

解决方案

1. 用Form组件包裹输入框

代码中定义了_formKey但未使用Form组件管理输入框,TextFormField在Form容器内可更稳定地维护状态与焦点,避免因状态异常丢失焦点:

// 在build方法中,将emailField和passwordField包裹在Form内
Form(
  key: _formKey,
  child: Column(
    children: [
      emailField,
      SizedBox(height: screenHeight * 0.02),
      passwordField,
    ],
  ),
)

2. 移除控制器的static修饰符

静态控制器易引发跨实例状态冲突,改为在State类内初始化并在销毁时释放:

// 替换原静态控制器
final _emailController = TextEditingController();
final _passwordController = TextEditingController();

// 在dispose方法中释放资源
@override
void dispose() {
  _emailController.dispose();
  _passwordController.dispose();
  super.dispose();
}

3. 修复屏幕适配函数问题

代码中调用的sizeHintText()未提供实现,若该函数的计算逻辑在键盘弹出时触发布局重建,会导致输入框丢失焦点。建议确保该函数返回值稳定,或直接使用基于MediaQuery的可靠计算方式。

4. 调整Scaffold的resizeToAvoidBottomInset属性

部分场景下该属性的自动调整会引发布局波动,尝试设置为false测试:

Scaffold(
  resizeToAvoidBottomInset: false,
  // 其他代码
)

5. 手动管理焦点

为输入框添加FocusNode,手动控制焦点请求,避免焦点被意外移除:

// 在State类中定义FocusNode
final _emailFocusNode = FocusNode();
final _passwordFocusNode = FocusNode();

// 在dispose中释放
@override
void dispose() {
  _emailFocusNode.dispose();
  _passwordFocusNode.dispose();
  super.dispose();
}

// 给TextFormField绑定focusNode
final emailField = TextFormField(
  focusNode: _emailFocusNode,
  // 其他属性
);

final passwordField = TextFormField(
  focusNode: _passwordFocusNode,
  // 其他属性
);

6. 检查模拟器配置

  • 关闭模拟器的硬件键盘选项(模拟器设置 -> 键盘 -> 取消"Hardware keyboard present");
  • 更新模拟器系统镜像或创建新的模拟器实例测试。

相关代码

导入代码

import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:prcarpolimi/Internet/NetworkCheck.dart';
import 'package:prcarpolimi/auth/signUp.dart';
import 'package:prcarpolimi/auth/forgot_password.dart';
import 'package:prcarpolimi/models/marker_to_pass.dart';
import 'package:prcarpolimi/models/userModel.dart';
import 'package:prcarpolimi/homepage.dart';
import 'package:prcarpolimi/services/services.dart';
import '../models/static_user.dart';
import 'package:intl/intl.dart'; 

实现代码

final Service loginService;

const Login({Key? key, required this.loginService}) : super(key: key);

@override
State<Login> createState() => _LoginState();
}

class _LoginState extends State<Login> {
  UserModel userModel = UserModel();
  static final _emailController = TextEditingController();
  static final _passwordController = TextEditingController();
  static final _formKey = GlobalKey<FormState>();
  bool from = true;

  Future<User?> loginUsingEmailPassword(
      {required String email,
      required String password,
      required BuildContext context}) async {
    User? user;

    try {
      user = await widget.loginService.signInWithemailandpass(email, password);
    } on FirebaseAuthException catch (e) {
      if (e.code == "user-not-found") {}
      Fluttertoast.showToast(
          msg: 'Login failed :( wrong email or password', fontSize: 20);
      return null;
    }

    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (context) => const Center(child: CircularProgressIndicator()),
    );
    return user;
  }

  _finishReservation(User user) async {
    var data = await widget.loginService
        .firebasefirestore()
        .collection('users')
        .doc(user.uid)
        .collection('booking-out')
        .get();

    if (data.docs.isNotEmpty) {
      for (var bookOut in data.docs) {
        String data = bookOut.data()['date'];
        final splitted = data.split('-');
        String finalDate = splitted[1];
        DateTime dayEnd = DateFormat("dd/MM/yyyy").parse(finalDate);
        if (dayEnd.compareTo(DateTime.now()) < 0) {
          await widget.loginService
              .firebasefirestore()
              .collection('users')
              .doc(user.uid)
              .collection('booking-out')
              .doc(bookOut.data()['bookingId'])
              .update({'status': 'f'});
        }
      }
    }

    var data2 = await widget.loginService
        .firebasefirestore()
        .collection('users')
        .doc(user.uid)
        .collection('cars')
        .get();

    if (data2.docs.isNotEmpty) {
      for (var car in data.docs) {
        await widget.loginService
            .firebasefirestore()
            .collection('users')
            .doc(car.data()['uid'])
            .collection('cars')
            .doc(car.data()['cid'])
            .collection('booking-in')
            .get()
            .then((ds) async {
          if (ds.docs.isNotEmpty) {
            for (var book in ds.docs) {
              String data = book.data()['date'];
              final splitted = data.split('-');
              String finalDate = splitted[1];
              DateTime dayEnd = DateFormat("dd/MM/yyyy").parse(finalDate);
              if (dayEnd.compareTo(DateTime.now()) < 0) {
                await widget.loginService
                    .firebasefirestore()
                    .collection('users')
                    .doc(user.uid)
                    .collection('cars')
                    .doc(book.data()['cid'])
                    .collection('booking-in')
                    .doc(book.data()['bookingId'])
                    .update({'status': 'f'});
              }
            }
          }
        });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    final screenHeight = MediaQuery.of(context).size.height;
    final screenWidth = MediaQuery.of(context).size.width;
    final screenText = MediaQuery.of(context).textScaleFactor;

    final emailField = TextFormField(
        key: const ValueKey(1),
        autofocus: false,
        style: TextStyle(fontSize: sizeHintText()),
        controller: _emailController,
        keyboardType: TextInputType.emailAddress,
        onSaved: (value) {
          _emailController.text = value!;
        },
        textInputAction: TextInputAction.next,
        decoration: InputDecoration(
            prefixIcon: Icon(Icons.mail, size: sizeHintText()),
            contentPadding: EdgeInsets.fromLTRB(screenWidth * 0.02,
                screenHeight * 0.015, screenWidth * 0.02, screenHeight * 0.015),
            hintText: "Email",
            hintStyle: TextStyle(fontSize: sizeHintText()),
            border:
                OutlineInputBorder(borderRadius: BorderRadius.circular(10))));

    final passwordField = TextFormField(
        key: const ValueKey(2),
        autofocus: false,
        style: TextStyle(fontSize: sizeHintText()),
        controller: _passwordController,
        obscureText: true,
        onSaved: (value) {
          _passwordController.text = value!;
        },
        textInputAction: TextInputAction.done,
        decoration: InputDecoration(
            prefixIcon: Icon(Icons.vpn_key, size: sizeHintText()),
            contentPadding: EdgeInsets.fromLTRB(screenWidth * 0.02,
                screenHeight * 0.015, screenWidth * 0.02, screenHeight * 0.015),
            hintText: "Password",
            hintStyle: TextStyle(fontSize: sizeHintText()),
            border:
                OutlineInputBorder(borderRadius: BorderRadius.circular(10))));

       return Scaffold(
            resizeToAvoidBottomInset: true,
            backgroundColor: Colors.white,
            body: SingleChildScrollView(
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                  SizedBox(height: screenHeight * 0.1),
                  Text("Welcome to PrCar!",
                      textAlign: TextAlign.center,
                      style: TextStyle(
                          color: Colors.black,
                          fontSize: screenText * 45,
                          fontWeight: FontWeight.bold)),
                  SizedBox(height: screenHeight * 0.05),
                  SizedBox(
                      height: screenHeight * 0.2,
                      child: Image.asset("assets/prcarlogo.png",
                          fit: BoxFit.contain)),
                  SizedBox(height: screenHeight * 0.05),
                  emailField,
                  SizedBox(height: screenHeight * 0.02),
                  passwordField,
                  SizedBox(height: screenHeight * 0.05),
                  Row(children: [
                    SizedBox(width: screenWidth * 0.06),
                    GestureDetector(
                        child: Text('Forgot password?',
                            style: TextStyle(
                                decoration: TextDecoration.underline,
                                color: Colors.redAccent,
                                fontSize: screenText * 16)),
                        onTap: () =>
                            Navigator.of(context).push(MaterialPageRoute(
                              builder: (context) => const ForgotPasswordPage(),
                            ))),
                    Text(' or ', style: TextStyle(fontSize: screenText * 12)),
                    GestureDetector(
                        key: Key("New Account"),
                        child: Text("Don't have an account?",
                            textAlign: TextAlign.start,
                            style: TextStyle(
                                decoration: TextDecoration.underline,
                                color: Colors.redAccent,
                                fontSize: screenText * 16)),
                        onTap: () => Navigator.push(
                            context,
                            MaterialPageRoute(
                                builder: (context) => const SignUp())))
                  ]),
                  SizedBox(height: screenHeight * 0.06),
                  Row(mainAxisAlignment: MainAxisAlignment.center, children: [
                    Container(
                        key: const Key("clickButtom"),
                        height: screenHeight * 0.07,
                        width: screenWidth * 0.85,
                        child: MaterialButton(
                            color: Colors.redAccent,
                            onPressed: () async {
                              if (await NetworkCheck().check()) {
                                User? user = await loginUsingEmailPassword(
                                    email: _emailController.text,
                                    password: _passwordController.text,
                                    context: context);
                                if (user != null) {
                                  await widget.loginService
                                      .firebasefirestore()
                                      .collection('users')
                                      .doc(user.uid)
                                      .get()
                                      .then((ds) {
                                    userModel = UserModel.fromMap(ds);
                                    StaticUser.email = userModel.email!;
                                    StaticUser.uid = userModel.uid!;
                                    StaticUser.firstName = userModel.firstName!;
                                    StaticUser.secondName =
                                        userModel.secondName!;
                                    PassMarker.from = true;
                                    _finishReservation(user);
                                    Navigator.push(
                                        context,
                                        MaterialPageRoute(
                                            builder: (context) => HomePage(
                                                homePageService: Service())));
                                  });
                                }
                              } else {
                                Fluttertoast.showToast(
                                    msg: 'No internet connection',
                                    fontSize: 20);
                              }
                            },
                            child: Text("Login",
                                style: TextStyle(
                                    color: Colors.white,
                                    fontSize: screenText * 25))),
                        decoration: BoxDecoration(
                            color: Colors.deepPurple,
                            borderRadius: BorderRadius.circular(12),
                            boxShadow: const [
                              BoxShadow(
                                  color: Colors.deepPurple,
                                  spreadRadius: 6,
                                  blurRadius: 3)
                            ]))
                  ])
                ])));
  }
}

内容的提问来源于stack exchange,提问作者Francesco Marin Vargas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 22:36:33