【问题标题】:How to preserve widget states in flutter, when navigating using BottomNavigationBar?使用BottomNavigationBar导航时如何在颤动中保留小部件状态?
【发布时间】:2018-09-01 12:05:48
【问题描述】:

我目前正在构建一个 Flutter 应用程序,该应用程序将在使用 BottomNavigationBar 时从一个屏幕导航到另一个屏幕并再次返回时保留状态。就像它在 Spotify 移动应用程序中工作一样;如果您在其中一个主屏幕上向下导航到导航层次结构中的某个级别,则通过底部导航栏更改屏幕,然后返回旧屏幕,将保留用户在该层次结构中的位置,包括保留国家。

我一直在碰壁,尝试了各种不同的事情,但没有成功。

我想知道如何防止pageChooser() 中的页面在用户点击BottomNavigationBar 项后切换时重建自身,而是保留他们已经发现的状态(页面都是有状态的小部件) .

import 'package:flutter/material.dart';
import './page_plan.dart';
import './page_profile.dart';
import './page_startup_namer.dart';

void main() => runApp(new Recipher());

class Recipher extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Pages();
  }
}

class Pages extends StatefulWidget {
  @override
  createState() => new PagesState();
}

class PagesState extends State<Pages> {
  int pageIndex = 0;


  pageChooser() {
    switch (this.pageIndex) {
      case 0:
        return new ProfilePage();
        break;

      case 1:
        return new PlanPage();
        break;

      case 2:
        return new StartUpNamerPage(); 
        break;  

      default:
        return new Container(
          child: new Center(
            child: new Text(
              'No page found by page chooser.',
              style: new TextStyle(fontSize: 30.0)
              )
            ),
          );     
    }
  }

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        body: pageChooser(),
        bottomNavigationBar: new BottomNavigationBar(
          currentIndex: pageIndex,
          onTap: (int tappedIndex) { //Toggle pageChooser and rebuild state with the index that was tapped in bottom navbar
            setState(
              (){ this.pageIndex = tappedIndex; }
              ); 
            },
          items: <BottomNavigationBarItem>[
            new BottomNavigationBarItem(
              title: new Text('Profile'),
              icon: new Icon(Icons.account_box)
              ),
              new BottomNavigationBarItem(
                title: new Text('Plan'),
                icon: new Icon(Icons.calendar_today)
              ),
                new BottomNavigationBarItem(
                title: new Text('Startup'),
                icon: new Icon(Icons.alarm_on)
              )
            ],
          )
      )
    );
  }
}

【问题讨论】:

  • 使用 Stack 保留每个页面。这可能会有所帮助:stackoverflow.com/questions/45235570/…
  • 很好,谢谢。这行得通。实际上我已经尝试过使用 IndexedStack,但我现在发现这与 Offstage 和 Tickermode 的工作方式不同。

标签: dart flutter


【解决方案1】:

为了保持BottomNavigationBar中的状态,你可以使用IndexedStack

    @override
      Widget build(BuildContext context) {
        return Scaffold(
          bottomNavigationBar: BottomNavigationBar(
            onTap: (index) {
              setState(() {
                current_tab = index;
              });
            },
            currentIndex: current_tab,
            items: [
              BottomNavigationBarItem(
                ...
              ),
              BottomNavigationBarItem(
                ...
              ),
            ],
          ),
          body: IndexedStack(
            children: <Widget>[
              PageOne(),
              PageTwo(),
            ],
            index: current_tab,
          ),
        );
      }

【讨论】:

  • 但它有性能问题。 IndexedStack 最初加载所有选项卡,这是不必要的。
  • 这应该是公认的答案。效果很好。 @ArtBelch:将此答案标记为正确答案是对我们所有人的帮助。 :)
  • @JayaPrakash 你有没有找到一个解决方案来避免一次构建所有堆栈?
  • @anoop4real 我正在使用IndexedStack 来保留选项卡的所有小部件状态,并且我保留每个选项卡子引用的引用。通过选项卡更改回调,我曾经在选项卡子引用的帮助下调用子小部件内的任何方法。如果您需要我的回答,请发布一个问题,我会为您解答。
  • @JayaPrakash 关于性能,看看这个解决方案:stackoverflow.com/questions/66404759/…
【解决方案2】:

聚会迟到了,但我有一个简单的解决方案。PageView 小部件与AutomaticKeepAliveClinetMixin 结合使用。

它的美妙之处在于它在您点击它之前不会加载任何标签。


包含BottomNavigationBar的页面:

var _selectedPageIndex;
List<Widget> _pages;
PageController _pageController;

@override
void initState() {
  super.initState();

  _selectedPageIndex = 0;
  _pages = [
    //The individual tabs.
  ];

  _pageController = PageController(initialPage: _selectedPageIndex);
}

@override
void dispose() {
  _pageController.dispose();

  super.dispose();
}

@override
Widget build(BuildContext context) {
  ...
    body: PageView(
      controller: _pageController,
      physics: NeverScrollableScrollPhysics(),
      children: _pages,
    ),
   bottomNavigationBar: BottomNavigationBar(
      ...
      currentIndex: _selectedPageIndex,
      onTap: (selectedPageIndex) {
        setState(() {
          _selectedPageIndex = selectedPageIndex;
          _pageController.jumpToPage(selectedPageIndex);
        });
      },
  ...
}

单个标签:

class _HomeState extends State<Home> with AutomaticKeepAliveClientMixin<Home> {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    //Notice the super-call here.
    super.build(context);
    ...
  }
}

我已经制作了一个关于它的视频here

【讨论】:

  • 非常感谢!所有 _pages 一开始并没有像 IndexedStack 那样被加载,而是被保存了。
  • 完美运行...每个页面单独加载并保持状态。
  • 整洁 ? 我在找这个!希望底部导航栏不要每次都重绘。
  • 迄今为止最好的解决方案,谢谢! IndexedStack 在启动时加载所有内容。它只会在您导航到页面时立即加载,并且只会保留具有wantKeepAlive=true 的页面的状态。如果您只想保留其中一些而不是全部的状态,这非常有用。
【解决方案3】:

使用AutomaticKeepAliveClientMixin 强制您的标签内容不被释放。

class PersistantTab extends StatefulWidget {
  @override
  _PersistantTabState createState() => _PersistantTabState();
}

class _PersistantTabState extends State<PersistantTab> with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    return Container();
  }

  // Setting to true will force the tab to never be disposed. This could be dangerous.
  @override
  bool get wantKeepAlive => true;
}

为确保您的选项卡在不需要持久化时被释放,请让wantKeepAlive 返回一个类变量。您必须调用updateKeepAlive() 来更新保活状态。

动态保持活动示例:

// class PersistantTab extends StatefulWidget ...

class _PersistantTabState extends State<PersistantTab>
    with AutomaticKeepAliveClientMixin {
  bool keepAlive = false;

  @override
  void initState() {
    doAsyncStuff();
  }

  Future doAsyncStuff() async {
    keepAlive = true;
    updateKeepAlive();
    // Keeping alive...

    await Future.delayed(Duration(seconds: 10));

    keepAlive = false;
    updateKeepAlive();
    // Can be disposed whenever now.
  }

  @override
  bool get wantKeepAlive => keepAlive;

  @override
  Widget build(BuildContext context) {
    super.build();
    return Container();
  }
}

【讨论】:

  • 我们应该符合AutomaticKeepAliveClientMixin的父窗口小部件还是按下按钮时将显示的窗口小部件?
  • 来自文档:their build methods must call super.build,您的答案中缺少。
【解决方案4】:

不要在每次运行 pageChooser 时返回新实例,而是创建一个实例并返回相同的实例。

例子:

class Pages extends StatefulWidget {
  @override
  createState() => new PagesState();
}

class PagesState extends State<Pages> {
  int pageIndex = 0;

  // Create all the pages once and return same instance when required
  final ProfilePage _profilePage = new ProfilePage(); 
  final PlanPage _planPage = new PlanPage();
  final StartUpNamerPage _startUpNamerPage = new StartUpNamerPage();


  Widget pageChooser() {
    switch (this.pageIndex) {
      case 0:
        return _profilePage;
        break;

      case 1:
        return _planPage;
        break;

      case 2:
        return _startUpNamerPage;
        break;

      default:
        return new Container(
          child: new Center(
              child: new Text(
                  'No page found by page chooser.',
                  style: new TextStyle(fontSize: 30.0)
              )
          ),
        );
    }
  }

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
        home: new Scaffold(
            body: pageChooser(),
            bottomNavigationBar: new BottomNavigationBar(
              currentIndex: pageIndex,
              onTap: (int tappedIndex) { //Toggle pageChooser and rebuild state with the index that was tapped in bottom navbar
                setState(
                        (){ this.pageIndex = tappedIndex; }
                );
              },
              items: <BottomNavigationBarItem>[
                new BottomNavigationBarItem(
                    title: new Text('Profile'),
                    icon: new Icon(Icons.account_box)
                ),
                new BottomNavigationBarItem(
                    title: new Text('Plan'),
                    icon: new Icon(Icons.calendar_today)
                ),
                new BottomNavigationBarItem(
                    title: new Text('Startup'),
                    icon: new Icon(Icons.alarm_on)
                )
              ],
            )
        )
    );
  }
}

或者您可以使用PageViewStack 等小部件来实现相同的目的。

希望有帮助!

【讨论】:

  • 感谢您的回答。我使用结合 Offstage 和 Tickermode 的 Stack 解决了这个问题。 :-))
  • @ArtBelch,如果答案满足您的问题,请将其标记为正确:)
  • @ArtBelch 你能分享你的解决方案吗?
【解决方案5】:

使用“IndexedStack Widget”和“Bottom Navigation Bar Widget”来保持 Screens/pages/Widget 的状态

IndexedStack 提供 Widget 列表和您要显示的小部件索引,因为 IndexedStack 一次显示列表中的单个小部件。

final List<Widget> _children = [
    FirstClass(),
    SecondClass()
  ];

Scaffold(
  body: IndexedStack(
    index: _selectedPage,
    children: _children,
  ),
  bottomNavigationBar: BottomNavigationBar(
    ........
    ........
  ), 
);

【讨论】:

    【解决方案6】:

    我发现最方便的方法是使用 PageStorage 小部件和 PageStorageBucket,它充当键值持久层。

    阅读这篇文章以获得美丽的解释 -> https://steemit.com/utopian-io/@tensor/persisting-user-interface-state-and-building-bottom-navigation-bars-in-dart-s-flutter-framework

    【讨论】:

    • 在这种情况下,我的页面仍在加载数据
    【解决方案7】:

    在底部导航栏中保留选项卡状态的正确方法是使用 PageStorage() 小部件包装整个树,该小部件将 PageStorageBucket bucket 作为必需的命名参数,并且对于那些您想要保留其状态的选项卡,通过那些受尊重的带有PageStorageKey(&lt;str_key&gt;) 的小部件然后你就完成了!你可以在这个答案中看到更多细节,几周前我已经回答了一个问题:https://stackoverflow.com/a/68620032/11974847

    还有其他选择,例如IndexedWidget(),但您在使用它时应该小心,我已经解释过,在给定的链接答案中使用IndexedWidget() 时我们应该谨慎

    祝你好运……

    【讨论】:

      【解决方案8】:

      不要使用 IndexStack Widget,因为它会将所有选项卡一起实例化,并假设如果所有选项卡都在发出网络请求,那么回调将被弄乱,最后一个 API 调用选项卡可能会控制回调。

      为您的有状态小部件使用 AutomaticKeepAliveClientMixin,这是实现它的最简单方法,无需将所有选项卡一起实例化。

      我的代码具有向调用选项卡提供相应响应的接口,我通过以下方式实现它。

      创建您的有状态小部件

      class FollowUpsScreen extends StatefulWidget {
        FollowUpsScreen();
              
        @override
        State<StatefulWidget> createState() {
          return FollowUpsScreenState();
        }
      }
              
      class FollowUpsScreenState extends State<FollowUpsScreen>
           with AutomaticKeepAliveClientMixin<FollowUpsScreen>
                  implements OperationalControls {
          
        @override
        Widget build(BuildContext context) {
        //do not miss this line
          super.build(context);
          return .....;
        }
      
        @override
        bool get wantKeepAlive => true;
      }
      

      【讨论】:

        猜你喜欢
        • 2019-05-09
        • 2020-07-22
        • 1970-01-01
        • 2022-10-24
        • 1970-01-01
        • 2019-07-28
        • 2021-06-21
        • 1970-01-01
        • 2020-08-06
        相关资源
        最近更新 更多