在TabBarView 动态添加页面后删除其中一个页面会导致后面的页面状态错误或删除的页面不正确。出现这种问题是由于创建子页面时没有为子页面设置唯一的key导致的。

 1   void addNewPage() {
 2     _pageCount++;
 3     setState(() {
 4       String title = "页面$_pageCount";
 5       PageContent page = PageContent(data: title, pageId: _pageCount,);
 6       PageData data = PageData(data: title, pageId: _pageCount, content: page);
 7       listPages.add(data);
 8       nowIndex = listPages.length -1;
 9       resetTabController();
10     });
11   }

如上面的代码所示, 在创建PageContent 组件时如果没有指定全局唯一的key, 关闭页面时就会导致后面的页面被再次build或删除错误的页面,正确的代码如下

 1 void addNewPage() {
 2     _pageCount++;
 3     setState(() {
 4       String title = "页面$_pageCount";
 5       PageContent page = PageContent(data: title, pageId: _pageCount, key: ValueKey(title),);
 6       PageData data = PageData(data: title, pageId: _pageCount, content: page);
 7       listPages.add(data);
 8       nowIndex = listPages.length -1;
 9       resetTabController();
10     });
11   }

指定了全局唯一key后在删除子页面,后续页面就可以正常显示。

 flutter TabBarView 动态添加删除页面-LMLPHP

所有代码如下

import 'package:flutter/material.dart';

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

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.white),
        primaryColor: Colors.white,
        scaffoldBackgroundColor: Colors.white,
        dialogBackgroundColor: Colors.white,
        useMaterial3: true,
      ),
      home: const PageMain(),
      /*
      home: ChangeNotifierProvider(
          create: (context) => HomeProvider(),
          builder: (context, child) => const HomePage(),
      ),
      */
    );
  }
}

class PageData {
  final String data;
  final int pageId;
  final Widget content;

  PageData({
    required this.data,
    required this.pageId,
    required this.content,
  });
}

class _StatePageMain extends State<PageMain> with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
  final List<PageData> listPages = <PageData>[];
  int nowIndex = 0;
  int _pageCount = 0;
  TabController? tabController;

  @override
  void initState() {
    super.initState();
    setState(() {
      tabController = TabController(length: listPages.length, vsync: this);
    });
  }

  @override
  bool get wantKeepAlive => true;

  void addNewPage() {
    _pageCount++;
    setState(() {
      String title = "页面$_pageCount";
      PageContent page = PageContent(data: title, pageId: _pageCount, key: ValueKey(title),);
      PageData data = PageData(data: title, pageId: _pageCount, content: page);
      listPages.add(data);
      nowIndex = listPages.length -1;
      resetTabController();
    });
  }

  //选中某个页面
  void onSelectPage(PageData page) {
    //页面已经选中
    int selIndex = 0;
    for (int index = 0; index < listPages.length; index++) {
      PageData item = listPages[index];
      if (item.pageId == page.pageId) {
        selIndex = index;
        break;
      }
    }
    //选中页面没有更改
    if (selIndex == nowIndex) {
      return;
    }
    setState(() {
      nowIndex = selIndex;
      tabController?.animateTo(nowIndex);
    });
  }

  //关闭页面
  void onClosePage(PageData data) {
    int closedIndex = 0;
    for (int index = 0; index < listPages.length; index++) {
      PageData item = listPages[index];
      if (item.pageId == data.pageId) {
        closedIndex = index;
        break;
      }
    }
    setState(() {
      listPages.removeAt(closedIndex);
      if (closedIndex <= nowIndex) {
        nowIndex--;
      }
      if (nowIndex < 0) {
        nowIndex = 0;
      } else if (nowIndex >= listPages.length) {
        nowIndex = listPages.length -1;
      }
      resetTabController();
    });
  }

  void resetTabController() {
    if (tabController?.length != listPages.length) {
      tabController?.dispose();
      tabController = TabController(
        length: listPages.length,
        vsync: this,
        initialIndex: nowIndex,
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Scaffold(
      appBar: AppBar(
        bottom: PreferredSize(
            preferredSize: const Size.fromHeight(40),
            child: TabBar(
                controller: tabController,
                tabs: listPages.map((item) => Tab(child: TitleBarItem(data: item, closeCallback: (data) => onClosePage(data)),)).toList(),
            ),
        ),
      ),
      body: TabBarView(
        controller: tabController,
        children: listPages.map((item) => item.content).toList(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => addNewPage(),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

class PageMain extends StatefulWidget {
  const PageMain({super.key});

  @override
  State<PageMain> createState() => _StatePageMain();
}

class _StatePageContent extends State<PageContent> with AutomaticKeepAliveClientMixin {
  List<String> listItems = <String>[];

  @override
  void initState() {
    print("初始化页面内容控制器:${widget.data}");
    setState(() {
      for (int index = 0; index <= 30; index++) {
        listItems.add("${widget.data} - $index");
      }
    });
    super.initState();
  }

  @override
  void dispose() {
    print("释放页面内容控制器:${widget.data}");
    super.dispose();
  }

  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    print("Build页面 ${widget.data}");
    return Container(
      alignment: Alignment.center,
      child: Column(
        children: [
          Text(widget.data),
          Expanded(
            child: ListView.builder(
                itemExtent: 30,
                itemCount: listItems.length,
                itemBuilder: (context, index) {
                  return Text(listItems[index]);
                }
            ),
          ),
        ],
      ),
    );
  }
}

class PageContent extends StatefulWidget {
  final int pageId;
  final String data;

  const PageContent({super.key, required this.data, required this.pageId});

  @override
  State<PageContent> createState() {
    return _StatePageContent();
  }
}

typedef ClickCallback = void Function(PageData data);

class TitleBarItem extends StatelessWidget {
  final PageData data;
  final ClickCallback closeCallback;

  const TitleBarItem({
    super.key,
    required this.data,
    required this.closeCallback,
  });

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 200,
      child: Row(
        children: [
          Expanded(child: Text(data.data)),
          IconButton(
              onPressed: () => closeCallback(data),
              icon: const Icon(Icons.close))
        ],
      ),
    );
  }
}
11-14 17:10