9.0 KiB
因收到Google相关通知,网站将会择期关闭。相关通知内容
23 应用界面整合
1. 界面整合的需求分析
如下所示,在应用的底部添加导航栏,进行界面间的切换操作。下面从数据和界面的角度对该进行分析:
———————————————————— ————————————————————
当前界面中需要添加的数据有:
底部栏的文字、图标资源列表 底部栏的激活索引
在点击底部栏的按钮时,需要更新激活索引,并进行界面的重新构建。这里定义一个 MenuData 类,用于维护标签和图标数据:
class MenuData { // 标签 final String label;
// 图标数据 final IconData icon;
const MenuData({ required this.label, required this.icon, }); }
对于界面构建逻辑来说,这是一个上下结构,上面是内容区域,下面是底部导航栏。所以,可以通过 Column 组件上下排列,其中内容区域通过 Expanded 组件进行延展,内容组件根据激活的索引值构建不同的界面。
- 代码实现:第一版
Flutter 中提供了 BottomNavigationBar 组件可以展示底部栏,这里单独封装一个 AppBottomBar 组件用于维护底部栏的界面构建逻辑。其中需要传入激活索引、点击回调、菜单数据列表:
class AppBottomBar extends StatelessWidget { final int currentIndex; final List menus; final ValueChanged? onItemTap;
const AppBottomBar({ Key? key, this.onItemTap, this.currentIndex = 0, required this.menus, }) : super(key: key);
@override Widget build(BuildContext context) { return BottomNavigationBar( backgroundColor: Colors.white, onTap: onItemTap, currentIndex: currentIndex, elevation: 3, type: BottomNavigationBarType.fixed, iconSize: 22, selectedItemColor: Theme.of(context).primaryColor, selectedLabelStyle: const TextStyle(fontWeight: FontWeight.bold), showUnselectedLabels: true, showSelectedLabels: true, items: menus.map(_buildItemByMenuMeta).toList(), ); }
BottomNavigationBarItem _buildItemByMenuMeta(MenuData menu) { return BottomNavigationBarItem( label: menu.label, icon: Icon(menu.icon), ); } }
然后就是构建整体结构,这里创建一个 AppNavigation 组件来处理。由于激活索引数据需要在交互时改变,并重新构建界面,所以 AppNavigation 继承自 StatefulWidget,在状态类中处理界面构建和状态数据维护的逻辑。
class AppNavigation extends StatefulWidget { const AppNavigation({Key? key}) : super(key: key);
@override State createState() => _AppNavigationState(); }
class _AppNavigationState extends State { int _index = 0;
final List menus = const [ MenuData(label: '猜数字', icon: Icons.question_mark), MenuData(label: '电子木鱼', icon: Icons.my_library_music_outlined), MenuData(label: '白板绘制', icon: Icons.palette_outlined), ];
@override Widget build(BuildContext context) { return Column( children: [ Expanded( child: _buildContent(_index)), AppBottomBar( currentIndex: _index, onItemTap: _onChangePage, menus: menus, ) ], ); }
void _onChangePage(int index) { setState(() { _index = index; }); }
内容区域的构建使用 _buildContent 方法,根据不同的激活索引,返回创建不同的界面:
index = 0 时,构建猜数字界面; index = 1 时,构建电子木鱼界面; index = 2 时,构建白板绘制界面;
Widget _buildContent(int index) { switch(index){ case 0: return const GuessPage(); case 1: return const MuyuPage(); case 2: return const Paper(); default: return const SizedBox.shrink(); } } }
到这里就完成了点击底部导航,切换界面的功能,当前代码位置: navigation 。 但这种方式处理会有一些问题:伴随着界面的消失,状态类会被销毁;下次再到该界面时会重新初始化状态类,如下所示:
在绘制面板绘制 切换后,状态重置
- 状态类数据的保持
想要避免每次切换都会重置状态数据,大体上有三种解决方案:
1.使用 AutomaticKeepAliveClientMixin 对状态类进行保活,这种方案只能用于可滑动组件中。这里可以使用 PageView 组件来实现切页并保活的效果。 2.将状态数据提升到上层,比如将三个界面的状态数据都交由 _AppNavigationState 状态类维护。如果直接用这种方式,很容易造成一个超级大的类,来维护很多数据。其实状态管理工具,就是基于这种思路,将数据交由上层维护,同时提供了分模块处理数据的能力。 3.保持数据的持久性,比如将数据保存到本地文件或数据库,每次初始化时进行加载复现。这种方式处理起来比较麻烦,初始化加载数据也需要一点时间。但这种方式在界面不可见时,可以释放内存中的数据。
这里使用 方式 1 来处理是最简单的。在 _buildContent 方法中返回 PageView 组件,并将三个内容界面作为 children 入参,通过 PageController 来控制界面的切换。注意一点:将 physics 设置设置为 NeverScrollableScrollPhysics 可以禁止 PageView 的滑动,如果想要运行滑动切页,可以去除。
---->[_AppNavigationState]---- final PageController _ctrl = PageController();
Widget _buildContent() { return PageView( physics: const NeverScrollableScrollPhysics(), controller: _ctrl, children: const [ GuessPage(), MuyuPage(), Paper(), ], ); }
void _onChangePage(int index) { _ctrl.jumpToPage(index); setState(() { _index = index; }); }
另外如果期望某个状态类保活,需要让其混入 AutomaticKeepAliveClientMixin, 并覆写 wantKeepAlive 返回 true 。如下是对画板状态类的处理,其他两个同理:
在绘制面板绘制 切换后,状态保活
到这里,就将之前的三个小案例,集成到了一个应用中,并且在切换界面的过程中,可以保持状态数据不被重置。当前代码位置 navigation。
上面可以保证程序运行期间,各界面状态类的保活,但是当应用关闭之后,内存中的数据会被清空。再次进入应用时还是无法恢复到之前的状态,想要记住用户的信息,就必须对数据进行持久化的存储。比如存储为本地文件、数据库、网络数据等,下一篇将介绍数据的持久化存储。
- 优化一些缺陷
如下所示,左侧是 Column 组件上下排列,当键盘顶起之后,底部会留出一块空白,高为底部导航高度。想解决这个问题,使用 Scaffold 组件即可,它有一个 bottomNavigationBar 的插槽,不会被键盘顶起。
Column 结构 Scaffold 结构
这时,将 _AppNavigationState 的构建方法改为如下代码:
@override Widget build(BuildContext context) { return Scaffold( body: _buildContent(), bottomNavigationBar: AppBottomBar( currentIndex: _index, onItemTap: _onChangePage, menus: menus, ), ); }
下面以 AppBar 的主题介绍一下 Flutter 默认配置的能力。项目中希望所有的 AppBar 都是白色背景、状态类透明、标题居中、图标颜色、文字颜色为黑色。
如果每次使用 AppBar 组件就配置一次,那代码书写将会非常复杂。Flutter 在主题数据的功能,只要指定主题,其下节点中的对应组件,就会默认使用的配置数据。如下所示,在 MaterialApp 的 theme 入参中可以配置主题数据:
这样,以前使用 AppBar 的地方就不用再配置那么多信息了。比如电子木鱼界面的 AppBar 就可以清爽多了:
这里只是拿 AppBarTheme 举个例子,还有其他很多的主题可以配置,大家可以在以后慢慢了解。
- 本章小结
本章我们主要将之前的三个小案例整合到了一个项目中,通过底部导航栏 + PageView 实现界面间的切换。另外也就 State 的状态保活进行了简单地认识,这里只是程序运行期间,保证各界面状态类的活性,但是当应用关闭之后,内存中的数据会被清空。再次进入应用时还是无法恢复到之前的状态。
想要永久记住用户的信息,就必须对数据进行持久化的存储。比如存储为本地文件、数据库、网络数据等,在程序启动时进行加载,恢复状态数据。这是应用程序非常重要的一个部分,下一篇将介绍数据的持久化存储。