鸿蒙开发:一个底部的曲线导航

前言

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

我们先看下静态效果:

动态效果如下:

目前第一个版本,完成了基本的曲线形式效果,还未追加曲线的移动动画,在下一个版本会加上,那么针对这样的一个曲线形式导航,它是如何来实现的呢?答案很简单,就是使用简单的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%')
  }
}

属性介绍

相关总结

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

相关推荐
佛系打工仔25 分钟前
绘制K线第二章:背景网格绘制
android·前端·架构
AlbertZein25 分钟前
HarmonyOS下饭菜时间 -- @Monitor
harmonyos
AlbertZein27 分钟前
HarmonyOS一杯冰美式的时间 -- UIUtils基础功能
harmonyos
行者962 小时前
Flutter与OpenHarmony跨平台分享组件深度实践
flutter·harmonyos·鸿蒙
行者963 小时前
Flutter跨平台开发在OpenHarmony上的评分组件实现与优化
开发语言·flutter·harmonyos·鸿蒙
my_power5204 小时前
车载安卓面试题汇总
android
csj504 小时前
安卓基础之《(15)—内容提供者(1)在应用之间共享数据》
android
CareyWYR4 小时前
我开发了一款工具箱类型APP:CreativeUtil
ios·app·mac
yeziyfx4 小时前
kotlin中 ?:的用法
android·开发语言·kotlin
90后的晨仔5 小时前
HarmonyOS 多模块项目中的公共库治理与最佳实践
harmonyos