前言
似乎现在的应用,底部导航都是千篇一律,基本上都是几个按钮,点击之后,切换切换图标,更改更改颜色,稍微好点的会增加动画效果,今天,给大家推荐一款,底部的曲线导航,它可以让底部导航的实现方式别具一格。
我们先看下静态效果:

动态效果如下:

目前第一个版本,完成了基本的曲线形式效果,还未追加曲线的移动动画,在下一个版本会加上,那么针对这样的一个曲线形式导航,它是如何来实现的呢?答案很简单,就是使用简单的Path路径绘制。
Path路径,有两种方式实现,一种是系统提供的Path组件,另一种就是使用new drawing.Path(),然后使用canvas来绘制,两种方式都可以实现,看大家的技术选型,这里我使用的是Path组件,因为只需要设置符合SVG路径描述规范的命令字符串即可实现。
SVG路径介绍
SVG路径是SVG(可缩放矢量图形)中用于创建复杂形状的核心元素,通过数学描述定义图形轮廓,支持直线、曲线、弧线等多种图形绘制。
例如,绘制一个三角形,直接在属性commands传递SVG路径即可,代码如下:
TypeScript
Path()
.width('210px')
.height('310px')
.commands('M100 0 L200 240 L0 240 Z')
.fillOpacity(0)
.stroke(Color.Black)
.strokeWidth(3)
相对来说还是比较的简单,下面就针对SVG中的绘制命令,简单的罗列一下:

曲线绘制
由于需要点击时,让本按钮进行曲线展示,有两个方面需要考虑,一是曲线的位置,点击不同位置的按钮,曲线就会移动到指定位置,二是曲线连接之处,保持平滑过渡,使用三次贝塞尔曲线段完成。
完整的绘制曲线代码如下,大家可以作为参考,averageWidth为每个tab按钮的宽度,navigationSize为导航数量。
TypeScript
// 绘制曲线的函数
async drawCurve(position: number) {
// 初始点:0,100
let tagPosition = 0
let d = 'M 0 ' + tagPosition;
// 计算曲线的起始和结束位置
const curveStart = (position - 1) * this.averageWidth;
const curveEnd = position * this.averageWidth;
// 0到曲线起始位置的直线部分
if (curveStart > 0) {
d += ' L ' + curveStart + ' ' + tagPosition;
}
// 曲线部分:使用两个三次贝塞尔曲线段确保平滑连接
const midPoint = (curveStart + curveEnd) / 2;
const controlPoint1X = curveStart + (this.averageWidth / this.navigationSize);
const controlPoint2X = curveEnd - (this.averageWidth / this.navigationSize);
d += ' C ' + controlPoint1X + ' ' + tagPosition + ' ' + controlPoint1X + ' ' + this._privateCurveBottom + ' ' +
midPoint +
' ' +
this._privateCurveBottom;
d += ' C ' + controlPoint2X + ' ' + this._privateCurveBottom + ' ' + controlPoint2X + ' ' + tagPosition + ' ' +
curveEnd +
' ' + tagPosition;
// 曲线结束位置到最大宽度的直线部分
if (curveEnd < this._privateMaxWidth) {
d += ' L ' + this._privateMaxWidth + ' ' + tagPosition;
}
// 新增闭合路径逻辑
d += ` V ${this._privateCurveBottom}`; // 垂直延伸到底部
d += ' H 0'; // 水平返回起点
d += ' Z';
this._privatePathCommands = d
}
快速使用
如果你不想自己实现,而是想快速的使用,这个已经为大家考虑到了,目前已经上传到到了中心仓库,有需要的同学,可以前去体验。
中心仓库地址:
ohpm.openharmony.cn/#/cn/detail...
依赖方式
1、远程依赖
方式一:在Terminal窗口中,执行如下命令安装三方包,DevEco Studio会自动在工程的oh-package.json5中自动添加三方包依赖。
建议:在使用的模块路径下进行执行命令。
TypeScript
ohpm install @abner/curve_navigation
方式二:在模块的oh-package.json5中设置三方包依赖,配置示例如下:
TypeScript
"dependencies": { "@abner/curve_navigation": "^1.0.1"}
代码使用
TypeScript
CurveNavigation({
navigationSize: 5, //tab数量
navigationPaddingBottom: WindowUtils.windowBottomNavHeight,//距离底部距离
onPageChange: (position: number) => {
//page改变
this.selectIndex = position
},
tabView: (position: number) => {
//导航视图
this.tabView(position)
},
pageView: (position: number) => {
//页面视图
this.pageView(position)
}
})
完整案例
TypeScript
@Entry
@Component
struct Index {
private tempColorArray: ResourceColor[] = ["#ffcc80", "#ff9323ac", "#ff26a965", "#ff93d923", "#ff11c5de",]
@State selectIndex: number = 0
private tabArray: Resource[] =
[$r('sys.symbol.house_fill'), $r('sys.symbol.square_fill_grid_2x2'), $r('sys.symbol.bell_fill'),
$r('sys.symbol.externaldrive_fill'), $r('sys.symbol.person_2_fill')]
@Builder
tabView(position: number) {
if (this.selectIndex == position) {
Column() {
SymbolGlyph(this.tabArray[position])
.fontSize(20)
.renderingStrategy(SymbolRenderingStrategy.SINGLE)
.fontColor([Color.Black])
}
.backgroundColor(Color.White)
.width(35)
.height(35)
.borderRadius(35)
.justifyContent(FlexAlign.Center)
} else {
SymbolGlyph(this.tabArray[position])
.fontSize(20)
.renderingStrategy(SymbolRenderingStrategy.SINGLE)
.fontColor([Color.Gray])
}
}
@Builder
pageView(position: number) {
Column() {
Text(position.toString())
}.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
.backgroundColor(this.tempColorArray[position])
}
build() {
RelativeContainer() {
CurveNavigation({
navigationSize: 5, //tab数量
navigationPaddingBottom: WindowUtils.windowBottomNavHeight,
onPageChange: (position: number) => {
this.selectIndex = position
},
tabView: (position: number) => {
this.tabView(position)
},
pageView: (position: number) => {
this.pageView(position)
}
})
}
.height('100%')
.width('100%')
}
}
属性介绍

相关总结
目前唯一的遗憾是,没有实现曲线在切换时的动画偏移,而是直接的切换,后续会针对这块再单独的处理,大家可以先进行使用。