开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情
前言
常见的app基本上都是用tab动态切换页面,本篇文章我们通过简单地动手实现一个常见app+设置通用主题+字体来加深对Flutter的实践学习
在安卓中我们如果要实现上述效果,通常需要搭配底部的button list 或者是radiogroup 加上顶部的fragment实现状态的切换。
那么flutter是如何实现这一逻辑的呢?
一、BottomNavigationBar
BottomNavigationBar({
super.key,
required this.items,
this.onTap,
this.currentIndex = 0,
this.elevation,
this.type,
Color? fixedColor,
this.backgroundColor,
this.iconSize = 24.0,
Color? selectedItemColor,
this.unselectedItemColor,
this.selectedIconTheme,
this.unselectedIconTheme,
this.selectedFontSize = 14.0,
this.unselectedFontSize = 12.0,
this.selectedLabelStyle,
this.unselectedLabelStyle,
this.showSelectedLabels,
this.showUnselectedLabels,
this.mouseCursor,
this.enableFeedback,
this.landscapeLayout,
})
1.1 参数介绍
| 属性 | 介绍 |
|---|---|
| items | 必填项,设置各个按钮 |
| onTap | 点击事件 |
| currentIndex | 当前选中item下标 |
| elevation | 控制阴影高度,默认为8.0 |
| type | BottomNavigationBarType,默认 fixed,设置为 shifting 时,建议设置选中样式,和为选中样式,提供一个特殊动画 |
| fixedColor | 选中item填充色 |
| backgroundColor | 整个BottomNavigationBar背景色 |
| iconSize | 图标大小,默认24.0 |
| selectedItemColor | 选中title填充色 |
| unselectedItemColor | 未选中title填充色 |
| selectedIconTheme | 选中item图标主题 |
| unselectedIconTheme | 未选中item图标主题 |
| selectedFontSize | 选中 title 字体大小,默认14.0 |
| unselectedFontSize | 未选中title字体大小,默认12.0 |
| selectedLabelStyle | 选中 title 样式 TextStyle |
| unselectedLabelStyle | 未选中 title 样式 TextStyle |
| showSelectedLabels | 是否展示选中 title,默认为true |
| showUnselectedLabels | 是否展示未选中 title,默认为true |
| mouseCursor | 鼠标悬停,Web 开发可以了解 |
二、前期准备
实现状态页面的tab切换,我们需要提前准备好底部的tab图标,可以从iconFont网站自行搜索下载对应两种色值的图标(选中态+未选中态)。
将icon图标放入assets指定素材的所在目录,注意后续需要在pubspec.yaml中指定图标所在目录,同时使用pub get命令更新资源,如果图标文件是png格式,指定整个文件夹即可,如果图标文件是jpg格式,需要指定文件名及后缀。
assets:
- images/
- images/a.jpg
- images/b.jpg
三、项目结构
3.1、四个状态页
首先我们需要对应建立四个状态页面对应tab的切换,由于逻辑较简单,四个tab页都是stateless的.
import 'package:flutter/material.dart';
class TabMainPage extends StatelessWidget {
const TabMainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('我是主页'),
),
);
}
}
其他三个页面都利用中间的文本进行标识
3.2、AppHomePage
首页用于装载BottomNavigationBar控件,同时实现四个状态页的切换
import 'package:fftest1/tab_dynamic.dart';
import 'package:fftest1/tab_main.dart';
import 'package:fftest1/tab_mine.dart';
import 'package:fftest1/tab_msg.dart';
import 'package:flutter/material.dart';
class AppHomePage extends StatefulWidget {
const AppHomePage({Key? key}) : super(key: key);
@override
State<AppHomePage> createState() => _AppHomePageState();
}
class _AppHomePageState extends State<AppHomePage> {
int _index = 0;
List<Widget> _homeWidgets = [
TabMainPage(),
TabDynamicPage(),
TabMsgPage(),
TabMinePage(),
];
void _onBottomNavigationBarTapped(index) {
setState(() {
_index = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter项目实战'),
),
body: IndexedStack(
index: _index,
children: _homeWidgets,
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
currentIndex: _index,
onTap: _onBottomNavigationBarTapped,
items: [_getBottomNavItem('首页', 'images/ic_tab_1_unselect.png',
'images/ic_tab_1_select.png', 0),
_getBottomNavItem('动态', 'images/ic_tab_2_unselect.png',
'images/ic_tab_2_select.png', 1),
_getBottomNavItem('消息', 'images/ic_tab_3_unselect.png',
'images/ic_tab_3_select.png', 2),
_getBottomNavItem('我的', 'images/ic_tab_4_unselect.png',
'images/ic_tab_4_select.png', 3),
],
),
);
}
BottomNavigationBarItem _getBottomNavItem(
String title, String normalIcon, String pressedIcon, int index) {
return BottomNavigationBarItem(
icon: _index == index
? Image.asset(
pressedIcon,
width: 32,
height: 28,
)
: Image.asset(
normalIcon,
width: 32,
height: 28,
),
label: title);
}
}
可以看到在主页组件里控制的状态变量是_index,首页的body使用了IndexedStack
3.3 IndexedStack
IndexedStack是一个管理页面显示层级的容器,使用index属性可以确定当前容器里哪个页面在最顶上.
IndexedStack的children要求是一个Widget数组,也就是我们提前声明的四个状态页数组
可以看到,_onBottomNavigationBarTapped方法中定义了_index的修改,当BarItem的图标被点击后,此时会回调onTap属性指定的方法,通过这个方式控制IndexedStack页面的层级切换.
3.4 MainApp
main.dart作为app的入口,指定了app的首页和一些基本配置,所以尽量这块的代码精简一些.
import 'package:flutter/material.dart';
import 'app_main.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App框架',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AppHomePage(),
);
}
}
四、一些小优化
为了实现app内一些色调和字体的统一,我们可以在main.dart里进行一些统一的配置.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App框架',
theme: ThemeData(
primarySwatch: Colors.blue,
accentColor: Colors.blue[600],
textTheme: TextTheme(
headline1: TextStyle(
fontSize: 36.0, fontWeight: FontWeight.bold, color: Colors.white),
headline2: TextStyle(
fontSize: 32.0, fontWeight: FontWeight.w400, color: Colors.white),
headline3: TextStyle(
fontSize: 28.0, fontWeight: FontWeight.w400, color: Colors.white),
headline4: TextStyle(
fontSize: 24.0, fontWeight: FontWeight.w400, color: Colors.white),
headline6: TextStyle(
fontSize: 14.0, fontWeight: FontWeight.w200, color: Colors.white),
bodyText1: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w200,
),
),
fontFamily: 'Georgia',
),
home: AppHomePage(),
);
}
}
主要在MaterialApp的theme属性中,声明了字体颜色和字体大小等通用属性
- brightness:app模式切换,包括dark和light,dark对应的是深色模式,light对应浅色模式。后面可以用这个参数实现app夜间模式切换
- primaryColor:主色调,设置后导航栏就会变成主色调颜色.
- accentColor:辅助色
- textTheme:文字主体
- fontFamily:字体族
此时就可以通过Theme.of(context)来获取app主题,再获得相应的字体样式了.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('我是动态页面', style: Theme.of(context).textTheme.bodyText1),
),
);
}
color也可以同样声明
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter项目实战'),
),
body: IndexedStack(
index: _index,
children: _homeWidgets,
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
currentIndex: _index,
onTap: _onBottomNavigationBarTapped,
selectedItemColor: Theme.of(context).primaryColor,
items: [_getBottomNavItem('首页', 'images/ic_tab_1_unselect.png',
'images/ic_tab_1_select.png', 0),
_getBottomNavItem('动态', 'images/ic_tab_2_unselect.png',
'images/ic_tab_2_select.png', 1),
_getBottomNavItem('消息', 'images/ic_tab_3_unselect.png',
'images/ic_tab_3_select.png', 2),
_getBottomNavItem('我的', 'images/ic_tab_4_unselect.png',
'images/ic_tab_4_select.png', 3),
],
),
);
}
当我们需要规范app内统一配置文件时,就可以通过在MaterialApp中统一进行配置.
eg.app主色调从蓝色切换成绿色