首页 > 解决方案 > Flutter TabBar 和 TabBarView 在动态调整选项卡数量时不同步

问题描述

我有一种情况,我有一个小部件,它可以让我从列表中选择哪些选项卡选项应该显示在另一个小部件中(第二个小部件有一个TabController)。

我正在使用 aChangeNotifier来保持选择哪些选项卡在列表中的状态。

除了我在最后一个选项卡上然后删除它的情况外,这一切都很好 - 在这种情况下它仍然有效,但是TabBar返回到第一个选项卡,而TabBarView返回到第二个选项卡。

在此处输入图像描述

我已经尝试了多种不同的方法来解决这个问题(添加keys到小部件,手动将选项卡控制器索引保存在状态中并在延迟后导航到那里,在调用 a 的顶级小部件中添加回调setState),这些都没有任何效果.

这是完整的代码 - 我试图使它成为我正在做的最小版本:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Tab Refresh Issue Demo',
      home: Scaffold(body:
        ChangeNotifierProvider<CurrenLTabsProvider>(
          create: (_) => CurrenLTabsProvider(),
          child: Consumer<CurrenLTabsProvider>(
          builder: (context, tp, child) =>
            Row(
              children: [
                const SizedBox( 
                  child: TabSelectionWidget(),
                  width: 200,
                  height: 1000,
                ),
                SizedBox( 
                  child: TabWidget(tp.availableTabItems, tp._selectedTabIds), 
                  width: 800,
                  height: 1000,
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class CurrenLTabsProvider extends ChangeNotifier {
  
  List<MyTabItem> availableTabItems = [
    MyTabItem(1, 'Tab 1', const Text('Content for Tab 1')),
    MyTabItem(2, 'Tab 2', const Text('Content for Tab 2')),
    MyTabItem(3, 'Tab 3', const Text('Content for Tab 3')),
   // MyTabItem(4, 'Tab 4', const Text('Content for Tab 4')),
   // MyTabItem(5, 'Tab 5', const Text('Content for Tab 5')),
  ];

  List<int> _selectedTabIds = [];

  int currentTabIndex = 0;

  set selectedTabs(List<int> ids) {
    _selectedTabIds = ids;
    notifyListeners();
  }

  List<int> get selectedTabs => _selectedTabIds;

  void doNotifyListeners() {
    notifyListeners();
  }
}


class MyTabItem {
  final int id;
  final String title;
  final Widget widget;
  MyTabItem(this.id, this.title, this.widget);
}



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

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

class _TabSelectionWidgetState extends State<TabSelectionWidget> {

  @override
  Widget build(BuildContext context) {

    return Consumer<CurrenLTabsProvider>(
      builder: (context, tabsProvider, child) {
        return Column(
          children: [
            Expanded(
              child: ListView.builder(
                itemCount: tabsProvider.availableTabItems.length,
                itemBuilder: (context, index) {
                  final item = tabsProvider.availableTabItems[index];
                  return ListTile(
                    title: Text(item.title),
                    leading: Checkbox(
                      value: tabsProvider.selectedTabs.contains(item.id),
                      onChanged: (value) {
                        if (value==true) {
                          setState(() {
                            tabsProvider.selectedTabs.add(item.id);
                            tabsProvider.doNotifyListeners();
                          });
                        } else {
                          setState(() {
                            tabsProvider.selectedTabs.remove(item.id);
                            tabsProvider.doNotifyListeners();
                          });
                        }
                      },
                    ),
                  );
                },
              ),
            ),
          ],
        );
      }
    );

  }

}


class TabWidget extends StatefulWidget {
  const TabWidget(this.allItems, this.selectedTabs, {Key? key}) : super(key: key);

  final List<MyTabItem> allItems;
  final List<int> selectedTabs;

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

class _TabWidgetState extends State<TabWidget>  with TickerProviderStateMixin {

  late TabController _tabController;
  @override
  void initState() {
    _tabController = TabController(length: widget.selectedTabs.length, vsync: this);
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    
    if (widget.selectedTabs.isEmpty) {
      return Container(
        padding: const EdgeInsets.all(20),
        child: const Text("Select some tabs to be available."),
      );
    } // else .. 

    // re-initialise here, so changes made in other widgets are picked up when the widget is rebuilt
    _tabController = TabController(length: widget.selectedTabs.length, vsync: this);


    var tabs = <Widget>[];
    List<Widget> tabBody = [];
    // loop through all available tabs
    for (var i = 0; i < widget.allItems.length; i++) {
      // if it is selected, then show it
      if (widget.selectedTabs.contains(widget.allItems[i].id)) {
        tabs.add( Tab(text: widget.allItems[i].title) );
        tabBody.add( widget.allItems[i].widget );
      }
    }

    return  Column(
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
          TabBar(
              labelColor: Colors.black,
              unselectedLabelColor: Colors.black54,
              tabs: tabs,
              controller: _tabController,
              indicatorSize: TabBarIndicatorSize.tab,
          ),
          Expanded(
            child: TabBarView(
              children: tabBody,
              controller: _tabController,
            ),
          ),
      ]
    );

  }
}

为什么TabBar重置到第一个条目,而TabBarView重置到第二个条目?
我能做些什么来修复它,以便它们都重置为第一个条目?

标签: fluttertabsstate

解决方案


提供UniqueKey()TabWidget()。它解决了这个代码片段的问题。它会像

         TabWidget(
                    tp.availableTabItems,
                    tp._selectedTabIds,
                    key: UniqueKey(),
                  ),

在此处输入图像描述


推荐阅读