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

Flutter中setState切换页面时键盘自动弹出问题

Flutter AnimatedIndexedStack切换页面后键盘自动弹出问题

问题描述

我用Flutter开发移动应用,主页采用AnimatedIndexedStack实现页面切换,只需修改index即可完成切换。应用初始运行时页面切换无异常,但当切换到包含TextField的页面并编辑内容,关闭键盘后再切换到任意无TextField的页面时,键盘会自动弹出;此后无论起始和目标页面是什么,只要修改index,键盘都会自动弹出。

相关代码

AnimatedIndexedStack实现

import 'package:flutter/material.dart';

class AnimatedIndexedStack extends StatefulWidget {
  final int index;
  final List<Widget> children;

  const AnimatedIndexedStack({
    Key? key,
    required this.index,
    required this.children,
  }) : super(key: key);

  @override
  _AnimatedIndexedStackState createState() => _AnimatedIndexedStackState();
}

class _AnimatedIndexedStackState extends State<AnimatedIndexedStack>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  int _index = 0;

  @override
  void initState() {
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 250),
    );
    _animation = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.ease,
      ),
    );

    _index = widget.index;
    _controller.forward();
    super.initState();
  }

  @override
  void didUpdateWidget(AnimatedIndexedStack oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.index != _index) {
      _controller.reverse().then((_) {
        setState(() => _index = widget.index);
        _controller.forward();
      });
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return Opacity(
          opacity: _controller.value,
          child: child,
        );
      },
      child: IndexedStack(
        index: _index,
        children: widget.children,
      ),
    );
  }
}

主页代码

class Homepage extends StatefulWidget {
  const Homepage({Key? key}) : super(key: key);

  @override
  _HomepageState createState() => _HomepageState();
}

class _HomepageState extends State<Homepage> {
  var _currentIndex = 0;
  final _homeIndex = 0;
  final _titles = ['Home', 'New Booking', 'Trips', 'Payment History', 'Support', 'Profile'];
  final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
  var _bookingStep = 0;

  @override
  void initState() {
    _currentIndex = _homeIndex;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async => _onWillPop(),
      child: Scaffold(
        key: scaffoldKey,
        extendBody: true,
        floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
        drawer: AppDrawer(selectedIndex: _currentIndex, onItemTap: (index) {
          setState(() {
            _currentIndex = index;
          });
          scaffoldKey.currentState!.closeDrawer();
        },),
        body: SafeArea(
          bottom: false,
          child: AnimatedIndexedStack(
            index: _currentIndex,
            children: [
              const BookingsScreen(),
              NewBookingScreen(onExit: () {
                setState(() {
                  _currentIndex = 0;
                });
              }, bookingStep: _bookingStep, nextStep: () {
                setState(() {
                  _bookingStep = _bookingStep + 1;
                });
              },),
              const Trips(),
              const PaymentHistoryScreen(),
              SupportScreen(),
              const ProfileScreen()
            ],
          )
        ),
      ),
    );
  }
}

问题原因

IndexedStack的核心特性是保留所有子组件的状态,包括TextField的焦点状态。当你在含TextField的页面编辑后,即使手动关闭键盘,TextField可能仍持有焦点;切换页面时,IndexedStack只是隐藏了原页面,并未释放其状态,在AnimatedIndexedStack的切换动画完成后,原页面的TextField焦点被重新激活,导致键盘自动弹出。

解决方案

最直接有效的方法是在切换页面之前主动清除所有焦点,关闭键盘:

  1. 修改主页中所有切换_currentIndex的逻辑,添加失焦代码:

    • 抽屉点击事件:
      drawer: AppDrawer(selectedIndex: _currentIndex, onItemTap: (index) {
        // 强制关闭键盘,清除焦点
        FocusManager.instance.primaryFocus?.unfocus();
        setState(() {
          _currentIndex = index;
        });
        scaffoldKey.currentState!.closeDrawer();
      },),
      
    • NewBookingScreen的退出回调:
      NewBookingScreen(onExit: () {
        FocusManager.instance.primaryFocus?.unfocus();
        setState(() {
          _currentIndex = 0;
        });
      }, bookingStep: _bookingStep, nextStep: () {
        setState(() {
          _bookingStep = _bookingStep + 1;
        });
      },),
      
  2. (可选)在含TextField的页面中,添加焦点监听,确保页面销毁时失焦:
    以NewBookingScreen为例:

    class NewBookingScreen extends StatefulWidget {
      // ... 原有代码
    }
    
    class _NewBookingScreenState extends State<NewBookingScreen> {
      late FocusNode _textFieldFocusNode;
    
      @override
      void initState() {
        super.initState();
        _textFieldFocusNode = FocusNode();
      }
    
      @override
      void dispose() {
        // 页面销毁时主动失焦
        _textFieldFocusNode.unfocus();
        _textFieldFocusNode.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return TextField(
          focusNode: _textFieldFocusNode,
          // ... 其他配置
        );
      }
    }
    

总结

通过在页面切换前强制清除焦点,就能避免IndexedStack保留的TextField焦点导致键盘自动弹出的问题,同时配合页面销毁时的焦点清理,能进一步优化状态管理。

内容的提问来源于stack exchange,提问作者Danish Ajaib

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 11:20:26