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

Flutter BottomNavigationBar复用组件时页面不切换问题排查

Flutter BottomNavigationBar 页面切换不更新问题

我在Flutter中实现了BottomNavigationBar,发现底部导航对应的两个页面组件仅两个参数不同、逻辑高度相似,于是将它们合并为一个WorkoutListPage,通过底部导航页面传递不同参数。但修改后切换底部导航时页面无法正常更新,通常仅显示初始tab(index=0)对应的内容;而把其中一个tab改为指向不同的FreestyleDDPage组件时,页面则能正常切换。


相关代码如下:

底部导航页面代码

class FreestylePage extends Stateful Widget {
  const FreestylePage({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _FreestylePageState();
  }
}

class _FreestylePageState extends State<FreestylePage> {
  int _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: showCategory(_currentIndex),
      )),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        items: const [
          BottomNavigationBarItem(
              icon: Icon(Icons.looks_one_outlined),
              label: 'Single rope',
              backgroundColor: Color.fromRGBO(204, 16, 138, 1)),
          BottomNavigationBarItem(
              icon: Icon(Icons.looks_two_outlined),
              label: 'Double dutch',
              backgroundColor: Color.fromRGBO(204, 16, 138, 1)),
        ],
        onTap: (index) {
          if (mounted) {
            setState(() {
              _currentIndex = index;
            });
          }
        },
      ),
    );
  }

  showCategory(index) {
    if (index == 0) {
      return [
        WorkoutListPage(categoryIndex: 2, subCategories: Utils.srfDropdown)
      ];
    } else {
      return [
        WorkoutListPage(categoryIndex: 3, subCategories: Utils.ddfDropdown)
      ];
    }
  }
}

WorkoutListPage代码

class WorkoutListPage extends Stateful Widget {
  final int categoryIndex;
  final List<String> subCategories;
  const WorkoutListPage(
      {Key? key, required this.categoryIndex, required this.subCategories})
      : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _WorkoutListPageState();
  }
}

class _WorkoutListPageState extends State<WorkoutListPage> {
  bool isLoading = true;
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) =>
      FutureBuilder<List<Map<String, dynamic>>>(
        future: MyCard.getData(widget.categoryIndex, widget.subCategories)!
            .whenComplete(() => setState(() {
                  isLoading = false;
                })),
        builder: ((context, snapshot) {
          if (snapshot.hasData && snapshot.data!.isNotEmpty) {
            return FutureBuilder<List<MyCard>>(
                future: MyCard.readData(snapshot.data),
                builder: (context, cards) {
                  if (cards.hasData) {
                    final card = cards.data!;
                    return Expanded(
                      child: ListView.builder(
                        padding: const EdgeInsets.all(16),
                        itemCount: card.length,
                        itemBuilder: (context, index) {
                          return MyCard.buildCard(card[index], context);
                        },
                      ),
                    );
                  } else {
                    return const Text("No data");
                  }
                });
          } else {
            return isLoading
                ? Column(
                    children: const [CircularProgressIndicator()],
                  )
                : const Text("You do not have any workouts yet");
          }
        }),
      );
}

可正常工作的修改后showCategory函数

showCategory(index) {
    if (index == 0) {
      return [
        WorkoutListPage(categoryIndex: 2, subCategories: Utils.srfDropdown)
      ];
    } else {
      return [const FreestyleDDPage()];
    }
  }

FreestyleDDPage代码

class FreestyleDDPage extends Stateful Widget {
  const FreestyleDDPage({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _FreestyleDDPageState();
  }
}

class _FreestyleDDPageState extends State<FreestyleDDPage> {
  var isLoading = true;

  @override
  Widget build(BuildContext context) =>
      FutureBuilder<List<Map<String, dynamic>>>(
        future: MyCard.getData(3, Utils.ddfDropdown)!
            .whenComplete(() => setState(() {
                  isLoading = false;
                })),
        builder: ((context, snapshot) {
          if (snapshot.hasData && snapshot.data!.isNotEmpty) {
            return FutureBuilder<List<MyCard>>(
                future: MyCard.readData(snapshot.data),
                builder: (context, cards) {
                  if (cards.hasData) {
                    final card = cards.data!;
                    return Expanded(
                      child: ListView.builder(
                        padding: const EdgeInsets.all(16),
                        itemCount: card.length,
                        itemBuilder: (context, index) {
                          return MyCard.buildCard(card[index], context);
                        },
                      ),
                    );
                  } else {
                    return const Text("No data");
                  }
                });
          } else {
            return isLoading
                ? Column(
                    children: const [CircularProgressIndicator()],
                  )
                : const Text("You do not have any workouts yet");
          }
        }),
      );
}

问题原因

Flutter通过Key识别Widget实例是否相同。切换tab时,两个WorkoutListPage属于同一类型且未指定唯一Key,Flutter会判定为同一个Widget,不会重新创建State;而FutureBuilder在initState阶段已执行初始参数的请求,后续参数变化无法触发新的数据请求,导致页面内容不更新。

解决方案

方案1:给每个WorkoutListPage添加唯一Key

让Flutter识别为不同的Widget实例,触发State重建:

showCategory(index) {
  if (index == 0) {
    return [
      WorkoutListPage(
        key: const ValueKey(2),
        categoryIndex: 2, 
        subCategories: Utils.srfDropdown
      )
    ];
  } else {
    return [
      WorkoutListPage(
        key: const ValueKey(3),
        categoryIndex: 3, 
        subCategories: Utils.ddfDropdown
      )
    ];
  }
}

方案2:监听参数变化重新请求数据

在WorkoutListPage的State中重写didUpdateWidget方法,参数变化时重新发起请求:

class _WorkoutListPageState extends State<WorkoutListPage> {
  bool isLoading = true;
  late Future<List<Map<String, dynamic>>> _dataFuture;

  @override
  void initState() {
    super.initState();
    _dataFuture = _fetchData();
  }

  @override
  void didUpdateWidget(covariant WorkoutListPage oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.categoryIndex != widget.categoryIndex || 
        oldWidget.subCategories != widget.subCategories) {
      setState(() {
        isLoading = true;
        _dataFuture = _fetchData();
      });
    }
  }

  Future<List<Map<String, dynamic>>> _fetchData() {
    return MyCard.getData(widget.categoryIndex, widget.subCategories)!
        .whenComplete(() => setState(() {
              isLoading = false;
            }));
  }

  @override
  Widget build(BuildContext context) =>
      FutureBuilder<List<Map<String, dynamic>>>(
        future: _dataFuture,
        // 原有builder逻辑保持不变
      );
}

两种方案均可解决问题,方案1简单直接,方案2适合需要保留State状态的场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 21:50:32