flutter TabBarView 动态添加删除页面

在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后在删除子页面,后续页面就可以正常显示。

所有代码如下

复制代码
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))
        ],
      ),
    );
  }
}
相关推荐
AiFlutter3 小时前
Flutter通过 Coap发送组播
flutter
嘟嘟叽1 天前
初学 flutter 环境变量配置
flutter
iFlyCai1 天前
深入理解Flutter生命周期函数之StatefulWidget(一)
flutter·生命周期·dart·statefulwidget
sunly_1 天前
Flutter:photo_view图片预览功能
android·javascript·flutter
Summer不秃1 天前
Flutter中sqflite的使用案例
flutter
sunly_1 天前
Flutter:TweenAnimationBuilder自定义隐式动画
flutter
AiFlutter1 天前
Flutter-Web首次加载时添加动画
前端·flutter
Allen Su2 天前
【Flutter 问题系列第 84 篇】如何清除指定网络图片的缓存
flutter·缓存·如何清除指定网络图片的缓存·网络图片缓存
sunly_2 天前
Flutter:key的作用原理(LocalKey ,GlobalKey)
开发语言·javascript·flutter
AiFlutter2 天前
Flutter-flutter_blue_plus打包后无法进行设备扫描
flutter