flutter?TabBarView?動態(tài)添加刪除頁面的示例代碼
在TabBarView 動態(tài)添加頁面后刪除其中一個頁面會導致后面的頁面狀態(tài)錯誤或刪除的頁面不正確。出現(xiàn)這種問題是由于創(chuàng)建子頁面時沒有為子頁面設置唯一的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();
});
}如上面的代碼所示, 在創(chuàng)建PageContent 組件時如果沒有指定全局唯一的key, 關(guān)閉頁面時就會導致后面的頁面被再次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后在刪除子頁面,后續(xù)頁面就可以正常顯示。

所有代碼如下
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) {
//頁面已經(jīng)選中
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);
});
}
//關(guān)閉頁面
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("初始化頁面內(nèi)容控制器:${widget.data}");
setState(() {
for (int index = 0; index <= 30; index++) {
listItems.add("${widget.data} - $index");
}
});
super.initState();
}
@override
void dispose() {
print("釋放頁面內(nèi)容控制器:${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))
],
),
);
}
}到此這篇關(guān)于flutter TabBarView 動態(tài)添加刪除頁面的文章就介紹到這了,更多相關(guān)flutter TabBarView 刪除頁面內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot創(chuàng)建監(jiān)聽器的方法示例
在Java中,監(jiān)聽器(Listener)是一種設計模式,它允許對象在 特定事件 發(fā)生時 自動執(zhí)行某些操作,這種設計模式通常用于實現(xiàn) 發(fā)布-訂閱模型,本文給大家介紹了SpringBoot創(chuàng)建監(jiān)聽器的方法示例,感興趣的通過可以參考一下2024-04-04
Spring中的@PropertySource注解源碼詳細解析
這篇文章主要介紹了Spring中的@PropertySource注解源碼詳細解析,@PropertySource注解,標注在配置類@Configuration上面,下面主要分析一下@PropertySource注解的處理過程,也就是怎么把配置信息從.properies文件放到environment中的,需要的朋友可以參考下2024-01-01
SpringBoot讀取Resource目錄下文件的四種方式總結(jié)
在Spring?Boot項目中,經(jīng)常需要獲取resources目錄下的文件,這些文件可以包括配置文件、模板文件、靜態(tài)資源等,本文將介紹四種常用的方法來獲取resources目錄下的文件,需要的朋友可以參考下2023-08-08

