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))
],
),
);
}
}
flutter TabBarView 动态添加删除页面的更多相关文章
- 编辑 Ext 表格(一)——— 动态添加删除行列
一.动态增删行 在 ext 表格中,动态添加行主要和表格绑定的 store 有关, 通过对 store 数据集进行添加或删除,就能实现表格行的动态添加删除. (1) 动态添加表格的行 gridS ...
- 用Javascript动态添加删除HTML元素实例 (转载)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- jquery动态添加删除div--事件绑定,对象克隆
我想做一个可以动态添加删除div的功能.中间遇到一个问题,最后在manong123.com开发文摘 版主的热心帮助下解答了(答案在最后) 使用到的jquery方法和思想就是:事件的绑定和销毁(unbi ...
- JS动态添加删除html
本功能要求是页面传一个List 集合给后台而且页面可以动态添加删除html代码需求如下: 下面是jsp页面代码 <%@ page language="java" pageEn ...
- js实现网页收藏功能,动态添加删除网址
<html> <head> <title> 动态添加删除网址 </title> <meta charset="utf-8"&g ...
- jQuery动态添加删除CSS样式
jQuery框架提供了两个CSS样式操作方法,一个是追加样式addClass,一个是移除样式removeClass,下面通过一个小例子讲解用法. jQuery动态追加移除CSS样式 <!DOCT ...
- C#控制IIS动态添加删除网站
我的目的是在Winform程序里面,可以直接启动一个HTTP服务端,给下游客户连接使用. 查找相关技术,有两种方法: 1.使用C#动态添加网站应用到IIS中,借用IIS的管理能力来提供HTTP接口.本 ...
- Angular-表单动态添加删除
angular本身不允许去操作DOM,在angular的角度来说,所有操作都以数据为核心,剩下的事情由angular来完成.所以说,想清楚问题的根源,解决起来也不是那么困难. 前提 那么,要做的这个添 ...
- jQuery动态添加删除select项
// 添加 function col_add() { var selObj = $("#mySelect"); var value="value"; var t ...
- zookeeper动态添加/删除集群中实例(zookeeper 3.6)
一,用来作为demo操作的zookeeper集群中的实例: 机器名:zk1 server.1=172.18.1.1:2888:3888 机器名:zk2 server.2=172.18.1.2:2888 ...
随机推荐
- Windows 服务管理
创建服务 New-Service -Name NAME -BinaryPathName COMMAND -StartupType Automatic -Description DESCRIPTION ...
- 【Python】Selenium自动化测试之动态识别验证码图片方法(附静态图片文字获取)
目录 一.前提 二.获取验证码 三.获取4位验证码 四.判断验证码是否正确 五.输入验证码登录 六.登录页面类 七.完整的获取验证码类代码 八.附录:静态图片文字提取 一.前提 返回目录 经常会遇到登 ...
- LinkedHashMap原理详解—从LRU缓存机制说起
写在前面 从一道Leetcode题目说起 首先,来看一下Leetcode里面的一道经典题目:146.LRU缓存机制,题目描述如下: 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结 ...
- Angular Material 18+ 高级教程 – Material Ripple
介绍 Ripple (波纹) 是 Material Design 中一个标志性的特色. 点击 button 会溅起水波的感觉. 参考 Docs – Ripples When to use it? 一般 ...
- HTML & CSS – Practice Projects
前言 学完了 w3school 就要练练手了. 这篇是记入我学习的过程, 和知识点. update: 2022-02-27 用文章来表达太难了, 用视频比较合理. 所以我就没有继续写了. 这里记入几篇 ...
- RabbitMQ——死信队列介绍和应用
死信和死信队列的概念 什么是死信?简单来说就是无法被消费和处理的消息.一般生产者将消息投递到broker或者queue,消费者直接从中取出消息进行消费.但有时因为某些原因导致消息不能被消费,导致消息积 ...
- @vue/cli eslint插件使用指南
使用步骤 使用 npm 安装 @vue/cli-service 版本对应的 @vue/cli-plugin-eslint 例如:"@vue/cli-service": " ...
- 搭建本地nginx-rtmp服务,初体验rtmp推流、拉流
实验环境说明: ubuntu 16.04 进行本实验的前提:需要在ubuntu上搭建好ffmpeg环境,参考我的另一篇博文 ffmpeg编译过程经历的99八十一难 下面开始本文内容 PART1 编译安 ...
- SQL limit字句
limit用法介绍 limit子句可以返回检索查询行的某一连续的部分 用法介绍: SELECT column_list FROM table1 ORDER BY column_list LIMIT r ...
- i mean
马上教师节了,咱们不给教练整坨大的吗