Flutter:在 BottomNavigationBar 中更新 currentIndex 时,PageView 的性能滞后(但如果我不更新 currentIndex,则不会滞后)
Flutter: Laggy performance with PageView when updating the currentIndex in a BottomNavigationBar (but no lag if I don't update the currentIndex)
我正在创建一个带有脚手架的应用程序,其中包含:
- 正文中的
FutureBuilder
在加载数据时创建 PageView
作为其子项。
- 一个
BottomNavigationBar
与 PageView
同步以获得更直观的导航。
功能方面,一切正常。我可以在页面之间左右滑动,currentIndex
在 BottomNavigationBar
中正确更新,如果我点击 BottomNavigationBar
元素,PageView
将动画到正确的页面不出所料。
但是...在页面之间切换时,性能确实很差,即使在配置文件模式下也是如此。
经过大量调查,我确认延迟仅存在 如果我更新 BottomNavigationBar
的 currentIndex
。
如果我不更新 BottomNavigationBar
,在页面之间切换时动画仍然非常流畅,无论是在 PageView
上滑动还是点击 BottomNavigationBar
元素本身。
我还可以确认在使用 setState
和使用 Provider
时发生的情况完全相同。我真的希望这只是 setState
方法效率低下......但运气不好:(
对于 setState
实现,这就是我正在做的:
在 PageView
:
onPageChanged: (page) {
setState(() {
_selectedIndex = page;
});
}
在 BottomBarNavigation 上:
onTap: _onTappedBar,
currentIndex: _selectedIndex
及以下:
void _onTappedBar(int value) {
_pageController.animateToPage(value, duration: Duration(milliseconds: 500), curve: Curves.ease);
setState(() {
_selectedIndex = value;
});
}
如果我注释掉这两个 setState
方法,应用程序会再次变得非常光滑,我也可以正确使用 BottomNavigationBar
- 它只是不会更新所选项目。
有趣的是,如果我只注释掉两个 setState
方法(_selectedIndex = page;
和 _selectedIndex = value;
)中的行,但将方法留在那里,应用程序仍然尽管 setState
方法完全是空的并且没有更新任何东西,但仍然滞后 ...??
这是 Provider
版本:
在 PageView
:
onPageChanged: (page) {
Provider.of<BottomNavigationBarProvider>(context, listen: false).currentIndex = page;
}
在BottomBarNavigation
:
onTap: _onTappedBar,
currentIndex: Provider.of<BottomNavigationBarProvider>(context).currentIndex,
及以下:
void _onTappedBar(int value) {
Provider.of<BottomNavigationBarProvider>(context, listen: false).currentIndex = value;
pageController.animateToPage(value, duration: Duration(milliseconds: 500), curve: Curves.ease);
}
如前所述,与 setState
版本一样滞后 :(
知道是什么导致了这种滞后以及如何解决这个问题吗?我真的不知道还能尝试什么。
好的,所以我想我设法解决了它,同时还学习了有关 Flutter 的宝贵一课!
我在 setState
/ Provider
困境中走在了正确的轨道上 - 你 do 需要使用 Provider
(或其他状态管理解决方案)如果你想避免重建整个页面。
然而,这还不够。
为了利用该实现的模块化,您还需要在主小部件之外提取相关小部件(在本例中为整个 BottomNavigationBar
)。如果你不这样做,主页上的所有内容似乎仍会重建,即使只有一个小部件正在侦听 Provider
通知。
这就是我的 root_screen
的 build
方法的结构(为了便于阅读,简化了正文内容):
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: _pageController,
children: <Widget>[
HomeScreen(),
PerformanceScreen(),
SettingsScreen(),
],
onPageChanged: (page) {
Provider.of<BottomNavigationBarProvider>(context, listen: false).currentIndex = page;
},
);
bottomNavigationBar: MyBottomNavigationBar(onTapped: _onTappedBar),
);
}
请注意 bottomNavigationBar:
参数如何不再在此 root_screen
中定义。相反,我在一个单独的 Dart 文件中创建了一个新的 class(一个 StatelessWidget
),它接受一个 onTapped
函数作为参数,我从这里实例化它。
所说的 _onTappedBar
函数就在 root_screen
的此处定义,就在 build
方法下面:
void _onTappedBar(int value) {
Provider.of<BottomNavigationBarProvider>(context, listen: false).currentIndex = value;
_pageController.animateToPage(value, duration: Duration(milliseconds: 500), curve: Curves.ease);
}
这是包含新 MyBottomNavigationBar
class:
的独立 Dart 文件
class MyBottomNavigationBar extends StatelessWidget {
@override
const MyBottomNavigationBar({
Key key,
@required this.onTapped,
}) : super(key: key);
final Function onTapped;
Widget build(BuildContext context) {
return BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(
icon: Icon(Icons.trending_up), title: Text('Performance')),
BottomNavigationBarItem(
icon: Icon(Icons.settings), title: Text('Settings')),
],
onTap: onTapped,
currentIndex:
Provider.of<BottomNavigationBarProvider>(context).currentIndex,
);
}
}
同样为了完整性(并且因为我绝对需要知道),我尝试再次使用 setState
方法,同时将 BottomNavigationBar
保留在其新的单独文件中。我想了解是否仅提取小部件就足以解决问题,或者无论如何您仍然需要使用状态管理解决方案。
事实证明……这还不够!使用 setState 的性能再次变得糟糕,即使 BottomNavigationBar 小部件被提取到它自己的 class 文件中。
所以最重要的是,为了保持您的应用程序高效和动画流畅,请记住提取小部件并尽可能模块化您的 Flutter 代码,以及使用状态管理解决方案而不是 setState
。这似乎是避免不必要的重绘的唯一方法(而且您的代码显然会更清晰,更容易调试)。
我正在创建一个带有脚手架的应用程序,其中包含:
- 正文中的
FutureBuilder
在加载数据时创建PageView
作为其子项。 - 一个
BottomNavigationBar
与PageView
同步以获得更直观的导航。
功能方面,一切正常。我可以在页面之间左右滑动,currentIndex
在 BottomNavigationBar
中正确更新,如果我点击 BottomNavigationBar
元素,PageView
将动画到正确的页面不出所料。
但是...在页面之间切换时,性能确实很差,即使在配置文件模式下也是如此。
经过大量调查,我确认延迟仅存在 如果我更新 BottomNavigationBar
的 currentIndex
。
如果我不更新 BottomNavigationBar
,在页面之间切换时动画仍然非常流畅,无论是在 PageView
上滑动还是点击 BottomNavigationBar
元素本身。
我还可以确认在使用 setState
和使用 Provider
时发生的情况完全相同。我真的希望这只是 setState
方法效率低下......但运气不好:(
对于 setState
实现,这就是我正在做的:
在 PageView
:
onPageChanged: (page) {
setState(() {
_selectedIndex = page;
});
}
在 BottomBarNavigation 上:
onTap: _onTappedBar,
currentIndex: _selectedIndex
及以下:
void _onTappedBar(int value) {
_pageController.animateToPage(value, duration: Duration(milliseconds: 500), curve: Curves.ease);
setState(() {
_selectedIndex = value;
});
}
如果我注释掉这两个 setState
方法,应用程序会再次变得非常光滑,我也可以正确使用 BottomNavigationBar
- 它只是不会更新所选项目。
有趣的是,如果我只注释掉两个 setState
方法(_selectedIndex = page;
和 _selectedIndex = value;
)中的行,但将方法留在那里,应用程序仍然尽管 setState
方法完全是空的并且没有更新任何东西,但仍然滞后 ...??
这是 Provider
版本:
在 PageView
:
onPageChanged: (page) {
Provider.of<BottomNavigationBarProvider>(context, listen: false).currentIndex = page;
}
在BottomBarNavigation
:
onTap: _onTappedBar,
currentIndex: Provider.of<BottomNavigationBarProvider>(context).currentIndex,
及以下:
void _onTappedBar(int value) {
Provider.of<BottomNavigationBarProvider>(context, listen: false).currentIndex = value;
pageController.animateToPage(value, duration: Duration(milliseconds: 500), curve: Curves.ease);
}
如前所述,与 setState
版本一样滞后 :(
知道是什么导致了这种滞后以及如何解决这个问题吗?我真的不知道还能尝试什么。
好的,所以我想我设法解决了它,同时还学习了有关 Flutter 的宝贵一课!
我在 setState
/ Provider
困境中走在了正确的轨道上 - 你 do 需要使用 Provider
(或其他状态管理解决方案)如果你想避免重建整个页面。
然而,这还不够。
为了利用该实现的模块化,您还需要在主小部件之外提取相关小部件(在本例中为整个 BottomNavigationBar
)。如果你不这样做,主页上的所有内容似乎仍会重建,即使只有一个小部件正在侦听 Provider
通知。
这就是我的 root_screen
的 build
方法的结构(为了便于阅读,简化了正文内容):
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: _pageController,
children: <Widget>[
HomeScreen(),
PerformanceScreen(),
SettingsScreen(),
],
onPageChanged: (page) {
Provider.of<BottomNavigationBarProvider>(context, listen: false).currentIndex = page;
},
);
bottomNavigationBar: MyBottomNavigationBar(onTapped: _onTappedBar),
);
}
请注意 bottomNavigationBar:
参数如何不再在此 root_screen
中定义。相反,我在一个单独的 Dart 文件中创建了一个新的 class(一个 StatelessWidget
),它接受一个 onTapped
函数作为参数,我从这里实例化它。
所说的 _onTappedBar
函数就在 root_screen
的此处定义,就在 build
方法下面:
void _onTappedBar(int value) {
Provider.of<BottomNavigationBarProvider>(context, listen: false).currentIndex = value;
_pageController.animateToPage(value, duration: Duration(milliseconds: 500), curve: Curves.ease);
}
这是包含新 MyBottomNavigationBar
class:
class MyBottomNavigationBar extends StatelessWidget {
@override
const MyBottomNavigationBar({
Key key,
@required this.onTapped,
}) : super(key: key);
final Function onTapped;
Widget build(BuildContext context) {
return BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(
icon: Icon(Icons.trending_up), title: Text('Performance')),
BottomNavigationBarItem(
icon: Icon(Icons.settings), title: Text('Settings')),
],
onTap: onTapped,
currentIndex:
Provider.of<BottomNavigationBarProvider>(context).currentIndex,
);
}
}
同样为了完整性(并且因为我绝对需要知道),我尝试再次使用 setState
方法,同时将 BottomNavigationBar
保留在其新的单独文件中。我想了解是否仅提取小部件就足以解决问题,或者无论如何您仍然需要使用状态管理解决方案。
事实证明……这还不够!使用 setState 的性能再次变得糟糕,即使 BottomNavigationBar 小部件被提取到它自己的 class 文件中。
所以最重要的是,为了保持您的应用程序高效和动画流畅,请记住提取小部件并尽可能模块化您的 Flutter 代码,以及使用状态管理解决方案而不是 setState
。这似乎是避免不必要的重绘的唯一方法(而且您的代码显然会更清晰,更容易调试)。