前言:
成都創(chuàng)新互聯(lián)公司主打移動(dòng)網(wǎng)站、做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)、網(wǎng)站改版、網(wǎng)絡(luò)推廣、網(wǎng)站維護(hù)、域名申請(qǐng)、等互聯(lián)網(wǎng)信息服務(wù),為各行業(yè)提供服務(wù)。在技術(shù)實(shí)力的保障下,我們?yōu)榭蛻?hù)承諾穩(wěn)定,放心的服務(wù),根據(jù)網(wǎng)站的內(nèi)容與功能再?zèng)Q定采用什么樣的設(shè)計(jì)。最后,要實(shí)現(xiàn)符合網(wǎng)站需求的內(nèi)容、功能與設(shè)計(jì),我們還會(huì)規(guī)劃穩(wěn)定安全的技術(shù)方案做保障。
在Flutter應(yīng)用中,導(dǎo)航欄切換頁(yè)面后默認(rèn)情況下會(huì)丟失原頁(yè)面狀態(tài),即每次進(jìn)入頁(yè)面時(shí)都會(huì)重新初始化狀態(tài),如果在initState中打印日志,會(huì)發(fā)現(xiàn)每次進(jìn)入時(shí)都會(huì)輸出,顯然這樣增加了額外的開(kāi)銷(xiāo),并且?guī)?lái)了不好的用戶(hù)體驗(yàn)。
在正文之前,先看一些常見(jiàn)的App導(dǎo)航,以喜馬拉雅FM為例:
它擁有一個(gè)固定的底部導(dǎo)航以及首頁(yè)的頂部導(dǎo)航,可以看到不管是點(diǎn)擊底部導(dǎo)航切換頁(yè)面還是在首頁(yè)左右側(cè)滑切換頁(yè)面,之前的頁(yè)面狀態(tài)都是始終維持的,下面就具體介紹下如何在flutter中實(shí)現(xiàn)類(lèi)似喜馬拉雅的導(dǎo)航效果
第一步:實(shí)現(xiàn)固定的底部導(dǎo)航
在通過(guò)flutter create生成的項(xiàng)目模板中,我們先簡(jiǎn)化一下代碼,將MyHomePage提取到一個(gè)單獨(dú)的home.dart文件,并在Scaffold腳手架中添加bottomNavigationBar底部導(dǎo)航,在body中展示當(dāng)前選中的子頁(yè)面。
/// 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('首頁(yè)')), BottomNavigationBarItem(icon: Icon(Icons.music_video), title: Text('聽(tīng)')), 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] ); } }
其中的三個(gè)子頁(yè)面結(jié)構(gòu)相同,均顯示一個(gè)計(jì)數(shù)器和一個(gè)加號(hào)按鈕,以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), ) ); } }
當(dāng)前效果如下:
可以看到,從第二頁(yè)切換回第一頁(yè)時(shí),第一頁(yè)的狀態(tài)已經(jīng)丟失
第二步:實(shí)現(xiàn)底部導(dǎo)航切換時(shí)保持原頁(yè)面狀態(tài)
可能有些小伙伴在搜索后會(huì)開(kāi)始直接使用官方推薦的AutomaticKeepAliveClientMixin,通過(guò)在子頁(yè)面的State類(lèi)重寫(xiě)wantKeepAlive為true 。 然而,如果你的代碼和我上面的類(lèi)似,body中并沒(méi)有使用PageView或TabBarView,很不幸的告訴你,踩到坑了,這樣是無(wú)效的,原因后面再詳述。現(xiàn)在我們先來(lái)介紹另外兩種方式:
① 使用IndexedStack實(shí)現(xiàn)
IndexedStack繼承自Stack,它的作用是顯示第index個(gè)child,其它c(diǎn)hild在頁(yè)面上是不可見(jiàn)的,但所有child的狀態(tài)都被保持,所以這個(gè)Widget可以實(shí)現(xiàn)我們的需求,我們只需要將現(xiàn)在的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, )); }
保存后再次測(cè)試一下
② 使用Offstage實(shí)現(xiàn)
Offstage的作用十分簡(jiǎn)單,通過(guò)一個(gè)參數(shù)來(lái)控制child是否顯示,所以我們同樣可以組合使用Offstage來(lái)實(shí)現(xiàn)該需求,其實(shí)現(xiàn)原理與IndexedStack類(lèi)似
/// 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], ), ], )); } }
在上面的兩種方式中都可以實(shí)現(xiàn)保持原頁(yè)面狀態(tài)的需求,但這里有一些開(kāi)銷(xiāo)上的問(wèn)題,有經(jīng)驗(yàn)的小伙伴應(yīng)該能發(fā)現(xiàn)當(dāng)應(yīng)用第一次加載的時(shí)候,所有子頁(yè)狀態(tài)都被實(shí)例化了(>這里的細(xì)節(jié)并不是因?yàn)槲抑苯影炎禹?yè)實(shí)例化放在bodyList里...<),如果在子頁(yè)State的initState中打印日志,可以在終端看到一次性輸出了所有子頁(yè)的日志。下面就介紹另一種通過(guò)繼承AutomaticKeepAliveClientMixin的方式來(lái)更好的實(shí)現(xiàn)保持狀態(tài)。
第三步:實(shí)現(xiàn)首頁(yè)的頂部導(dǎo)航
首先我們通過(guò)配合使用TabBar+TabBarView+AutomaticKeepAliveClientMixin來(lái)實(shí)現(xiàn)頂部導(dǎo)航(注意:TabBar和TabBarView需要提供controller,如果自己沒(méi)有定義,則必須使用DefaultTabController包裹)。此處也可以選擇使用PageView,后面會(huì)介紹。
我們先在home.dart文件移除Scaffold腳手架中的appBar頂部工具欄,然后開(kāi)始重寫(xiě)首頁(yè)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('小說(shuō)'), 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(), // 禁止滑動(dòng) )) ], )); } }
其中推薦頁(yè)、VIP頁(yè)、小說(shuō)頁(yè)、直播頁(yè)的結(jié)構(gòu)仍和之前的首頁(yè)結(jié)構(gòu)相同,僅顯示一個(gè)計(jì)數(shù)器和一個(gè)加號(hào)按鈕,以推薦頁(yè)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('首頁(yè)推薦: $count', style: TextStyle(fontSize: 30)) ), floatingActionButton: FloatingActionButton( onPressed: add, child: Icon(Icons.add), )); } }
保存后測(cè)試,
可以看到,現(xiàn)在添加了首頁(yè)頂部導(dǎo)航,且默認(rèn)支持左右側(cè)滑,接下來(lái)再進(jìn)一步的完善狀態(tài)保持
第四步:實(shí)現(xiàn)首頁(yè)頂部導(dǎo)航切換時(shí)保持原頁(yè)面狀態(tài)
③ 使用AutomaticKeepAliveClientMixin實(shí)現(xiàn)
寫(xiě)到這里已經(jīng)很簡(jiǎn)單了,我們只需要在首頁(yè)導(dǎo)航內(nèi)需要保持頁(yè)面狀態(tài)的子頁(yè)State中,繼承AutomaticKeepAliveClientMixin并重寫(xiě)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)
以首頁(yè)推薦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('首頁(yè)推薦: $count', style: TextStyle(fontSize: 30)) ), floatingActionButton: FloatingActionButton( onPressed: add, child: Icon(Icons.add), )); } }
再次保存測(cè)試,
現(xiàn)在已經(jīng)可以看到,不管是切換底部導(dǎo)航還是切換首頁(yè)頂部導(dǎo)航,所有的頁(yè)面狀態(tài)都可以被保持,并且在應(yīng)用第一次加載時(shí),終端只看到recommend initState的日志,第一次切換首頁(yè)頂部導(dǎo)航至vip頁(yè)面時(shí),終端輸出vip initState,當(dāng)再次返回推薦頁(yè)時(shí),不再輸出recommend initState。
所以,使用TabBarView+AutomaticKeepAliveClientMixin這種方式既實(shí)現(xiàn)了頁(yè)面狀態(tài)的保持,又具有類(lèi)似惰性求值的功能,對(duì)于未使用的頁(yè)面狀態(tài)不會(huì)進(jìn)行實(shí)例化,減小了應(yīng)用初始化時(shí)的開(kāi)銷(xiāo)。
更新
前面在底部導(dǎo)航介紹了使用IndexedStack和Offstage兩種方式實(shí)現(xiàn)保持頁(yè)面狀態(tài),但它們的缺點(diǎn)在于第一次加載時(shí)便實(shí)例化了所有的子頁(yè)面State。為了進(jìn)一步優(yōu)化,下面我們使用PageView+AutomaticKeepAliveClientMixin重寫(xiě)之前的底部導(dǎo)航,其中PageView和TabBarView的實(shí)現(xiàn)原理類(lèi)似,具體選擇哪一個(gè)并沒(méi)有強(qiáng)制要求。更新后的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('首頁(yè)')), BottomNavigationBarItem(icon: Icon(Icons.music_video), title: Text('聽(tīng)')), 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(), // 禁止滑動(dòng) )); } }
然后在bodyList的子頁(yè)State中繼承AutomaticKeepAliveClientMixin并重寫(xiě)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,更新后保存運(yùn)行,應(yīng)用第一次加載時(shí)不會(huì)輸出second initState,僅當(dāng)?shù)谝淮吸c(diǎn)擊底部導(dǎo)航切換至該頁(yè)時(shí),該子頁(yè)的State被實(shí)例化。
至此,如何實(shí)現(xiàn)一個(gè)類(lèi)似的 底部 + 首頁(yè)頂部導(dǎo)航 完結(jié) ~
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)創(chuàng)新互聯(lián)的支持。
當(dāng)前文章:Flutter實(shí)現(xiàn)頁(yè)面切換后保持原頁(yè)面狀態(tài)的3種方法
文章轉(zhuǎn)載:http://www.2m8n56k.cn/article6/joosog.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號(hào)、網(wǎng)站制作、小程序開(kāi)發(fā)、標(biāo)簽優(yōu)化、定制網(wǎng)站、網(wǎng)頁(yè)設(shè)計(jì)公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:[email protected]。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
全網(wǎng)營(yíng)銷(xiāo)推廣知識(shí)