本文主要通过flutter实现底部导航栏与顶部卡片切换效果,本文demo地址
文章目录
一、底部导航栏
底部导航栏的应用场景非常多,基本每个app都要用到,常用于app登录后的顶级页面托管
在flutter中,底部导航栏的创建主要用到了
1,State状态管理
2,BottomNavigationBar底部导航组件
3,IndexedStack缓存组件
1.1、新建入口页面
app入口文件(main.dart)
import 'package:flutter/material.dart';
import 'BottomTabBar.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: BottomTabBar(),
);
}
}
1.2、底部导航栏组件
首先我们需要定义状态变量与页面绑定
定义currentIndex:当前页面index,
定义pageList:具体业务页面列表
int currentIndex = 0;
List pageList = [FirstPage(), SecondPage(), ThirdPage()];
底部导航栏的组件ui样式,我们使用系统提供的BottomNavigationBar,BottomNavigationBar组件api如下
具体实现如下(BottomTabBar.dart)
import 'package:flutter/material.dart';
import 'tabs/category/CategoryPage.dart';
import 'tabs/home/HomePage.dart';
import 'tabs/mine/MinePage.dart';
import 'tabs/setting/SettingPage.dart';
class BottomTabBar extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _BottomTabBarState();
}
}
class _BottomTabBarState extends State<BottomTabBar> {
int currentIndex = 0;
List pageList = [HomePage(), CategoryPage(), SettingPage(), MinePage()];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('导航栏'),
),
body: this.pageList[this.currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: this.currentIndex,
onTap: (int index) {
setState(() {
this.currentIndex = index;
});
},
type: BottomNavigationBarType.fixed, //配置底部tabs可以有
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('首页')),
BottomNavigationBarItem(icon: Icon(Icons.category), title: Text('分类')),
BottomNavigationBarItem(icon: Icon(Icons.settings), title: Text('设置')),
BottomNavigationBarItem(icon: Icon(Icons.people), title: Text('我的')),
],
),
);
}
}
1.3、其他页面处理
对于加载的其他业务页面(CategoryPage.dart、HomePage.dart、MinePage.dart、SettingPage.dart),处理如下
import 'package:flutter/material.dart';
class MinePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Text('我的'),
);
}
}
1.5、底部TabBar gif演示
这样我们就可以运行出一个简单的底部切换demo
效果如下
1.4、页面缓存
但是这样存在一个问题
底部Tabbar切换时currentPage会被销毁,重新加载页面。底部Tabbar每切换一次,就请求一次网络,造成了资源浪费,用户体验也比较差。
这里我们使用IndexedStack做缓存,将body的页面调整如下
//调整前
body: this.pageList[this.currentIndex],
//加缓存
body: IndexedStack(
children: <Widget>[
...this.pageList,
],
index: currentIndex,
),
1.5、底部导航凸起效果
在Scaffold容器下添加悬浮按钮,覆盖到tabBar上面
floatingActionButton: Container(
height: 70,
width: 70,
padding: EdgeInsets.all(8),
//设置:内边距8
margin: EdgeInsets.only(top: 2),
//设置:外边距2, 这样就可以让浮动按钮下来,贴近分类Tab文字
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40), color: Colors.white),
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
//可以实现重新渲染页面,因为_currentIndex变成了1,所以页面会跳转到分类页面
this.currentIndex = 1; //点击浮动按钮时,切换到分类页面
});
},
backgroundColor: this.currentIndex == 1
? Colors.red
: Colors.yellow //利用三目运算符,实现选中时,浮动按钮背景颜色变化
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
二、顶部导航栏(卡片切换)
顶部导航栏使用场景也非常多,常用于单页面里面的卡片切换
2.1、新建入口文件
首先顶部导航栏我们写到首页中(HomePage.dart)
import 'package:flutter/material.dart';
import 'widget/TopTabBar.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: TopTabBar()
);
}
}
2.2、顶部导航栏组件
我们使用TabBarView做缓存,相当于ScrollTabView,组件api如下
具体代码(TopTabBar.dart)
import 'package:flutter/material.dart';
class TopTabBar extends StatefulWidget {
@override
TopTabBarState createState() => TopTabBarState();
}
class TopTabBarState extends State<TopTabBar>
with SingleTickerProviderStateMixin {
late TabController tabController;
var tabs = <Text>[];
@override
void initState() {
super.initState();
tabs = <Text>[
Text('儿童'),
Text('女装'),
Text('百货'),
Text('美食'),
Text('美妆'),
];
tabController = TabController(length: tabs.length, vsync: this);
//.addListenter 可以对 TabController 增加监听,每次发生切换,都能够走到方法中
this.tabController.addListener(() {
print(this.tabController.toString());
print(this.tabController.index);
print(this.tabController.length);
print(this.tabController.previousIndex);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: new Column(
children: <Widget>[
ConstrainedBox(
constraints: BoxConstraints(
minWidth: double.infinity,
minHeight: Size.fromHeight(kMinInteractiveDimension).height,
),
child: Container(
color: Colors.blue,
child: TabBar(
tabs: tabs,
controller: tabController,
onTap: (int index) {
print('Selected......$index');
},
unselectedLabelColor: Colors.white,
//设置未选中时的字体颜色,tabs里面的字体样式优先级最高
unselectedLabelStyle: TextStyle(fontSize: 16),
//设置未选中时的字体样式,tabs里面的字体样式优先级最高
labelColor: Colors.red,
//设置选中时的字体颜色,tabs里面的字体样式优先级最高
labelStyle: TextStyle(fontSize: 16.0),
//设置选中时的字体样式,tabs里面的字体样式优先级最高
isScrollable: false,
//isScrollable 默认为false 里面标题平分显示 ;true 可以滚动不平分显示
indicatorColor: Colors.red,
//选中下划线的颜色
indicatorSize: TabBarIndicatorSize.label,
//选中下划线的长度,label时跟文字内容长度一样,tab时跟一个Tab的长度一样
indicatorWeight: 4.0, //选中下划线的高度,值越大高度越高,默认为2。0
),
),
),
Expanded(
child: new TabBarView(
controller: tabController,
children: <Widget>[
ListViewContnet(),
ListViewContnet(),
ListViewContnet(),
ListViewContnet(),
ListViewContnet(),
],
)),
],
),
);
}
}
class ListViewContnet extends StatelessWidget {
const ListViewContnet({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
const TITLE = '标题标题标题标题标题标题标题';
return ListView(
children: <Widget>[
ListTile(title: Text(TITLE)),
ListTile(title: Text(TITLE)),
ListTile(title: Text(TITLE)),
ListTile(title: Text(TITLE)),
ListTile(title: Text(TITLE)),
ListTile(title: Text(TITLE)),
ListTile(title: Text(TITLE)),
ListTile(title: Text(TITLE)),
ListTile(title: Text(TITLE)),
ListTile(title: Text(TITLE)),
],
);
}
}
2.3、顶部固定卡片切换 gif演示
这样就实现了卡片切换效果(可滑动不平均大小),效果如下
2.4、顶部可滑动卡片 gif演示
新闻中卡片可能有多个动态加载,我们需要顶部可以滑动不平均大小的效果
这里我们更改TabBar的isScrollable属性为true即可。效果如下