Как сохранить состояния виджетов во флаттере при навигации с помощью BottomNavigationBar?

В настоящее время я работаю над созданием приложения 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)
              )
            ],
          )
      )
    );
  }
}

10 ответов

Для сохранения состояния в 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,
          ),
        );
      }

Поздно к вечеринке, но у меня есть простое решение. Использовать 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);
    ...
  }
}

Я снял об этом видео здесь.

Используйте 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) {
    return Container();
  }
}

Вместо того, чтобы возвращать новый экземпляр каждый раз, когда вы запускаете 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)
                )
              ],
            )
        )
    );
  }
}

Или вы можете использовать виджеты, такие как PageView или же Stack добиться того же.

Надеюсь, это поможет!

Используйте " Виджет IndexedStack " с " Виджетом нижней панели навигации ", чтобы сохранить состояние экранов / страниц / виджета

Предоставьте список виджетов в IndexedStack и индекс виджетов, которые вы хотите показать, потому что IndexedStack показывает один виджет из списка одновременно.

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

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

Самый удобный способ, который я нашел, - использовать виджет PageStorage вместе с PageStorageBucket, который действует как постоянный слой ключевого значения.

Прочтите эту статью, чтобы получить красивое объяснение -> https://steemit.com/utopian-io/@tensor/persisting-user-interface-state-and-building-bottom-navigation-bars-in-dart-s-flutter-framework

Не используйте 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;
}

Краткое изложение решений: предложены два подхода

Решение 1. АвтоматическийKeepAliveClientMixin

      class SubPage extends StatefulWidget {
  @override
  State<SubPage> createState() => _SubPageState();
}

class _SubPageState extends State<SubPage> with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context); // Ensure that the mixin is initialized
    return Container();
  }

  @override
  bool get wantKeepAlive => true;
}
/*-------------------------------*/
class MainPage extends StatefulWidget {
  const MainPage({Key? key}) : super(key: key);

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  int currentBottom = 0;

  static const List<Widget> _page = [
    SubPage(),
    SubPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        bottomNavigationBar: BottomNavigationBar(
          currentIndex: currentBottom,
          onTap: (index) => setState(() {
            currentBottom = index;
          }),
          items: const [
            BottomNavigationBarItem(icon: Icon(Icons.home)),
            BottomNavigationBarItem(icon: Icon(Icons.settings))
          ],
        ),
        body: _page.elementAt(currentBottom));
  }
}

Решение 2. Индексированный стек

      class MainPage extends StatefulWidget {
  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  int _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _currentIndex,
        children: <Widget>[
          Page1(),
          Page2(),
          Page3(),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (int index) {
          setState(() {
            _currentIndex = index;
          });
        },
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Page 1',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: 'Page 2',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: 'Page 3',
          ),
        ],
      ),
    );
  }
}
/*--------------------------------*/
class Page1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      // Page 1 content
    );
  }
}
// Repeat the above for Page2 and Page3

правильный способ сохранить состояние вкладок на нижней панели навигации - обернуть все дерево PageStorage() виджет, который принимает PageStorageBucket bucket в качестве обязательного именованного параметра и для тех вкладок, для которых вы хотите сохранить его состояние, вместо уважаемых виджетов с PageStorageKey(<str_key>)тогда все готово !! вы можете увидеть более подробную информацию в этом ответе, на который я ответил несколько недель назад на один вопрос: /questions/53428616/flutter-bottomnavigator-s-indeksirovannyim-stekom/58558227#58558227

есть и другие альтернативы, такие как, но вы должны остерегаться его использования, я уже объяснил, что мы должны проявлять осторожность при использовании IndexedWidget() в данной ссылке ответ

удачи дружище ..

Это решение основано наCupertinoTabScaffoldреализация, которая не будет загружать ненужные экраны.

      import 'package:flutter/material.dart';

enum MainPage { home, profile }

class BottomNavScreen extends StatefulWidget {
  const BottomNavScreen({super.key});

  @override
  State<BottomNavScreen> createState() => _BottomNavScreenState();
}

class _BottomNavScreenState extends State<BottomNavScreen> {
  var currentPage = MainPage.home;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageSwitchingView(
        currentPageIndex: MainPage.values.indexOf(currentPage),
        pageCount: MainPage.values.length,
        pageBuilder: _pageBuilder,
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: MainPage.values.indexOf(currentPage),
        onTap: (index) => setState(() => currentPage = MainPage.values[index]),
        items: const [
          BottomNavigationBarItem(
            label: 'Home',
            icon: Icon(Icons.home),
          ),
          BottomNavigationBarItem(
            label: 'Profile',
            icon: Icon(Icons.account_circle),
          ),
        ],
      ),
    );
  }

  Widget _pageBuilder(BuildContext context, int index) {
    final page = MainPage.values[index];

    switch (page) {
      case MainPage.home:
        return ...
      case MainPage.profile:
        return ...
    }
  }
}

/// A widget laying out multiple pages with only one active page being built
/// at a time and on stage. Off stage pages' animations are stopped.
class PageSwitchingView extends StatefulWidget {
  const PageSwitchingView({
    super.key,
    required this.currentPageIndex,
    required this.pageCount,
    required this.pageBuilder,
  });

  final int currentPageIndex;
  final int pageCount;
  final IndexedWidgetBuilder pageBuilder;

  @override
  State<PageSwitchingView> createState() => _PageSwitchingViewState();
}

class _PageSwitchingViewState extends State<PageSwitchingView> {
  final List<bool> shouldBuildPage = <bool>[];

  @override
  void initState() {
    super.initState();
    shouldBuildPage.addAll(List<bool>.filled(widget.pageCount, false));
  }

  @override
  void didUpdateWidget(PageSwitchingView oldWidget) {
    super.didUpdateWidget(oldWidget);

    // Only partially invalidate the pages cache to avoid breaking the current
    // behavior. We assume that the only possible change is either:
    // - new pages are appended to the page list, or
    // - some trailing pages are removed.
    // If the above assumption is not true, some pages may lose their state.
    final lengthDiff = widget.pageCount - shouldBuildPage.length;
    if (lengthDiff > 0) {
      shouldBuildPage.addAll(List<bool>.filled(lengthDiff, false));
    } else if (lengthDiff < 0) {
      shouldBuildPage.removeRange(widget.pageCount, shouldBuildPage.length);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      fit: StackFit.expand,
      children: List<Widget>.generate(widget.pageCount, (int index) {
        final active = index == widget.currentPageIndex;
        shouldBuildPage[index] = active || shouldBuildPage[index];

        return HeroMode(
          enabled: active,
          child: Offstage(
            offstage: !active,
            child: TickerMode(
              enabled: active,
              child: Builder(
                builder: (BuildContext context) {
                  return shouldBuildPage[index] ? widget.pageBuilder(context, index) : Container();
                },
              ),
            ),
          ),
        );
      }),
    );
  }
}
Другие вопросы по тегам