learn-tech/专栏/Flutter入门教程/23应用界面整合.md
2024-10-16 00:01:16 +08:00

303 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
23 应用界面整合
1. 界面整合的需求分析
如下所示,在应用的底部添加导航栏,进行界面间的切换操作。下面从数据和界面的角度对该进行分析:
————————————————————
————————————————————
当前界面中需要添加的数据有:
底部栏的文字、图标资源列表
底部栏的激活索引
在点击底部栏的按钮时,需要更新激活索引,并进行界面的重新构建。这里定义一个 MenuData 类,用于维护标签和图标数据:
class MenuData {
// 标签
final String label;
// 图标数据
final IconData icon;
const MenuData({
required this.label,
required this.icon,
});
}
对于界面构建逻辑来说,这是一个上下结构,上面是内容区域,下面是底部导航栏。所以,可以通过 Column 组件上下排列,其中内容区域通过 Expanded 组件进行延展,内容组件根据激活的索引值构建不同的界面。
2. 代码实现:第一版
Flutter 中提供了 BottomNavigationBar 组件可以展示底部栏,这里单独封装一个 AppBottomBar 组件用于维护底部栏的界面构建逻辑。其中需要传入激活索引、点击回调、菜单数据列表:
class AppBottomBar extends StatelessWidget {
final int currentIndex;
final List<MenuData> menus;
final ValueChanged<int>? 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<AppNavigation> createState() => _AppNavigationState();
}
class _AppNavigationState extends State<AppNavigation> {
int _index = 0;
final List<MenuData> 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 。 但这种方式处理会有一些问题:伴随着界面的消失,状态类会被销毁;下次再到该界面时会重新初始化状态类,如下所示:
在绘制面板绘制
切换后,状态重置
3. 状态类数据的保持
想要避免每次切换都会重置状态数据,大体上有三种解决方案:
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。
上面可以保证程序运行期间,各界面状态类的保活,但是当应用关闭之后,内存中的数据会被清空。再次进入应用时还是无法恢复到之前的状态,想要记住用户的信息,就必须对数据进行持久化的存储。比如存储为本地文件、数据库、网络数据等,下一篇将介绍数据的持久化存储。
4. 优化一些缺陷
如下所示,左侧是 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 举个例子,还有其他很多的主题可以配置,大家可以在以后慢慢了解。
5. 本章小结
本章我们主要将之前的三个小案例整合到了一个项目中,通过底部导航栏 + PageView 实现界面间的切换。另外也就 State 的状态保活进行了简单地认识,这里只是程序运行期间,保证各界面状态类的活性,但是当应用关闭之后,内存中的数据会被清空。再次进入应用时还是无法恢复到之前的状态。
想要永久记住用户的信息,就必须对数据进行持久化的存储。比如存储为本地文件、数据库、网络数据等,在程序启动时进行加载,恢复状态数据。这是应用程序非常重要的一个部分,下一篇将介绍数据的持久化存储。