Flutter bottomNavigator с индексированным стеком
Вопрос относительно перехода между вкладками с использованием индексированного стека для отображения соответствующей страницы. Я делаю это для того, чтобы сохранить прокрутку / состояние страниц. Это прекрасно работает. Я могу изменить текущую отображаемую страницу, щелкнув вкладку, а также могу перемещаться внутри каждой страницы (каждая страница имеет собственный навигатор). Это код для рендеринга страниц.
Widget build(BuildContext context) {
return IndexedStack(
index: widget.selectedIndex,
children: List.generate(widget._size, (index) {
return _buildNavigator(index);
}));
}
Проблема Mu в том, что IndexedStack строит сразу все страницы. На некоторых из моих страниц я хочу загружать данные из API, я хочу сделать это при первом построении виджета и только в том случае, если страница в данный момент видна. Как это сделать? в моей текущей реализации все виджеты создаются одновременно, поэтому все мои вызовы API вызываются даже для страниц, которые в данный момент не отрисованы.
Не уверен, что мне что-то здесь не хватает, или есть лучший способ реализовать нижнюю панель навигации. Кстати, я также использую Provider для управления состоянием.
6 ответов
Я столкнулся с той же проблемой. Мое решение состояло в том, чтобы сохранить список загруженных вкладок, а затем использовать его для создания списка
IndexedStack
дети внутри
Widget build(BuildContext context)
метод. Тогда в
onTap
метод
BottomNavigationBar
, Я звонил
setState()
чтобы обновить список загруженных вкладок, а также текущую индексную переменную. См. ниже:
class Index extends StatefulWidget {
const Index({Key? key}) : super(key: key);
@override
_IndexState createState() => _IndexState();
}
class _IndexState extends State<Index> {
int _currentIndex = 0;
List loadedPages = [0,];
@override
Widget build(BuildContext context) {
var screens = [
const FirstTab(),
loadedPages.contains(1) ? const SecondTab() : Container(),
loadedPages.contains(2) ? const ThirdTab() : Container(),
loadedPages.contains(3) ? const FourthTab() : Container(),
];
return Scaffold(
appBar: AppBar(
// AppBar
),
body: IndexedStack(
index: _currentIndex,
children: screens,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
var pages = loadedPages;
if (!pages.contains(index)) {
pages.add(index);
}
setState(() {
_currentIndex = index;
loadedPages = pages;
});
},
items: const [
// items
],
),
);
}
}
Теперь вызовы API на второй, третьей и четвертой вкладках не вызываются, пока не будет выполнен переход к.
Используйте PageView вместо IndexedStack
PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _pageController,
children: const [
Page1(),
Page2(),
Page3(),
],
),
вы можете переключать страницы с помощью pageController
_pageController.jumpToPage(0);
@tsahnar, да, я также столкнулся с той же проблемой, что и индексированный виджет с вызовом api, отображающий все виджеты, предоставленные его дочерним элементам, сразу, поэтому, когда отдельные страницы независимо извлекают данные из api, тогда возникает проблема
попробуй это :
- создать список виджетов, которые перемещаются по вашей панели навигации (каждый виджет с ключевым конструктором, где определить
PageStorageKey(<key>)
для каждого виджета)
var widgetList = <Widget>[
Page01(key:PageStorageKey(<key>)),
Page02(key:PageStorageKey(<key>))
];
- затем создайте
PageStorageBucket()
который хранит состояние ваших виджетов и предоставляет его в будущем всякий раз, когда он нам понадобится в течение жизни приложения, даже если виджет удаляется из дерева
final _bucket = PageStorageBucket();
потом
var currentIndex = 0;
затем на вашей основной базовой странице, где нижняя панель навигации существует в вашем теле вместо
IndexedStack
использоватьbody:PageStorage(bucket: _bucket,child:widgetsList[currentIndex])
и создайте нижнюю панель навигации на этой главной базовой странице, а затем на вкладке значка панели навигации управляйте предварительным состоянием индексной страницы с помощью
setState((){})
текущее состояние к
currentIndex
он должен решить вашу проблему, но через год будет уже слишком поздно
ты нашел решение?
Я обнаружил ту же проблему, что и вы, и я попробовал этот обходной путь (я еще не обнаружил с ним никаких проблем)
. Идея состоит в том, чтобы создать новый виджет для управления состоянием видимости виджетов, которые вызывали api, и создавать его, когда он стало видно.
В вашей
IndexedStack
заверните свой
_buildNavigator
с таким виджетом:
class BaseTabPage extends StatefulWidget {
final bool isVisible;
final Widget child;
BaseTabPage({Key key, this.child, this.isVisible});
@override
State<StatefulWidget> createState() => _BaseTabPageState();
}
/*
This state is to prevent tab pages creation before show them. It'll only add the
child widget to the widget tree when isVisible is true at least one time
i.e. if the child widget makes an api call, it'll only do when isVisible is true
for the first time
*/
class _BaseTabPageState extends State<BaseTabPage> {
bool alreadyShowed = false;
@override
Widget build(BuildContext context) {
alreadyShowed = widget.isVisible ? true : alreadyShowed;
return alreadyShowed ? widget.child : Container();
}
}
Например, в моем коде у меня есть что-то вроде этого для создания навигаторов для каждой вкладки, где
_selectedIndex
это выбранное положение и
tabPosition
позиция этой страницы в
BottomNavigationBar
Widget _buildTabPage(int tabPosition) {
final visibility = _selectedIndex == tabPosition;
return BaseTabPage(
isVisible: visibility,
child: _buildNavigator(tabPosition),
);
}
Благодаря этому у меня есть логика вызова API полностью в дочерних виджетах, а нижняя навигация ничего о них не знает. Дай мне знать, если ты увидишь что-то не так, потому что я новичок в флаттере.
Если мы представим, что страница 2 вашего IndexedStack - это страница, на которой вам нужны данные вызова API...
Чтобы вызвать API только на странице 2, выполните вызов API в обработчике onTap BottomNavigationBar:
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
items: []
onTap: (index) {
if (index == 2 && !_apiCallCompleted) {
makeApiCall();
}
setState(() {
_currentIndex = index;
});
},
),
Здесь обработчик onTap ищет индекс "2" и то, что вы еще не выполнили вызов API.
Имейте логическое значение внутри вашего метода вызова API, установив _apiCallCompleted = true.
В ответ на ваш комментарий о желании сохранить вызов API внутри виджета, вы можете передать индекс IndexedStack каждому виджету.
Каждый виджет будет проверять, является ли он "активным" / видимым виджетом, используя предоставленный индекс. Если active/visible и apiCallDone == false, выполните вызов API.
Изменить. В предыдущей версии второго предложения я помещал вызов API вbuild()
случайно, чего никогда не следует делать, так как его можно вызывать до 60 раз в секунду. initState()
это место, где должна происходить инициализация / доступ к данным, если таковой имеется. Поскольку была логическая проверка, чтобы увидеть, был ли уже выполнен вызов API, это не было бы катастрофой, но в любом случае это не лучшая практика.
child: IndexedStack(
index: _currentIndex,
children: [
WidgetOne(_currentIndex),
WidgetTwo(_currentIndex)
],
),
class WidgetOne extends StatefulWidget {
final int index;
WidgetOne(this.index);
@override
_WidgetOneState createState() => _WidgetOneState();
}
class _WidgetOneState extends State<WidgetOne> {
bool apiCallDone = false;
@override
void initState() {
super.initState();
if (widget.index == 1) {
// todo - make API call
apiCallDone = true;
}
}
@override
Widget build(BuildContext context) {
// todo - display results of API call
}
}
class WidgetTwo extends StatefulWidget {
final int index;
WidgetTwo(this.index);
@override
_WidgetTwoState createState() => _WidgetTwoState();
}
class _WidgetTwoState extends State<WidgetTwo> {
bool apiCallDone = false;
@override
void initState() {
super.initState();
if (widget.index == 2) {
// todo - make API call
apiCallDone = true;
}
}
@override
Widget build(BuildContext context) {
// todo - display results of API call
}
}
Flutter будет создавать все виджеты внутри стека, поэтому, если ваши страницы выполняют вызов API внутри initState, он будет запускаться при сборке.
Что вы можете сделать, так это создать отдельную функцию для вызова API. Затем вызовите функцию из навигатора или из управления состоянием.
Надеюсь, это поможет вам понять, как это реализовать.