Flutter:TextField获取焦点后键盘立即消失问题求助
问题:TextField点击后键盘弹出立即消失(ScreenUtil + GoRouter 冲突)
点击TextField准备输入时,弹出的键盘会立即消失。移除ScreenUtil组件后问题解决,将GoRouter替换为自定义路由并移除ScreenUtil也能解决,但需要保留ScreenUtil实现响应式布局。
相关代码
main.dart
import 'package:bloc/bloc.dart'; import 'package:elcampione/presentation/app.dart'; import 'package:elcampione/presentation/bloc_observer.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'data/repositories/repositories.dart'; import 'firebase_options.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); // 禁止屏幕旋转 SystemChrome.setPreferredOrientations( [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); Bloc.observer = AppBlocObserver(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); final authenticationRepository = AuthenticationRepository(); await authenticationRepository.user.first; runApp(App(authenticationRepository: authenticationRepository)); }
app.dart
import 'package:elcampione/blocs/auth/auth_bloc.dart'; import 'package:elcampione/presentation/themes/theme.dart'; import 'package:elcampione/routes/routes.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../data/repositories/repositories.dart'; class App extends StatelessWidget { const App({ super.key, required AuthenticationRepository authenticationRepository, }) : _authenticationRepository = authenticationRepository; final AuthenticationRepository _authenticationRepository; @override Widget build(BuildContext context) { return RepositoryProvider<AuthenticationRepository>( create: (context) => _authenticationRepository, child: BlocProvider( create: (_) => AuthBloc( authenticationRepository: _authenticationRepository, ), child: const AppView(), ), ); } } class AppView extends StatelessWidget { const AppView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final state = context.select((AuthBloc bloc) => bloc.state.status); final isAuthenticated = state == AuthStatus.authenticated; return ScreenUtilInit( builder: (context, child) => MaterialApp.router( debugShowCheckedModeBanner: false, theme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme), darkTheme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme), title: 'El Campione', locale: const Locale('en'), routerConfig: AppRouter.router(isAuthenticated), ), designSize: const Size(390, 844), ); } }
app_route_config.dart
import 'package:elcampione/presentation/screens/screens.dart'; import 'package:elcampione/routes/app_route_constants.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class AppRouter { static GoRouter router(bool isAuthenticated) { GoRouter routes = GoRouter( initialLocation: isAuthenticated ? '/home' : '/signin', routes: [ GoRoute( name: AppRoutesConstants.home, path: '/home', builder: (context, state) => const HomeScreen(), ), GoRoute( name: AppRoutesConstants.signIn, path: '/signin', builder: (context, state) => const SignInScreen(), ), GoRoute( name: AppRoutesConstants.signUp, path: '/signup', builder: (context, state) => const SignUpScreen(), ) ], redirect: (context, state) => !isAuthenticated ? '/signin' : null, ); return routes; } }
解决方案
问题根源在于AppView会随AuthBloc状态变化频繁重建,导致ScreenUtilInit和MaterialApp.router反复初始化,TextField焦点丢失,键盘随之消失。以下是针对性修复方案:
方案1:让GoRouter自行监听Auth状态并处理跳转
修改路由配置,让GoRouter直接监听AuthBloc的状态流,避免AppView因状态变化重建整个MaterialApp:
- 新增
GoRouterRefreshStream类,用于监听Bloc状态流并通知GoRouter刷新:
import 'package:flutter/material.dart'; import 'package:bloc/bloc.dart'; class GoRouterRefreshStream extends ChangeNotifier { GoRouterRefreshStream(Stream<dynamic> stream) { _subscription = stream.listen((_) => notifyListeners()); } late final StreamSubscription<dynamic> _subscription; @override void dispose() { _subscription.cancel(); super.dispose(); } }
- 修改
AppRouter,移除外部传入的isAuthenticated参数,改为内部监听AuthBloc状态:
import 'package:elcampione/blocs/auth/auth_bloc.dart'; import 'package:elcampione/presentation/screens/screens.dart'; import 'package:elcampione/routes/app_route_constants.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; class AppRouter { static GoRouter router(BuildContext context) { return GoRouter( initialLocation: '/', routes: [ GoRoute( name: AppRoutesConstants.home, path: '/home', builder: (context, state) => const HomeScreen(), ), GoRoute( name: AppRoutesConstants.signIn, path: '/signin', builder: (context, state) => const SignInScreen(), ), GoRoute( name: AppRoutesConstants.signUp, path: '/signup', builder: (context, state) => const SignUpScreen(), ) ], redirect: (context, state) { final authStatus = context.read<AuthBloc>().state.status; final isAuthenticated = authStatus == AuthStatus.authenticated; final isOnAuthPage = state.matchedLocation == '/signin' || state.matchedLocation == '/signup'; // 未认证且不在登录/注册页,跳转登录页 if (!isAuthenticated && !isOnAuthPage) { return '/signin'; } // 已认证且在登录/注册页,跳转首页 if (isAuthenticated && isOnAuthPage) { return '/home'; } return null; }, // 监听AuthBloc状态变化,自动刷新路由 refreshListenable: GoRouterRefreshStream(context.read<AuthBloc>().stream), ); } }
- 修改
AppView,移除状态监听逻辑,直接传递context给路由:
class AppView extends StatelessWidget { const AppView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return ScreenUtilInit( builder: (context, child) => MaterialApp.router( debugShowCheckedModeBanner: false, theme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme), darkTheme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme), title: 'El Campione', locale: const Locale('en'), routerConfig: AppRouter.router(context), ), designSize: const Size(390, 844), ); } }
方案2:将ScreenUtilInit移至页面层级
如果不想修改路由逻辑,可以把ScreenUtilInit从AppView移到每个页面的根节点,避免MaterialApp因ScreenUtil重建:
例如在SignInScreen中:
class SignInScreen extends StatelessWidget { const SignInScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return ScreenUtilInit( designSize: const Size(390, 844), builder: (context, child) => Scaffold( // 页面内容 body: ..., ), ); } }
可以封装一个BaseScreen组件统一处理,减少重复代码。
内容的提问来源于stack exchange,提问作者Montassar Zaroui
相关产品推荐
相关产品推荐

