Flutter实现闲鱼底部导航栏中间突出效果

news/2025/10/2 8:46:30/文章来源:https://www.cnblogs.com/asterlee/p/19123183

Flutter实现闲鱼底部导航栏中间突出效果

Posted on 2025-10-02 08:41  lifeisastory  阅读(0)  评论(0)    收藏  举报

实现思路

Scaffold 组件中使用 bottomNavigationBarfloatingActionButton 属性建立底部导航栏和浮动按钮,同时使用 floatingActionButtonLocation 属性指定浮动按钮的位置。

默认情况下,当 floatingActionButton 融入 bottomNavigationBar 时,仅可实现如下图效果:(指定 bottomNavigationBarBottomAppBar 组件,其 shape 属性为 CircularNotchedRectangle,指定 floatingActionButtonLocationFloatingActionButtonLocation.centerDocked

所以需要自定义实现一个类似 CircularNotchedRectangle 类和 FloatingActionButtonLocation.centerDocked 类。

CircularNotchedRectangle 的实现原理

我们的目的是实现如下图的效果:

可以将缺口部分分成三部分:

其中,A 和 C 段是关于圆心对称的两段二次贝塞尔曲线,用来平滑过渡,B 是一段圆弧。

B 通过圆的半径可以轻松得到,重点来讨论二次贝塞尔曲线如何实现。对于一段二次贝塞尔曲线,有三个点,即 P0(起始点)、P1(控制点)、P2(结束点):

我们希望 P0 和 P2 处的连接是平滑的,即连接点处两段曲线的切线相同。由于 P0 所在的曲线为直线,因此我们仅考虑 P2 的平滑连接即可。

注意,下面的计算的笛卡尔坐标系原点为圆心\(O\)

先指定 \(P_1(a,b)\)\(P_0(c,b)\)(a、c是经验值),显然此时 b=0。现在问题转化为求 \(P_2\) 的坐标 \((x_2,y_2)\),另外我们还有下面的条件:

  • 圆的方程:\(x^2 + y^2 = R^2\ (R = r + Notch)\)
  • 直线方程:\(xx_2 + yy_2 = R^2\)
  • \(P_1\) 在直线上:\(ax_2 + by_2 = R^2\ ①\)
  • \(P_2\) 在圆上:\(x_2^2 + y_2^2 = R^2\ ②\)

通过联立①式和②式,可得:

\((a^2 + b^2)x_2^2\ -\ 2aR^2x_2\ +\ R^4\ -\ b^2R^2 = 0\)

\((a^2 + b^2)y_2^2\ -\ 2bR^2y_2\ +\ R^4\ -\ a^2R^2 = 0\)

通过求根公式可得:

\(x_2 = \frac{aR^2\ \pm\ \sqrt{a^2R^4\ -\ (a^2\ +\ b^2)(R^4\ -\ b^2R^2)}}{a^2\ + \ b^2} = \frac{aR^2\ \pm\ \sqrt{a^2b^2R^2\ +\ b^4R^2\ -\ b^2R^4}}{a^2\ + \ b^2}\)

\(y_2 = \frac{bR^2\ \pm\ \sqrt{b^2R^4\ -\ (a^2\ +\ b^2)(R^4\ -\ a^2R^2)}}{a^2\ + \ b^2} = \frac{bR^2\ \pm\ \sqrt{a^2b^2R^2\ +\ a^4R^2\ -\ a^2R^4}}{a^2\ + \ b^2}\)

\(P_1\) 坐标 \((a,b)\) 代入即可得到 \(P_2\) 的坐标。(注意,还需要使用 \(P_2\) 在圆上这一条件判断两个 \(x_2\) 和两个 \(y_2\) 的对应情况)

选取在圆心下面的一组解,再使用二次贝塞尔曲线连接 A 的两端点即可得到 CircularNotchedRectangle 的效果。

所以,仿照 CircularNotchedRectangle 的实现,我们根据推导公式和入参选择圆心上方或者下方的一组解即可实现我们需要的效果。

FloatingActionButtonLocation.centerDocked 的实现原理

通过查看源代码可以发现,FloatingActionButtonLocation.centerDocked 调用了 _CenterDockedFabLocation 类,它继承自 StandardFabLocation 类,并混入了 FabCenterOffsetXFabDockedOffsetY 两个类。

StandardFabLocation 类继承自 FloatingActionButtonLocation,需要重写 getOffset() 来得到 FAB 的偏移量。

StandardFabLocation 类中已经重写了 getOffset() ,它还定义了 getOffsetX()getOffsetY() 来获取 X 和 Y 轴的偏移量。getOffsetX()getOffsetY()FabCenterOffsetXFabDockedOffsetY 两个混入类中实现,得到正确的 X 和 Y 轴的偏移。

所以,我们的自定义类只需继承 FloatingActionButtonLocation 并根据自定义位置重写 getOffset() 即可。

代码实现

自定义类 CircularCustomRectangle

class CircularCustomRectangle extends NotchedShape {/// FAB 融合进 bottomNavigationBar 的自定义样式/// 如果 [guest] 向上或向下移动过半,则不对 [host] 处理const CircularCustomRectangle({this.inverted = false,this.protruded = true,});/// 控制在导航栏顶部还是底部作用效果,默认顶部,设置[true]表示作用在底部final bool inverted;/// 控制向上突出还是向下凹入,默认向上突出,设置[false]表示向下凹入final bool protruded;@overridePath getOuterPath(Rect host, Rect? guest) {// 判断 guest是否为 null 或没有覆盖 host,如果是则不对 host处理if (guest == null || !host.overlaps(guest)) {return Path()..addRect(host);}// 判断 guest 是否向上或向下移动过半,如果过半则不对 host 处理if (protruded && guest.center.dy < 0) {return Path()..addRect(host);} else if (!protruded && guest.center.dy > 0) {return Path()..addRect(host);}// 设置对 host 处理的圆弧半径final double r = guest.width / 2.0;// 生成一个圆弧半径,用于在 B 段连接 P2、P3final Radius radius = Radius.circular(r);// 根据传入参数进行指定位置、方向的处理final double invertMultiplier = inverted ? -1.0 : 1.0;final double protrudedMultiplier = protruded ? 1.0 : -1.0;/// 下面的计算逻辑,当前坐标原点全部为 guest 的圆心// 根据 guest 的位置动态计算圆周上的点到 y 轴的距离,用来参与决定圆滑过渡开始的位置double d = math.sqrt(r * r - guest.center.dy * guest.center.dy);const double s1 = 15; // 经验值,调整过渡的长度const double s2 = 2; // 经验值,调整过渡的高度// a 是以圆心为坐标原点时 P1 的横坐标final double a = -d - s2;// b 是以圆心为坐标原点时 P1 的纵坐标final double b = guest.center.dy;// 计算 x、y 的解的 deltafinal double sqrtDeltax = b.abs() * r * math.sqrt(a * a + b * b - r * r);final double sqrtDeltay = a.abs() * r * math.sqrt(b * b + a * a - r * r);// 计算 x 的两个解final double p2xA = ((a * r * r) - sqrtDeltax) / (a * a + b * b);final double p2xB = ((a * r * r) + sqrtDeltax) / (a * a + b * b);// 计算 y 的两个解// 先判断两个解的对应关系double p2yAtemp = ((b * r * r) - sqrtDeltay) / (a * a + b * b);double p2yBtemp = ((b * r * r) + sqrtDeltay) / (a * a + b * b);if (!(((p2xA * p2xA + p2yAtemp * p2yAtemp) - r * r).abs() < 5.0)) {double temp = p2yAtemp;p2yAtemp = p2yBtemp;p2yBtemp = temp;}// 再根据判断结果确定两个 x 的解与 y 的解的对应关系final double p2yA = p2yAtemp * invertMultiplier;final double p2yB = p2yBtemp * invertMultiplier;final List<Offset> p = List<Offset>.filled(6, Offset.zero);// 下面计算 P0、P1、P2,再根据 P0、P1、P2 镜像得到 P3、P4、P5p[0] = Offset(-d - s1, b);p[1] = Offset(a, b);// 根据 protrudedMultiplier 的值,选择要纵坐标大于0的向上凸出还是纵坐标小于0的向下凹入的坐标p[2] = protrudedMultiplier * p2yA > protrudedMultiplier * p2yB? Offset(p2xA, p2yA): Offset(p2xB, p2yB);p[3] = Offset(-1.0 * p[2].dx, p[2].dy);p[4] = Offset(-1.0 * p[1].dx, p[1].dy);p[5] = Offset(-1.0 * p[0].dx, p[0].dy);/// 下面将坐标原点从圆心转换成以 host 的左上角为坐标原点for (int i = 0; i < p.length; i += 1) {double x = p[i].dx + guest.center.dx; // x轴方向没有变化,直接加 guest 的中心点坐标即可double y =-p[i].dy + guest.center.dy; // y轴方向反向,需要先将原 y 轴反向再加 guest 的中心点坐标p[i] = Offset(x, y);}// 根据位置点生成路径final Path path = Path()..moveTo(host.left, host.top);if (!inverted) {path..lineTo(p[0].dx, p[0].dy)..quadraticBezierTo(p[1].dx, p[1].dy, p[2].dx, p[2].dy)..arcToPoint(p[3],radius: radius,clockwise: protruded,) // 这里的 clockwise 控制了圆弧的方向..quadraticBezierTo(p[4].dx, p[4].dy, p[5].dx, p[5].dy)..lineTo(host.right, host.top)..lineTo(host.right, host.bottom)..lineTo(host.left, host.bottom);} else {path..lineTo(host.right, host.top)..lineTo(host.right, host.bottom)..lineTo(p[5].dx, p[5].dy)..quadraticBezierTo(p[4].dx, p[4].dy, p[3].dx, p[3].dy)..arcToPoint(p[2], radius: radius, clockwise: protruded)..quadraticBezierTo(p[1].dx, p[1].dy, p[0].dx, p[0].dy)..lineTo(host.left, host.bottom);}return path..close();}
}

自定义类 FloatingButtonCustomLocation

class FloatingButtonCustomLocation extends FloatingActionButtonLocation {/// 控制 FAB 位置的自定义类FloatingButtonCustomLocation(this.location, {this.offsetX = 0,this.offsetY = 0,});/// [location] 表示参照物,比如 [FloatingActionButtonLocation.startTop] 表示以 [bottomNavigationBar] 左上角为原点FloatingActionButtonLocation location;/// [offsetX] 表示 X 方向的偏移量final double offsetX;/// [offsetY] 表示 Y 方向的偏移量final double offsetY;@overrideOffset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {Offset offset = location.getOffset(scaffoldGeometry);return Offset(offset.dx + offsetX, offset.dy + offsetY);}
}

优化

如果 floatingActionButtonLocation 属性使用了自定义的类,在点击导航栏按钮时会让 FAB 执行缩放动画,我们可以在 floatingActionButtonAnimator 属性中使用自定义的动画类来取消这个缩放动画:

class ScalingCustomAnimation extends FloatingActionButtonAnimator {/// 控制 FAB 动画的自定义类ScalingCustomAnimation();@overrideOffset getOffset({required Offset begin,required Offset end,required double progress,}) {return Offset.lerp(begin, end, progress)!;}@overrideAnimation<double> getRotationAnimation({required Animation<double> parent}) {return Tween<double>(begin: 1.0, end: 1.0).animate(parent);}@overrideAnimation<double> getScaleAnimation({required Animation<double> parent}) {return Tween<double>(begin: 1.0, end: 1.0).animate(parent);}
}

参考资料

https://zhuanlan.zhihu.com/p/394087615

https://juejin.cn/post/7153097948195192863

https://goo.gl/Ufzrqn

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/924711.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025试验机实力厂家品牌公司最新权威推荐榜:精准测试与技术创新标杆之选

在制造业转型升级与质量强国战略深入推进的背景下,试验机作为质量检测与材料研发的核心装备,正迎来前所未有的发展机遇。根据行业数据显示,2024年我国试验机市场规模已突破百亿元,年均复合增长率保持在12%以上,其…

AI元人文:价值共生体系统——构建人机文明的演进基石——DeeoSeek融合

AI元人文:价值共生体系统——构建人机文明的演进基石 摘要 本文针对传统AI"价值对齐"范式的局限性,提出"价值共生体系统"作为下一代智能系统的核心架构。该系统通过价值语义网络、共识决策引擎和…

详细介绍:大模型架构之GPT、LLaMA与PaLM模型

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025喷涂厂家TOP企业品牌推荐排行榜,喷涂、喷涂设备、 喷涂生产线、喷涂流水线推荐这十家公司!

在涂装行业快速发展的当下,企业对喷涂设备的需求日益提升,但诸多痛点却制约着行业升级。粉末回收效率低导致的原料浪费、换色耗时久引发的生产停滞、温度控制不准造成的涂层质量不稳定等问题,让不少企业陷入成本高企…

微科技h5制作网站模板下载网站开发的形式有

类型推导&#xff1a; 当使用 auto 关键字声明变量时&#xff0c;编译器会根据变量初始化的表达式推导出其类型。编译器会分析初始化表达式&#xff0c;并根据表达式的类型来确定变量的类型。 类型保留&#xff1a; 编译器在编译时将 auto 关键字替换为实际的类型&#xff0c;…

完整教程:【JAVA】【BUG】经常出现的典型 bug 及解决办法

完整教程:【JAVA】【BUG】经常出现的典型 bug 及解决办法pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consola…

python人网站开发案例服务商平台支付宝

来源&#xff1a;亿欧根据全球领先的信息技术研究与顾问公司Gartner的预测&#xff0c;人工智能&#xff08;AI&#xff09;产生的全球商业价值预计将在2018年达到1.2万亿美元&#xff0c;比2017年增长70%。此外&#xff0c;Gartner预计到2022年&#xff0c;人工智能衍生的商业…

网站建设更新维护工作企业网站制作的公司

Redis是什么&#xff0c;优缺点&#xff1f; Redis本质是一个K-V类型的内存数据库 纯内存操作&#xff0c;每秒可处理超过10w的读写操作 优点&#xff1a; 读写性能极高 非阻塞IO 单线程 支持持久化 支持事务 数据结构丰富 缺点&#xff1a; 容易受到物理内存的限制 主机宕机可…

读人形机器人28智慧城市2

读人形机器人28智慧城市1. 智慧城市 1.1. 智慧城市的概念已成为创新与可持续发展的灯塔 1.2. 在智慧城市景观中发挥核心作用的是由AI驱动的人形机器人,它们无缝融入人们的日常生活 1.3. 是城市生态系统中的积极参与者…

浅析 AC 自动机

哈喽大家好,我是 doooge,今天来点大家想看的东西啊。 \[\Huge \sf 浅析~AC~自动机 \] 前置知识:Trie,不需要 KMP。1. AC 自动机的构造与匹配 所谓 AC 自动机,是结合了 Trie 和 KMP 思想的自动机,简单来说就是一个…

实用指南:谷歌官方 Chrome DevTools MCP 正式发布

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025百度官网认证作用代理商推荐,北京益百科技通过官网认证,助力企业优化搜索排名,提升用户体验,降低营销成本

在当今数字化时代,互联网已成为企业宣传推广的主阵地。北京益百科技有限公司作为一家致力于为企业提供互联网解决方案的专业公司,自2014年与百度携手合作,成为北京地区百度信誉“首批独家”授权服务商以来,凭借其丰…

实用指南:Linux(操作系统)文件系统--对打开文件的管理

实用指南:Linux(操作系统)文件系统--对打开文件的管理pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas…

南昌建设局网站查询塔吊证怎么查网络优化工程师的工作内容

下载安装 下载地址: https://download.csdn.net/download/yijianxiangde100/88496463 安装apk 即可。 证书配置:

dede鲜花网站模板下载境外公司注册代理机构

前言 第11章对应的内容选择题和案例分析都会进行考查&#xff0c;这一章节属于10大管理的内容&#xff0c;学习要以教材为准。本章上午题分值预计在15分。 目录 11.13 制定预算 11.13.1 主要输入 11.13.2 主要输出 11.14 规划质量管理 11.14.1 主要输入 11.14.2 主要工…

上海营销型网站建设公司电商erp软件

语法上的小trick 构造函数 虽然不写构造函数也是可以的&#xff0c;但是可能会开翻车&#xff0c;所以还是写上吧。&#xff1a; 提供三种写法&#xff1a; ​ 使用的时候只用&#xff1a; 注意&#xff0c;这里的A[i]gg(3,3,3)的“gg”不能打括号&#xff0c;否则就是强制转换…

VMware ESXi 9.0.1.0 发布 - 领先的裸机 Hypervisor

VMware ESXi 9.0.1.0 发布 - 领先的裸机 HypervisorVMware ESXi 9.0.1.0 发布 - 领先的裸机 Hypervisor Standard (标准版)、Dell (戴尔)、HPE (慧与)、Lenovo (联想)、IEIT SYSTEMS (浪潮信息)、H3C (新华三)、Cisco…

VMware vSphere 9.0.1.0 发布 - 企业级工作负载平台

VMware vSphere 9.0.1.0 发布 - 企业级工作负载平台VMware vSphere 9.0.1.0 发布 - 企业级工作负载平台 ESXi 9.0 & vCenter Server 9.0 | vSphere 9.0 请访问原文链接:https://sysin.org/blog/vmware-vsphere-9/…

《索引实战:结构与场景解析》 - 详解

《索引实战:结构与场景解析》 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Mona…

阿里云无影发布首个Agentic Computer形态的个人计算产品 - 详解

阿里云无影发布首个Agentic Computer形态的个人计算产品 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Co…