【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);
}
}
【Flutter】如何优美地实现一个悬浮NavigationBar的更多相关文章
- 在页面左右一个悬浮div兼容IE6 IE7 8 9 Firefox chrome
在页面左右一个悬浮div兼容IE6 IE7 8 9 Firefox chrome #identifier-pannel { bottom: 345px; margin-left: 512px; pos ...
- Flutter学习六之实现一个带筛选的列表页面
上期实现了一个网络轮播图的效果,自定义了一个轮播图组件,继承自StatefulWidget,我们知道Flutter中并没有像Android中activity的概念.页面见的跳转是通过路由从一个全屏组件 ...
- Flutter学习三之搭建一个简单的项目框架
上一篇文章介绍了Dart的语法的基本使用,从这篇文章开始,开发一个基于玩Android网站的app.使用的他们开放的api来获取网站数据. 根据网站的结构,我们app最外层框架需要添加一个底部导航栏, ...
- 如何用C#做一个悬浮窗口程序
用C#做一个像FlashGet的悬浮窗口,其实很简单,不像以前需要调用很多系统API.大致的步骤如下. 首先是主窗体部分,即要判断窗体的状态来决定是否显示悬浮窗口. 局部成员声明: private F ...
- Flutter学习四之实现一个支持刷新加载的列表
上一篇文章用Scaffold widget搭建了一个带底部导航栏的的项目架构,这篇文章就来介绍一下在flutter中怎么实现一个带下拉刷新和上拉加载更多的一个列表,这里用到了pull_to_refre ...
- Flutter随笔(二)——使用Flutter Web + Docker + Nginx打造一个简单的Web项目
前言 Flutter作为一个跨平台UI框架,功能十分强大,仅用一套代码便能编译出Android.iOS.Web.windows.macOS.Windows.Linux等平台上的应用,各平台应用体验高度 ...
- Flutter学习之路---------第一个Flutter项目
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面. Flutter可以与现有的代码一起工作.在全世界,Flutter正在被越来越多的开发者和组织使用,并且 ...
- Flutter - JSON to Dart,一个json转dart实体的网站
如你所见,一个json转dart实体的网站,https://javiercbk.github.io/json_to_dart/
- 6.1.初识Flutter应用之实现一个计数器
用Android Studio和VS Code创建的Flutter应用模板是一个简单的计数器示例,本节先仔细讲解一下这个计数器Demo的源码,让读者对Flutter应用程序结构有个基本了解,在随后小节 ...
- 无NavigationBar到有NavigationBar视图切换时的一个坑
NavigationController在iOS App中是最常见不过了,可以说是每个App中必备的了.自iOS7开始,系统自带的右滑返回效果,也可以让有NavigationBar的视图切换很丝滑流畅 ...
随机推荐
- Android strings.xml按照key修改
strings.xml匹配替换 将两个Android项目中的多语言字符串文件(strings.xml)进行比较,如果其中一个项目中包含另一个项目没有的字符,则合并到单一的输出文件,并以 key 在原始 ...
- Gazebo、Rviz 结合 ORB-SLAM3 实现运动规划及定位
博客地址:https://www.cnblogs.com/zylyehuo/ 基于以下内容,详见博客链接 基于机器人自主移动实现SLAM建图 - zylyehuo - 博客园 (cnblogs.com ...
- 2023-06-17:说一说redis中渐进式rehash?
2023-06-17:说一说redis中渐进式rehash? 答案2023-06-17: 在Redis中,如果哈希表的数组一直保持不变,就会增加哈希冲突的可能性,从而降低检索效率.为了解决这个问题,R ...
- Prompt 手册——gpt-best-practices
本文链接:https://www.cnblogs.com/wanger-sjtu/p/17470388.html 本文是 OpenAI gpt-best-practices 对如何使用GPT的Prom ...
- 温故知新,signalR、RSA加密、ConcurrentQueue队列
这是一个用户密码异步加解密的Demo,包含了RSA非对称加密,ConcurrentQueue线程安全队列使用,signalR实时推送加解密信息等内容. 说在前面的话 距离上次更新已然快过去一年了,这中 ...
- 当使用POI打开Excel文件遇到out of memory时该如何处理?
摘要:本文由葡萄城技术团队于博客园原创并首发.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 当我们开发处理Excel文件时,Apache POI 是许多人 ...
- CF1728A Colored Balls: Revisited题解
去我的Blog观看 修改时间:2022/9/11修改了格式与标点 修改时间:2022/9/13修改了个别不严谨的语句 题目大意 有 \(n\) 种颜色的球,颜色为 \(i\) 的球为 \(cnt_i\ ...
- Code Generate 代码生成器 V1.0
Code Generate V1.0 代码生成器 根据配置的模板,根据建表语句,生成Code. 例如java代码.vue代码.jsp代码以及html代码等等,均可根据自己的代码写作习惯进行配置. 缺点 ...
- 【Springboot】拦截器
Springboot 拦截器 1.什么是拦截器? 拦截器可以根据 URL 对请求进行拦截,主要应用于登陆校验.权限验证.乱码解决.性能监控和异常处理等功能. 2.定义拦截器步骤 在 Spring Bo ...
- 打开页面就能玩?ZEGO 发布行业首套 Web 端在线 KTV 解决方案
近些年,在线K歌行业逐渐开始规模化发展,活跃用户不断破新高,据 ZEGO 即构科技研究院数据显示,2021 年中国在线 K 歌用户规模逾 5.2亿,网民渗透率达到 50%. 凭借着极大的便利性和娱乐性 ...