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

Flutter底部导航页面点击时无法重新加载问题求助

问题:底部导航切换时页面API未重新加载,初始化时全部调用

我有一个带BottomNavigationBar的MainPage,期望每次点击底部导航项时,对应页面能重新调用API加载数据。但实际情况是,所有底部导航页面的API在MainPage初始化时就全部触发,而非点击对应导航项时才执行。

现有代码

class MainPage extends StatefulWidget{
  @override
  _MainPageState createState() => new _MainPageState();
}

class _MainPageState extends State<MainPage>{
  ListQueue<int> _navigationQueue = ListQueue();
  int _selectedIndex = 0;
  int counter = Constant.CART_COUNT;

  List<GlobalKey<NavigatorState>> _navigatorKeys = [
    GlobalKey<NavigatorState>(),
    GlobalKey<NavigatorState>(),
    GlobalKey<NavigatorState>(),
    GlobalKey<NavigatorState>()
  ];
  Future<void> secureScreen() async {
    await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
  }

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

  @override
  Widget build(BuildContext context) {

    return WillPopScope(

      onWillPop: () async {
        if (_navigationQueue.isEmpty) return true;
        setState(() {
          _navigationQueue.removeLast();
          int position = _navigationQueue.isEmpty ? 0 : _navigationQueue.last;
          _selectedIndex = position;
        });
        return false;
      },
      child: Scaffold(
        backgroundColor: Colors.white,
        bottomNavigationBar: BottomNavigationBar(
          type: BottomNavigationBarType.fixed,
          currentIndex: _selectedIndex,
          selectedItemColor: Colors.blueAccent,
          showSelectedLabels: true,
          showUnselectedLabels: false,
          items: [
            BottomNavigationBarItem(
              icon: Icon(
                FontAwesome.home,
                color: Colors.grey,
              ),
              label: 'Home',
              activeIcon: Icon(
                FontAwesome.home,
                color: Colors.blueAccent,
              ),
            ),
            BottomNavigationBarItem(
              icon: Icon(
                FontAwesome.product_hunt,
                color: Colors.grey,
              ),
              label: 'Products',
              activeIcon: Icon(
                FontAwesome.product_hunt,
                color: Colors.blueAccent,
              ),
            ),
            BottomNavigationBarItem(
              icon: Icon(
                FontAwesome.users,
                color: Colors.grey,
              ),
              label: 'Customers',
              activeIcon: Icon(
                FontAwesome.users,
                color: Colors.blueAccent,
              ),
            ),
            BottomNavigationBarItem(
              icon: Icon(
                FontAwesome.search_plus,
                color: Colors.grey,
              ),
              label: 'Order Details',
              activeIcon: Icon(
                FontAwesome.users,
                color: Colors.blueAccent,
              ),
            ),
          ],
          onTap: (index) {
            if(_selectedIndex == _selectedIndex){
              _navigationQueue.removeWhere((element) => element == index);
              _navigationQueue.addLast(index);
              setState(() {
                this._selectedIndex = index;
              });
            }
          },
        ),
        body: Stack(
          children: [
            _buildOffstageNavigator(0),
            _buildOffstageNavigator(1),
            _buildOffstageNavigator(2),
            _buildOffstageNavigator(3)
          ],
        ),
      ),
    );
  }


  Map<String, WidgetBuilder> _routeBuilders(BuildContext context, int index) {
    return {
      '/': (context) {
        return [
          HomePage(),
          ProductSearchPage(),
          CustomerPage(),
          OrdersPage()
        ].elementAt(index);
      },
    };
  }

  Widget _buildOffstageNavigator(int index) {
    var routeBuilders = _routeBuilders(context, index);

    return Offstage(
      offstage: _selectedIndex != index,
      child: Navigator(
        key: _navigatorKeys[index],
        onGenerateRoute: (routeSettings) {
          return MaterialPageRoute(
            builder: (context) => routeBuilders[routeSettings.name]!(context),
          );
        },
      ),
    );
  }
}

原因分析

当前采用Stack+Offstage的实现方式,所有页面的Navigator及子页面实例会在MainPage初始化时全部创建,导致页面的initState或API初始化逻辑一次性执行完毕,和页面是否显示无关。

解决方案

方案1:仅渲染当前选中页面,切换时重建

修改body部分,不再嵌套所有页面的Navigator,只渲染当前选中的页面,通过setState触发页面重建:

  1. 修改build方法中的body:
body: Navigator(
  key: _navigatorKeys[_selectedIndex],
  onGenerateRoute: (routeSettings) {
    return MaterialPageRoute(
      builder: (context) => _routeBuilders(context, _selectedIndex)[routeSettings.name]!(context),
    );
  },
),
  1. 修正onTap逻辑(原代码存在条件判断错误):
onTap: (index) {
  _navigationQueue.removeWhere((element) => element == index);
  _navigationQueue.addLast(index);
  setState(() {
    _selectedIndex = index;
  });
},

每次切换导航项时,setState会触发页面重建,新的页面实例会被创建,其内部的API加载逻辑会重新执行。

方案2:给子页面添加刷新方法,切换时主动调用

如果需要保留页面状态(不重建页面),可以给子页面统一添加刷新方法,在导航切换时主动调用:

  1. 定义抽象类规范刷新方法:
abstract class RefreshablePage extends StatefulWidget {
  const RefreshablePage({super.key});

  void refreshData();
}
  1. 子页面实现该类并封装API加载逻辑:
class HomePage extends RefreshablePage {
  late HomePageState _state;

  @override
  HomePageState createState() => _state = HomePageState();

  @override
  void refreshData() {
    _state.fetchData();
  }
}

class HomePageState extends State<HomePage> {
  @override
  void initState() {
    super.initState();
    fetchData();
  }

  void fetchData() {
    // 这里写API调用逻辑
  }
}
  1. 在MainPage中保存子页面实例,并在onTap中调用刷新:
List<RefreshablePage>? _pages;

@override
void initState() {
  super.initState();
  secureScreen();
  _pages = [
    HomePage(),
    ProductSearchPage(),
    CustomerPage(),
    OrdersPage()
  ];
}

// 修改_routeBuilders返回保存的实例
Map<String, WidgetBuilder> _routeBuilders(BuildContext context, int index) {
  return {
    '/': (context) => _pages![index],
  };
}

// 修改onTap逻辑
onTap: (index) {
  if (_selectedIndex != index) {
    _navigationQueue.removeWhere((element) => element == index);
    _navigationQueue.addLast(index);
    setState(() {
      _selectedIndex = index;
    });
    _pages![index].refreshData();
  } else {
    // 点击当前页面时,回到初始页并刷新
    _navigatorKeys[index].currentState?.popUntil((route) => route.isFirst);
    _pages![index].refreshData();
  }
},

方案3:结合AutomaticKeepAliveClientMixin控制刷新时机

如果想保留页面状态,仅在页面被选中时刷新,可以使用AutomaticKeepAliveClientMixin并监听导航切换:

  1. 子页面实现并添加刷新回调:
class HomePage extends StatefulWidget {
  const HomePage({super.key});

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

class HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  void onPageSelected() {
    fetchData();
  }

  void fetchData() {
    // API调用逻辑
  }
}
  1. 在MainPage的onTap中触发刷新:
onTap: (index) {
  _navigationQueue.removeWhere((element) => element == index);
  _navigationQueue.addLast(index);
  setState(() {
    _selectedIndex = index;
  });
  // 获取对应页面状态并调用刷新
  final state = _navigatorKeys[index].currentState?.context.findAncestorStateOfType<HomePageState>();
  state?.onPageSelected();
  // 其他页面同理,可通过类型判断统一处理
},

内容的提问来源于stack exchange,提问作者Ajith S B

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 21:03:30