flutter TabBarView 动态添加删除页面的示例代码

 更新时间:2024年11月18日 09:49:32   作者:爱学习的绿叶  
在Flutter中使用TabBarView动态添加和删除页面时,如果未为每个页面设置唯一的key,会导致删除页面时出现状态错误或删除错误的页面,正确的做法是为每个页面指定全局唯一的key,这样可以确保页面在添加和删除时状态正确,感兴趣的朋友跟随小编一起看看吧

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

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

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

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();
    });
  }

指定了全局唯一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))
        ],
      ),
    );
  }
}

到此这篇关于flutter TabBarView 动态添加删除页面的文章就介绍到这了,更多相关flutter TabBarView 删除页面内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:

相关文章

  • 详解关于mybatis-plus中Service和Mapper的分析

    详解关于mybatis-plus中Service和Mapper的分析

    这篇文章主要介绍了详解关于mybatis-plus中Service和Mapper的分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • SpringSecurity实现原理及多过滤器链匹配规则详解

    SpringSecurity实现原理及多过滤器链匹配规则详解

    SpringSecurity通过FilterChainProxy管理多个过滤器链,根据请求的URI和RequestMatcher匹配,选择合适的过滤器链进行认证和授权处理,配置多个过滤器链需用@Order区分优先级,处理流程清晰高效
    2025-10-10
  • SpringBoot之如何搭建SpringBoot+Maven项目

    SpringBoot之如何搭建SpringBoot+Maven项目

    这篇文章主要介绍了SpringBoot之如何搭建SpringBoot+Maven项目问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • Delegate IDE build/run actions to maven 配置会影响程序运行吗?

    Delegate IDE build/run actions to maven 配置会影响程序运行吗?

    这篇文章主要介绍了Delegate IDE build/run actions to maven 配置会影响程序运行吗,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • Springboot如何解决前端请求跨域的问题

    Springboot如何解决前端请求跨域的问题

    这篇文章主要介绍了Springboot如何解决前端请求跨域的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • Java网约车项目实战之实现抢单功能详解

    Java网约车项目实战之实现抢单功能详解

    本文详细介绍了如何使用Java实现网约车项目的抢单功能,并提供了一个完整的代码示例,以便读者能够直接运行和参考,感兴趣的朋友一起看看吧
    2024-12-12
  • 解决因缺少Log4j依赖导致应用启动失败的问题

    解决因缺少Log4j依赖导致应用启动失败的问题

    日志是应用软件中不可缺少的部分,Apache的开源项目log4j是一个功能强大的日志组件,提供方便的日志记录。但这篇文章不是介绍Log4j,这篇文章主要介绍了关于因缺少Log4j依赖导致应用启动失败问题的相关资料,需要的朋友可以参考下。
    2017-04-04
  • SpringBoot Mybatis 配置文件形式详解

    SpringBoot Mybatis 配置文件形式详解

    这篇文章主要介绍了SpringBoot Mybatis 配置文件形式详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-03-03
  • Java线程创建(卖票),线程同步(卖包子)的实现示例

    Java线程创建(卖票),线程同步(卖包子)的实现示例

    这篇文章主要介绍了Java线程创建(卖票),线程同步(卖包子)的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • Java设计模式之装饰模式详解

    Java设计模式之装饰模式详解

    这篇文章主要介绍了Java设计模式中的装饰者模式,装饰者模式即Decorator Pattern,装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能,装饰模式又名包装模式。装饰器模式以对客户端透明的方式拓展对象的功能,是继承关系的一种替代方案
    2022-08-08

最新评论