麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 系統(tǒng) > Android > 正文

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

2019-10-21 21:19:24
字體:
供稿:網(wǎng)友

前言:

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

Flutter,頁面切換,原頁面狀態(tài)

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

第一步:實(shí)現(xiàn)固定的底部導(dǎo)航

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

/// home.dartimport '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] ); }}

其中的三個(gè)子頁面結(jié)構(gòu)相同,均顯示一個(gè)計(jì)數(shù)器和一個(gè)加號按鈕,以first_page.dart為例:

/// first_page.dartimport '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)前效果如下:

Flutter,頁面切換,原頁面狀態(tài)

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

第二步:實(shí)現(xiàn)底部導(dǎo)航切換時(shí)保持原頁面狀態(tài)

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

① 使用IndexedStack實(shí)現(xiàn)

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

/// home.dartclass _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, )); }

保存后再次測試一下

Flutter,頁面切換,原頁面狀態(tài)

② 使用Offstage實(shí)現(xiàn)

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

/// home.dartclass _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)保持原頁面狀態(tài)的需求,但這里有一些開銷上的問題,有經(jīng)驗(yàn)的小伙伴應(yīng)該能發(fā)現(xiàn)當(dāng)應(yīng)用第一次加載的時(shí)候,所有子頁狀態(tài)都被實(shí)例化了(>這里的細(xì)節(jié)并不是因?yàn)槲抑苯影炎禹搶?shí)例化放在bodyList里...<),如果在子頁State的initState中打印日志,可以在終端看到一次性輸出了所有子頁的日志。下面就介紹另一種通過繼承AutomaticKeepAliveClientMixin的方式來更好的實(shí)現(xiàn)保持狀態(tài)。

第三步:實(shí)現(xiàn)首頁的頂部導(dǎo)航

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

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

/// first_page.dartimport '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(), // 禁止滑動(dòng)   ))   ],  )); }}

其中推薦頁、VIP頁、小說頁、直播頁的結(jié)構(gòu)仍和之前的首頁結(jié)構(gòu)相同,僅顯示一個(gè)計(jì)數(shù)器和一個(gè)加號按鈕,以推薦頁recommend_page.dart為例:

/// recommend_page.dartimport '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),  )); }}

保存后測試,

Flutter,頁面切換,原頁面狀態(tài)

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

第四步:實(shí)現(xiàn)首頁頂部導(dǎo)航切換時(shí)保持原頁面狀態(tài)

③ 使用AutomaticKeepAliveClientMixin實(shí)現(xiàn)

寫到這里已經(jīng)很簡單了,我們只需要在首頁導(dǎo)航內(nèi)需要保持頁面狀態(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.dartimport '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),  )); }}

再次保存測試,

Flutter,頁面切換,原頁面狀態(tài)

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

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

更新

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

/// home.dartimport '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(), // 禁止滑動(dòng)  )); }}

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

/// second_page.dartimport '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)航切換至該頁時(shí),該子頁的State被實(shí)例化。

至此,如何實(shí)現(xiàn)一個(gè)類似的 底部 + 首頁頂部導(dǎo)航 完結(jié) ~

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對VEVB武林網(wǎng)的支持。


注:相關(guān)教程知識(shí)閱讀請移步到Android開發(fā)頻道。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 啊~用cao嗯力cao烂我视频 | 婷婷久久久久久 | 小情侣嗯啊哦视频www | 免费黄色大片网站 | 一区二区三区视频在线观看 | 蜜桃精品视频 | 宅男噜噜噜66国产免费观看 | 黄色高清视频网站 | 国产乱淫av一区二区三区 | 精品一区二区久久久久 | 国产美女白浆 | 久久久免费观看完整版 | 午夜爱爱福利 | 免费淫视频 | 91久久精品一二三区 | 久草在线视频在线 | 久草在线视频福利 | 黄视频免费在线观看 | 欧美一级网 | 91午夜免费视频 | 国产69精品久久久久久野外 | 免费观看国产精品视频 | 亚洲欧美天堂 | 日韩黄色免费观看 | 免费久久精品 | 中文字幕在线观看免费 | 日本黄色一级电影 | 久草在线最新 | 黄色毛片视频在线观看 | 亚洲第一页中文字幕 | 日本综合久久 | 91福利免费观看 | 日本一道aⅴ不卡免费播放 视屏一区 | 亚洲一区二区中文 | 国产成人视屏 | lutube成人福利在线观看污 | 欧美女人天堂 | 一区二区三区视频在线播放 | 国产免费一级淫片a级中文 99国产精品自拍 | 美国av在线免费观看 | 大号bbwassbigav头交 |