底部导航栏制作-1
工作中最简单的一个APP也要具备一个功能,就是底部导航栏,你很难找出没有底部导航栏的应用。这么刚需的功能,那就从这里开始吧。
主入口文件的编写
首先我们先写一个主入口文件,这个文件只是简单的APP通用结构,最主要的是要引入自定义的BottomNavigationWidget组件。
main.dart代码如下
import 'package:flutter/material.dart';
import 'bottom_navigation_widget.dart';
void main()=> runApp(new MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title:'Flutter bottomNavigationBar',
theme:ThemeData.light(),
home:BottomNavigationWidget()
);
}.
}
注意的是BottomNaivgationWidget这个组件还没有编写,所以现在会报错。
StatefulWidget 讲解
在编写BottomNaivgationWidget组件前,我们需要简单了解一下什么是StatefulWidget.
StatefulWidget具有可变状态(state)的窗口组件(widget)。使用这个要根据变化状态,调整State值。
在lib目录下,新建一个bottom_navigation_widget.dart文件。
它的初始化和以前使用的StatelessWidget不同,我们在VSCode中直接使用快捷方式生成代码(直接在VSCode中输入stful): name 输入控件名称
class name extends StatefulWidget {
_nameState createState() => _nameState();
}
class _nameState extends State<name> {
Widget build(BuildContext context) {
return Container(
child: child,
);
}
}
上面的代码可以清楚的看到,使用StatefulWidget分为两个部分,第一个部分是继承与StatefullWidget,第二个部分是继承于State.其实State部分才是我们的重点,主要的代码都会写在State中。
BottomNaivgationWidget自定义
接下来我们就要创建BottomNaivgationWidget这个Widget了,只是建立一个底部导航。
import 'package:flutter/material.dart';
class BottomNavigationWidget extends StatefulWidget {
_BottomNavigationWidgetState createState() => _BottomNavigationWidgetState();
}
class _BottomNavigationWidgetState extends State<BottomNavigationWidget> {
final _BottomNavigationColor = Colors.blue;
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon:Icon(
Icons.home,
color:_BottomNavigationColor,
),
label: '首页'
),
BottomNavigationBarItem(
icon:Icon(
Icons.email,
color:_BottomNavigationColor,
),
label: '邮件'
),
BottomNavigationBarItem(
icon:Icon(
Icons.pages,
color:_BottomNavigationColor,
),
label: "分页"
),
BottomNavigationBarItem(
icon:Icon(
Icons.airplay,
color:_BottomNavigationColor,
),
label: '设置'
),
],
type:BottomNavigationBarType.fixed
),
);
}
}
这时候我们就可以使用flutter run 来进行查看代码了,效果已经出现,在APP的页面上已经出现了一个底部导航栏,只不过现在还点击还没有什么效果。
下面让点击后可以切换页面,完成第一个小实例效果。
底部导航栏制作-2
继续把底部导航的完整效果做出来,我们先把几个要导航的页面创建出来。
子页面的编写
子页面我们就采用最简单的编写了,只放入一个AppBar和一个Center,然后用Text Widget表明即可。
先来写一个HomeScreen组件,新建一个pages目录,然后在目录下面新建home_screen.dart文件。
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar:AppBar(
title: Text('HOME'),
),
body:Center(
child: Text('HOME'),
)
);
}
}
有了这个文件剩下的文件就可以复制粘贴,然后改少量的代码来完成了。
分别建立:
email_screen.dart
pages_screen.dart
airplay_screen.dart
这些都是导航要用的子页面,有了这些页面,我们才能继续编写代码。
重写initState()方法
我们要重新initState()方法,把刚才做好的页面进行初始化到一个Widget数组中。有了数组就可以根据数组的索引来切换不同的页面了。这是现在几乎所有的APP采用的方式。
代码如下:
List<Widget> lit = [];
initState() {
lit
..add(HomeScreen())
..add(EmailScreen())
..add(PagesScreen())
..add(AirplayScreen());
super.initState();
}
这里的…add()是Dart语言的…语法,如果你学过编程模式,你一定听说过建造者模式,简单来说就是返回调用者本身。这里list后用了…add(),还会返回list,然后就一直使用…语法,能一直想list里增加widget元素。 最后我们调用了一些父类的initState()方法。
BottomNavigationBar里的响应事件
BottomNavigationBar组件里提供了一个相应事件onTap,这个事件自带一个索引值index,通过索引值我们就可以和我们list里的索引值相对应了。
onTap:(int index){
setState((){
_currentIndex= index;
});
},
现在给出全部的bottom_navigation_widget.dart的全部代码:
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'pages/home_screen.dart';
import 'pages/email_screen.dart';
import 'pages/pages_screen.dart';
import 'pages/airplay_screen.dart';
class BottomNavigationWidget extends StatefulWidget {
const BottomNavigationWidget({
Key? key}) : super(key: key);
State<BottomNavigationWidget> createState() => _BottomNavigationWidgetState();
}
class _BottomNavigationWidgetState extends State<BottomNavigationWidget> {
final _BottomNavigationColor = Colors.green;
int _currentIndex = 0;
List<Widget> lit = [];
initState() {
lit
..add(HomeScreen())
..add(EmailScreen())
..add(PagesScreen())
..add(AirplayScreen());
super.initState();
}
Widget build(BuildContext context) {
return Scaffold(
body: lit[_currentIndex],
bottomNavigationBar: BottomNavigationBar(items: [
BottomNavigationBarItem(
icon: Icon(
Icons.home,
color: _BottomNavigationColor,
),
label: '首页'),
BottomNavigationBarItem(
icon: Icon(
Icons.email,
color: _BottomNavigationColor,
),
label: '邮件'),
BottomNavigationBarItem(
icon: Icon(
Icons.pages,
color: _BottomNavigationColor,
),
label: "分页"),
BottomNavigationBarItem(
icon: Icon(
Icons.airplay,
color: _BottomNavigationColor,
),
label: '设置'),
],
currentIndex: _currentIndex,
onTap: (int index) {
setState(() {
_currentIndex = index;
});
},
type: BottomNavigationBarType.fixed),
);
}
}
现在基本掌握了APP底部导航的编写方法,其实这些知识是很面向工作的,因为真实项目中,也要如此划分代码结构。
不规则底部工具栏制作-1
大部分的底部导航都是中规中矩的,但有些时候也需要突出个性,比如在中间部位增加一个突出的按钮。这节课我们主要学习一下不规则的导航如何制作。
自定义主题样本
Flutter支持自定义主题,如果使用自定义主题,设置的内容项是非常多的,这可能让初学者头疼,Flutter贴心的为给我们准备了主题样本。
primarySwatch :现在支持18种主题样本了。
具体代码如下:
theme: ThemeData(
primarySwatch: Colors.lightBlue,
),
会了这个知识后,我们就可以先把我们的主入口文件编写一下了,具体代码如下:
import 'package:flutter/material.dart';
import 'bottom_appBar_demo.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.lightBlue,
),
home: BottomAppBarDemo(),
);
}
}
这时候bottom_appBar_demo.dart这个文件还没有,也找不到,这个文件是我们的主要文件,我们的主要业务逻辑会写在这个文件里。
因为没有所以写完之后会报错,那接下来我们就来编写这个文件。
floatingActionButton Widget
floatingActionButton 工作中我通常简称它为“FAB”,从字面理解可以看出,它是“可交互的浮动按钮”,其实在Flutter默认生成的代码中就有这家伙。
一般来说,它是一个圆形,中间放着图标,会优先显示在其他Widget的前面。
下面我们来看看它的常用属性:
onPressed :点击相应事件,最常用的一个属性。
tooltip:长按显示的提示文字,因为一般只放一个图标在上面,防止用户不知道,当我们点击长按时就会出现一段文字性解释。非常友好,不妨碍整体布局。
child :放置子元素,一般放置Icon Widget。
我们来看一下floatingActionButton的主要代码:
floatingActionButton: FloatingActionButton(
onPressed: (){
Navigator.of(context).push(MaterialPageRoute(builder:(BuildContext context){
return EachView('New Page');
}));
},
tooltip: 'YoYo',
child: Icon(
Icons.add,
color: Colors.white,
),
),
写完这些代码已经有了一个悬浮的按钮,但这个悬浮按钮还没有和低栏进行融合,这时候需要一个属性。
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
这时候就可以和底栏进行融合了。
BottomAppBar Widget
BottomAppBar 是 底部工具栏的意思,这个要比BottomNavigationBar widget灵活很多,可以放置文字和图标,当然也可以放置容器。
BottomAppBar的常用属性:
color:这个不用多说,底部工具栏的颜色。
shape:设置底栏的形状,一般使用这个都是为了和floatingActionButton融合,所以使用的值都是CircularNotchedRectangle(),有缺口的圆形矩形。
child : 里边可以放置大部分Widget,让我们随心所欲的设计底栏。
这节课先来看看这个布局,下节课我们再添加交互效果。
主要代码:
import 'package:flutter/material.dart';
class BottomAppBarDemo extends StatefulWidget {
_BottomAppBarDemoState createState() => _BottomAppBarDemoState();
}
class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: (){
},
tooltip: 'Increment',
child: Icon(
Icons.add,
color: Colors.white,
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomAppBar(
color:Colors.lightBlue,
shape:CircularNotchedRectangle(),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
IconButton(
icon:Icon(Icons.home),
color:Colors.white,
onPressed:(){
}
),
IconButton(
icon:Icon(Icons.airport_shuttle),
color:Colors.white,
onPressed:(){
}
),
],
),
)
,
);
}
}
不规则底部工具栏制作-2
已经完成了基本的页面布局,但是还没有交互效果,下面做一下交互效果。
StatefulWidget子页面的制作
在前面实例中我们使用了子页面,但子页面继承与StatelessWidget(不可变控件),所以很麻烦的写了4个页面,其实完全可以写一个继承于StatefulWidget的控件,进行搞定。
新建一个each_view.dart文件,然后输入如下代码:
import 'package:flutter/material.dart';
class EachView extends StatefulWidget {
String _title;
EachView(this._title);
State<EachView> createState() => _EachViewState();
}
class _EachViewState extends State<EachView> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget._title)),
body: Center(child: Text(widget._title),),
);
}
}
代码中设置了一个内部的_title变量,这个变量是从主页面传递过来的,然后根据传递过来的具体值显示在APP的标题栏和屏幕中间。
按钮交互效果的制作
这些效果都是在bottom_appBar_demo.dart页面完成的。首先我们需要引入新作的子页面each_view.dart。
import 'each_view.dart';
新建两个变量,主要作用是控制body中的试图,也就是显示不同的子页面。
late List _eachView; //创建视图数组
int _index = 0; //数组索引,通过改变索引值改变视图
下一步是为_eachView进行初始化赋值,我们可以直接重写初始化方法,具体代码如下:
initState() {
super.initState();
// ignore: deprecated_member_use
_eachView = [];
_eachView
..add(EachView('Home'))
..add(EachView('Me'));
}
剩下的就是写个个按钮的交互事件,交互的动作分两种:
直接打开子导航,比如我们点击了中间的”+“按钮,我们直接开启子页面。
onPressed: (){
Navigator.of(context).push(MaterialPageRoute(builder:(BuildContext context){
return EachView('New Page');
}));
},
改变状态,通过改变状态,来切换页面,这样我们整体页面并没有被刷新。
onPressed:(){
setState(()