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

前言

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

我们先看下静态效果:

动态效果如下:

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

属性介绍

相关总结

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

相关推荐
Kapaseker2 小时前
数据传参明妙理 临危受命逢转机
android·kotlin
2501_915909062 小时前
如何在 Windows 上上架 iOS App,分析上架流程哪些是不用mac的
android·macos·ios·小程序·uni-app·iphone·webview
特立独行的猫a2 小时前
OpenHarmony开源鸿蒙应用签名机制深度解析与工具使用指南
华为·开源·harmonyos·签名
Fate_I_C2 小时前
Flutter鸿蒙0-1开发-flutter create <prjn>
flutter·华为·harmonyos·鸿蒙
Python私教2 小时前
鸿蒙应用的网络请求和数据处理:从HTTP到本地缓存的完整方案
网络·http·harmonyos
走在路上的菜鸟2 小时前
Android学Dart学习笔记第二十五节 类修饰符
android·笔记·学习·flutter
灵感菇_3 小时前
Android ContentProvider全面解析
android·通信·四大组件·contentprovider
2501_915921433 小时前
分析 iOS 描述文件创建与管理中常见的问题
android·ios·小程序·https·uni-app·iphone·webview
杜子不疼.3 小时前
【Linux】进程控制(三):进程程序替换机制与替换函数详解
android·linux·运维