pbootcms网站模板|日韩1区2区|织梦模板||网站源码|日韩1区2区|jquery建站特效-html5模板网

Flutter實現頁面切換后保持原頁面狀態(tài)的3種方法

這篇文章主要給大家介紹了關于Flutter實現頁面切換后保持原頁面狀態(tài)的3種方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者使用Flutter具有一定的參考學習價值,需要的朋友

前言:

在Flutter應用中,導航欄切換頁面后默認情況下會丟失原頁面狀態(tài),即每次進入頁面時都會重新初始化狀態(tài),如果在initState中打印日志,會發(fā)現每次進入時都會輸出,顯然這樣增加了額外的開銷,并且?guī)砹瞬缓玫挠脩趔w驗。
在正文之前,先看一些常見的App導航,以喜馬拉雅FM為例:

它擁有一個固定的底部導航以及首頁的頂部導航,可以看到不管是點擊底部導航切換頁面還是在首頁左右側滑切換頁面,之前的頁面狀態(tài)都是始終維持的,下面就具體介紹下如何在flutter中實現類似喜馬拉雅的導航效果

第一步:實現固定的底部導航

在通過flutter create生成的項目模板中,我們先簡化一下代碼,將MyHomePage提取到一個單獨的home.dart文件,并在Scaffold腳手架中添加bottomNavigationBar底部導航,在body中展示當前選中的子頁面。


/// home.dart
import 'package:flutter/material.dart';

import './pages/first_page.dart';
import './pages/second_page.dart';
import './pages/third_page.dart';

class MyHomePage extends StatefulWidget {
 @override
 _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
 final items = [
 BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('首頁')),
 BottomNavigationBarItem(icon: Icon(Icons.music_video), title: Text('聽')),
 BottomNavigationBarItem(icon: Icon(Icons.message), title: Text('消息'))
 ];

 final bodyList = [FirstPage(), SecondPage(), ThirdPage()];

 int currentIndex = 0;

 void onTap(int index) {
 setState(() {
 currentIndex = index;
 });
 }

 @override
 Widget build(BuildContext context) {
 return Scaffold(
 appBar: AppBar(
  title: Text('demo'),
 ),
 bottomNavigationBar: BottomNavigationBar(
  items: items,
  currentIndex: currentIndex, 
  onTap: onTap
 ),
 body: bodyList[currentIndex]
 );
 }
}

其中的三個子頁面結構相同,均顯示一個計數器和一個加號按鈕,以first_page.dart為例:


/// first_page.dart
import 'package:flutter/material.dart';

class FirstPage extends StatefulWidget {
 @override
 _FirstPageState createState() => _FirstPageState();
}

class _FirstPageState extends State<FirstPage> {
 int count = 0;

 void add() {
 setState(() {
 count++;
 });
 }

 @override
 Widget build(BuildContext context) {
 return Scaffold(
 body: Center(
  child: Text('First: $count', style: TextStyle(fontSize: 30))
 ),
 floatingActionButton: FloatingActionButton(
  onPressed: add,
  child: Icon(Icons.add),
 )
 );
 }
}

當前效果如下:

可以看到,從第二頁切換回第一頁時,第一頁的狀態(tài)已經丟失

第二步:實現底部導航切換時保持原頁面狀態(tài)

可能有些小伙伴在搜索后會開始直接使用官方推薦的AutomaticKeepAliveClientMixin,通過在子頁面的State類重寫wantKeepAlive為true 。 然而,如果你的代碼和我上面的類似,body中并沒有使用PageView或TabBarView,很不幸的告訴你,踩到坑了,這樣是無效的,原因后面再詳述。現在我們先來介紹另外兩種方式:

① 使用IndexedStack實現

IndexedStack繼承自Stack,它的作用是顯示第index個child,其它child在頁面上是不可見的,但所有child的狀態(tài)都被保持,所以這個Widget可以實現我們的需求,我們只需要將現在的body用IndexedStack包裹一層即可


/// home.dart
class _MyHomePageState extends State<MyHomePage> {
 ...
 ...
 ...
 @override
 Widget build(BuildContext context) {
 return Scaffold(
 appBar: AppBar(
  title: Text('demo'),
 ),
 bottomNavigationBar: BottomNavigationBar(
  items: items, currentIndex: currentIndex, onTap: onTap),
 // body: bodyList[currentIndex]
 body: IndexedStack(
  index: currentIndex,
  children: bodyList,
 ));
 }

保存后再次測試一下

② 使用Offstage實現

Offstage的作用十分簡單,通過一個參數來控制child是否顯示,所以我們同樣可以組合使用Offstage來實現該需求,其實現原理與IndexedStack類似


/// home.dart
class _MyHomePageState extends State<MyHomePage> {
 ...
 ...
 ...
 @override
 Widget build(BuildContext context) {
 return Scaffold(
  appBar: AppBar(
   title: Text('demo'),
  ),
  bottomNavigationBar: BottomNavigationBar(
   items: items, currentIndex: currentIndex, onTap: onTap),
  // body: bodyList[currentIndex],
  body: Stack(
   children: [
   Offstage(
    offstage: currentIndex != 0,
    child: bodyList[0],
   ),
   Offstage(
    offstage: currentIndex != 1,
    child: bodyList[1],
   ),
   Offstage(
    offstage: currentIndex != 2,
    child: bodyList[2],
   ),
   ],
  ));
 }
}

在上面的兩種方式中都可以實現保持原頁面狀態(tài)的需求,但這里有一些開銷上的問題,有經驗的小伙伴應該能發(fā)現當應用第一次加載的時候,所有子頁狀態(tài)都被實例化了(>這里的細節(jié)并不是因為我直接把子頁實例化放在bodyList里...<),如果在子頁State的initState中打印日志,可以在終端看到一次性輸出了所有子頁的日志。下面就介紹另一種通過繼承AutomaticKeepAliveClientMixin的方式來更好的實現保持狀態(tài)。

第三步:實現首頁的頂部導航

首先我們通過配合使用TabBar+TabBarView+AutomaticKeepAliveClientMixin來實現頂部導航(注意:TabBar和TabBarView需要提供controller,如果自己沒有定義,則必須使用DefaultTabController包裹)。此處也可以選擇使用PageView,后面會介紹。

我們先在home.dart文件移除Scaffold腳手架中的appBar頂部工具欄,然后開始重寫首頁first_page.dart:


/// first_page.dart
import 'package:flutter/material.dart';

import './recommend_page.dart';
import './vip_page.dart';
import './novel_page.dart';
import './live_page.dart';

class _TabData {
 final Widget tab;
 final Widget body;
 _TabData({this.tab, this.body});
}

final _tabDataList = <_TabData>[
 _TabData(tab: Text('推薦'), body: RecommendPage()),
 _TabData(tab: Text('VIP'), body: VipPage()),
 _TabData(tab: Text('小說'), body: NovelPage()),
 _TabData(tab: Text('直播'), body: LivePage())
];

class FirstPage extends StatefulWidget {
 @override
 _FirstPageState createState() => _FirstPageState();
}

class _FirstPageState extends State<FirstPage> {
 final tabBarList = _tabDataList.map((item) => item.tab).toList();
 final tabBarViewList = _tabDataList.map((item) => item.body).toList();

 @override
 Widget build(BuildContext context) {
 return DefaultTabController(
  length: tabBarList.length,
  child: Column(
   children: <Widget>[
   Container(
    width: double.infinity,
    height: 80,
    padding: EdgeInsets.fromLTRB(20, 24, 0, 0),
    alignment: Alignment.centerLeft,
    color: Colors.black,
    child: TabBar(
     isScrollable: true,
     indicatorColor: Colors.red,
     indicatorSize: TabBarIndicatorSize.label,
     unselectedLabelColor: Colors.white,
     unselectedLabelStyle: TextStyle(fontSize: 18),
     labelColor: Colors.red,
     labelStyle: TextStyle(fontSize: 20),
     tabs: tabBarList),
   ),
   Expanded(
    child: TabBarView(
    children: tabBarViewList,
    // physics: NeverScrollableScrollPhysics(), // 禁止滑動
   ))
   ],
  ));
 }
}

其中推薦頁、VIP頁、小說頁、直播頁的結構仍和之前的首頁結構相同,僅顯示一個計數器和一個加號按鈕,以推薦頁recommend_page.dart為例:


/// recommend_page.dart
import 'package:flutter/material.dart';

class RecommendPage extends StatefulWidget {
 @override
 _RecommendPageState createState() => _RecommendPageState();
}

class _RecommendPageState extends State<RecommendPage> {
 int count = 0;

 void add() {
 setState(() {
  count++;
 });
 }
 
 @override
 void initState() {
 super.initState();
 print('recommend initState');
 }

 @override
 Widget build(BuildContext context) {
 return Scaffold(
  body:Center(
   child: Text('首頁推薦: $count', style: TextStyle(fontSize: 30))
  ),
  floatingActionButton: FloatingActionButton(
   onPressed: add,
   child: Icon(Icons.add),
  ));
 }
}

保存后測試,

可以看到,現在添加了首頁頂部導航,且默認支持左右側滑,接下來再進一步的完善狀態(tài)保持

第四步:實現首頁頂部導航切換時保持原頁面狀態(tài)

③ 使用AutomaticKeepAliveClientMixin實現

寫到這里已經很簡單了,我們只需要在首頁導航內需要保持頁面狀態(tài)的子頁State中,繼承AutomaticKeepAliveClientMixin并重寫wantKeepAlive為true即可。

notes:Subclasses must implement wantKeepAlive, and their build methods must call super.build (the return value will always return null, and should be ignored)

以首頁推薦recommend_page.dart為例:


/// recommend_page.dart
import 'package:flutter/material.dart';

class RecommendPage extends StatefulWidget {
 @override
 _RecommendPageState createState() => _RecommendPageState();
}

class _RecommendPageState extends State<RecommendPage>
 with AutomaticKeepAliveClientMixin {
 int count = 0;

 void add() {
 setState(() {
  count++;
 });
 }

 @override
 bool get wantKeepAlive => true;

 @override
 void initState() {
 super.initState();
 print('recommend initState');
 }

 @override
 Widget build(BuildContext context) {
 super.build(context);
 return Scaffold(
  body:Center(
   child: Text('首頁推薦: $count', style: TextStyle(fontSize: 30))
  ),
  floatingActionButton: FloatingActionButton(
   onPressed: add,
   child: Icon(Icons.add),
  ));
 }
}

再次保存測試,

現在已經可以看到,不管是切換底部導航還是切換首頁頂部導航,所有的頁面狀態(tài)都可以被保持,并且在應用第一次加載時,終端只看到recommend initState的日志,第一次切換首頁頂部導航至vip頁面時,終端輸出vip initState,當再次返回推薦頁時,不再輸出recommend initState。

所以,使用TabBarView+AutomaticKeepAliveClientMixin這種方式既實現了頁面狀態(tài)的保持,又具有類似惰性求值的功能,對于未使用的頁面狀態(tài)不會進行實例化,減小了應用初始化時的開銷。

更新

前面在底部導航介紹了使用IndexedStack和Offstage兩種方式實現保持頁面狀態(tài),但它們的缺點在于第一次加載時便實例化了所有的子頁面State。為了進一步優(yōu)化,下面我們使用PageView+AutomaticKeepAliveClientMixin重寫之前的底部導航,其中PageView和TabBarView的實現原理類似,具體選擇哪一個并沒有強制要求。更新后的home.dart文件如下:


/// home.dart
import 'package:flutter/material.dart';

import './pages/first_page.dart';
import './pages/second_page.dart';
import './pages/third_page.dart';

class MyHomePage extends StatefulWidget {
 @override
 _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
 final items = [
 BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('首頁')),
 BottomNavigationBarItem(icon: Icon(Icons.music_video), title: Text('聽')),
 BottomNavigationBarItem(icon: Icon(Icons.message), title: Text('消息'))
 ];

 final bodyList = [FirstPage(), SecondPage(), ThirdPage()];

 final pageController = PageController();

 int currentIndex = 0;

 void onTap(int index) {
 pageController.jumpToPage(index);
 }

 void onPageChanged(int index) {
 setState(() {
  currentIndex = index;
 });
 }

 @override
 Widget build(BuildContext context) {
 return Scaffold(
  bottomNavigationBar: BottomNavigationBar(
   items: items, currentIndex: currentIndex, onTap: onTap),
  // body: bodyList[currentIndex],
  body: PageView(
   controller: pageController,
   onPageChanged: onPageChanged,
   children: bodyList,
   physics: NeverScrollableScrollPhysics(), // 禁止滑動
  ));
 }
}

然后在bodyList的子頁State中繼承AutomaticKeepAliveClientMixin并重寫wantKeepAlive,以second_page.dart為例:


/// second_page.dart
import 'package:flutter/material.dart';

class SecondPage extends StatefulWidget {
 @override
 _SecondPageState createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage>
 with AutomaticKeepAliveClientMixin {
 int count = 0;

 void add() {
 setState(() {
  count++;
 });
 }

 @override
 bool get wantKeepAlive => true;
 
 @override
 void initState() {
 super.initState();
 print('second initState');
 }

 @override
 Widget build(BuildContext context) {
 super.build(context);
 return Scaffold(
  body: Center(
   child: Text('Second: $count', style: TextStyle(fontSize: 30))
  ),
  floatingActionButton: FloatingActionButton(
   onPressed: add,
   child: Icon(Icons.add),
  ));
 }
}

Ok,更新后保存運行,應用第一次加載時不會輸出second initState,僅當第一次點擊底部導航切換至該頁時,該子頁的State被實例化。

至此,如何實現一個類似的 底部 + 首頁頂部導航 完結 ~

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對html5模板網的支持。

【網站聲明】本站部分內容來源于互聯網,旨在幫助大家更快的解決問題,如果有圖片或者內容侵犯了您的權益,請聯系我們刪除處理,感謝您的支持!

相關文檔推薦

主站蜘蛛池模板: 探鸣起名网-品牌起名-英文商标起名-公司命名-企业取名包满意 | 蜂窝块状沸石分子筛-吸附脱硫分子筛-萍乡市捷龙环保科技有限公司 | 硅胶制品-硅橡胶制品-东莞硅胶制品厂家-广东帝博科技有限公司 | 破碎机_上海破碎机_破碎机设备_破碎机厂家-上海山卓重工机械有限公司 | 上海单片机培训|重庆曙海培训分支机构—CortexM3+uC/OS培训班,北京linux培训,Windows驱动开发培训|上海IC版图设计,西安linux培训,北京汽车电子EMC培训,ARM培训,MTK培训,Android培训 | 手术室净化厂家_成都实验室装修公司_无尘车间施工单位_洁净室工程建设团队-四川华锐16年行业经验 | 微学堂-电动能源汽车评测_电动车性能分享网 | 中高频感应加热设备|高频淬火设备|超音频感应加热电源|不锈钢管光亮退火机|真空管烤消设备 - 郑州蓝硕工业炉设备有限公司 | 亮点云建站-网站建设制作平台| 自动记录数据电子台秤,记忆储存重量电子桌称,设定时间记录电子秤-昆山巨天 | 旗杆生产厂家_不锈钢锥形旗杆价格_铝合金电动旗杆-上海锥升金属科技有限公司 | 中天寰创-内蒙古钢结构厂家|门式刚架|钢结构桁架|钢结构框架|包头钢结构煤棚 | pH污水传感器电极,溶解氧电极传感器-上海科蓝仪表科技有限公司 | 高压包-点火器-高压发生器-点火变压器-江苏天网 | 铝合金风口-玻璃钢轴流风机-玻璃钢屋顶风机-德州东润空调设备有限公司 | 酒糟烘干机-豆渣烘干机-薯渣烘干机-糟渣烘干设备厂家-焦作市真节能环保设备科技有限公司 | 「银杏树」银杏树行情价格_银杏树种植_山东程锦园林 | 北京开源多邦科技发展有限公司官网| 粤丰硕水性环氧地坪漆-防静电自流平厂家-环保地坪涂料代理 | 浙江华锤电器有限公司_地磅称重设备_防作弊地磅_浙江地磅售后维修_无人值守扫码过磅系统_浙江源头地磅厂家_浙江工厂直营地磅 | 行星齿轮减速机,减速机厂家,山东减速机-淄博兴江机械制造 | 地埋式垃圾站厂家【佳星环保】小区压缩垃圾中转站转运站 | 大型工业风扇_工业大风扇_大吊扇_厂房车间降温-合昌大风扇 | 扫地车厂家-山西洗地机-太原电动扫地车「大同朔州吕梁晋中忻州长治晋城洗地机」山西锦力环保科技有限公司 | 神马影院-实时更新秒播| 活性氧化铝球|氧化铝干燥剂|分子筛干燥剂|氢氧化铝粉-淄博同心材料有限公司 | 头条搜索极速版下载安装免费新版,头条搜索极速版邀请码怎么填写? - 欧远全 | 广州冷却塔维修厂家_冷却塔修理_凉水塔风机电机填料抢修-广东康明节能空调有限公司 | 不锈钢复合板|钛复合板|金属复合板|南钢集团安徽金元素复合材料有限公司-官网 | 天津蒸汽/热水锅炉-电锅炉安装维修直销厂家-天津鑫淼暖通设备有限公司 | 合肥白癜风医院_合肥治疗白癜风医院_合肥看白癜风医院哪家好_合肥华研白癜风医院 | 骨密度检测仪_骨密度分析仪_骨密度仪_动脉硬化检测仪专业生产厂家【品源医疗】 | 东莞喷砂机-喷砂机-喷砂机配件-喷砂器材-喷砂加工-东莞市协帆喷砂机械设备有限公司 | 炭黑吸油计_测试仪,单颗粒子硬度仪_ASTM标准炭黑自销-上海贺纳斯仪器仪表有限公司(HITEC中国办事处) | 丹尼克尔拧紧枪_自动送钉机_智能电批_柔性振动盘_螺丝供料器品牌 | 高清视频编码器,4K音视频编解码器,直播编码器,流媒体服务器,深圳海威视讯技术有限公司 | 隐形纱窗|防护纱窗|金刚网防盗纱窗|韦柏纱窗|上海青木装潢制品有限公司|纱窗国标起草单位 | 实木家具_实木家具定制_全屋定制_美式家具_圣蒂斯堡官网 | 蔡司三坐标-影像测量机-3D扫描仪-蔡司显微镜-扫描电镜-工业CT-ZEISS授权代理商三本工业测量 | 青岛侦探调查_青岛侦探事务所_青岛调查事务所_青岛婚外情取证-青岛狄仁杰国际侦探公司 | 「阿尔法设计官网」工业设计_产品设计_产品外观设计 深圳工业设计公司 |