【Flutter】怎么美丽地完成一个悬浮NavigationBar
【Flutter】怎么美丽地完结一个悬浮NavigationBar
最近写代码的时分遇到了一个如下的需求:
全体来说,底部的条是一个起浮的悬浮窗,有如下的三个按钮:
- 点击左面的要进入“主页”
- 点击中心的按钮要进行页面跳转,能够进入“创造页”
- 点击右边的按钮切换到“个人中心”页
运用Overlay来完结悬浮作用
首先是这个窗口该怎么创立的问题,明显需求Overlay悬浮在整个窗口顶部。
可是不能直接写在initState内,这样会触发“Build时重绘”的过错。所以咱们能够运用WidgetsBinding
,来监听Callback
,这样能够确保在主页Build完结时能够马上制作这个悬浮的窗口。
/rootpage
@override
void didChangeDependencies() {
print('root didChangeDependencies');
super.didChangeDependencies();
var widgetsBinding = WidgetsBinding.instance;
widgetsBinding.addPostFrameCallback((callback) {
print('addPostFrameCallback');
PNavigationBar.show(context, _tabController);
});
}
我将这个放入到了didChangeDependencies
内,首要是想经过混入TickerProviderStateMixin
能够在路由回来时从头触发didChangeDependencies
,不过抱负很饱满。终究在试验的过程中反倒没有触发,没有找到原因,期望有感爱好的大佬能够点拨一下。
理论参阅:Flutter 小而美系列|TickerProviderStateMixin 对生命周期的影响 - 掘金 (juejin.cn)
运用TabBar+TabView来完结NavigationBar的作用
首先说最简略的TabView部分
@override
Widget build(BuildContext context) {
return Scaffold(
body: TabBarView(
controller: _tabController,
children: [
HomePage(),
UserPage(),
],
),
);
}
这儿需求一个TabController,信任比较了解的朋友们也知道,需求混入TickerProviderStateMixin,才能够声明
画框的部分是首要部分。
自界说完结一个PNavigationBar
(详细的代码在本文终究)
整个PNavigationBar的完结十分简略,界说了一个show,一个remove,一个refresh办法,这样能够确保任何组件任何页面都能够随时操控PNavigationBar的出现和消失。
图标的切换
由于NavigationBar是存在切换图标的功用的,而咱们经过Image.asset获取的图标却没办法更新,所以咱们需求手动调用overlayEntry.markNeedsBuild
办法,来对整个底部组件进行重绘
中心按钮的完结
信任咱们也会有开始跟我相同的疑问,由于TabBar与TabView,还有TabController的数有必要共同,而咱们中心有一个自界说的加号按钮,我在这儿的完结十分简略粗犷,当然如果有更好的办法欢迎大佬指导。
我这儿仅仅经过简略的运算,来将两个组件别离操控在左面和右边,之后加号按钮在中心。
当然整个TabBar的烘托逻辑其实是有问题的,想要更深化地改TabBar的摆放办法,有必要需求自己手写一个TabBar。默许的摆放办法便是放到Expanded
内的,详细参阅了以下这篇博客:
Flutter系列之设置TabBar的tab紧凑摆放_flutter tabbar距离-CSDN博客
关于页面路由的问题
最难的部分便是这儿,首要在于怎么操控路由到其他界面就能够消失,再pop回来就能够显现。
咱们期望这些功用都能够在RootPage这一层完结,而不在各种子页面的push和pop中增加代码担负。
详细完结起来开始我的测验是didChangeDependencies
,可是终究试验下来并没有成果,我自己也并不知道原因。(小白是这样的)
而我终究决议选用原始的NavigationObserver办法,这儿感谢这个组件替我完结了这个功用:
lifecycle_lite | Flutter Package (pub.dev)
所以能够经过简略的onShow和onHide就能够完结啦!
代码出现
当然还有许多细节都没有说到,写这个功用时遇到的问题也有不少,自己技能有限,才能有限。等代码再优化的时分能够作为库开源给咱们。现在就暂时以这种博客的方式共享组件和代码。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:picturebook/pages/test/test_page.dart';
import '../color_utils.dart';
class PNavigationBar {
static OverlayEntry? overlayEntry;
static show(BuildContext context, TabController tabController) {
var overlayState = Overlay.of(context);
overlayEntry = OverlayEntry(
maintainState: true,
builder: (BuildContext context) {
final size = MediaQuery.of(context).size;
final height = size.height;
final width = size.width;
final boxWidth = width * 0.46;
final boxHeight = 60.h;
final iconHeight = 45.h;
return Positioned(
bottom: height * 0.06,
left: (width - boxWidth) / 2,
right: (width - boxWidth) / 2,
child: Stack(
children: [
Container(
decoration: BoxDecoration(
color: ColorUtils.orange,
borderRadius: BorderRadius.circular(boxHeight / 2),
),
width: boxWidth,
height: boxHeight,
child: TabBar(
controller: tabController,
indicatorColor: Colors.transparent,
padding: EdgeInsets.zero,
onTap: (index) {
tabController.animateTo(index);
overlayEntry?.markNeedsBuild();
},
tabs: [
Padding(
padding: EdgeInsets.only(right: iconHeight / 3),
child: Container(
width: iconHeight,
height: iconHeight,
decoration: BoxDecoration(
color: Colors.white30,
borderRadius: BorderRadius.circular(iconHeight / 2),
),
child: Center(
child: Image.asset(
tabController.index == 0
? 'assets/home_1.png'
: 'assets/home_0.png',
width: iconHeight * 0.5,
)),
),
),
Padding(
padding: EdgeInsets.only(left: iconHeight / 3),
child: Container(
width: iconHeight,
height: iconHeight,
decoration: BoxDecoration(
color: Colors.white30,
borderRadius: BorderRadius.circular(iconHeight / 2),
),
child: Center(
child: Image.asset(
tabController.index == 1
? 'assets/user_1.png'
: 'assets/user_0.png',
width: iconHeight * 0.5,
)),
),
),
],
)),
Align(
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.only(top: (boxHeight - iconHeight) / 2),
child: InkWell(
onTap: () {
print('push');
Navigator.push(
context,
MaterialPageRoute(builder: (context) => TestPage()),
);
},
child: Container(
width: iconHeight,
height: iconHeight,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(iconHeight / 2),
),
child: Center(
child: Image.asset(
'assets/add.png',
width: iconHeight * 0.5,
)),
),
),
),
),
],
),
);
},
);
overlayState.insert(overlayEntry!);
}
static remove() {
if (overlayEntry != null) {
overlayEntry!.remove();
}
}
static refresh(){
overlayEntry?.markNeedsBuild();
}
}
下面是运用的实例,十分美丽简练:
import 'package:flutter/material.dart';
import 'package:lifecycle_lite/lifecycle_mixin.dart';
import 'package:picturebook/pages/home_page.dart';
import 'package:picturebook/pages/user_page.dart';
import 'package:picturebook/utils/navigation/navigation_util.dart';
class RootPage extends StatefulWidget {
const RootPage({super.key});
@override
State<RootPage> createState() => _RootPageState();
}
class _RootPageState extends State<RootPage>
with TickerProviderStateMixin, LifecycleStatefulMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this)..addListener(() {
PNavigationBar.refresh();
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
var widgetsBinding = WidgetsBinding.instance;
widgetsBinding.addPostFrameCallback((callback) {
PNavigationBar.show(context, _tabController);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: TabBarView(
controller: _tabController,
children: [
HomePage(),
UserPage(),
],
),
);
}
@override
void whenHide() {
PNavigationBar.remove();
}
@override
void whenShow() {
PNavigationBar.show(context, _tabController);
}
}