0

OpenHarmony - ArkUI(TS)声明式开发之底部导航栏

 1 year ago
source link: https://blog.51cto.com/harmonyos/5567052
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

作者:梁青松

本项目基于 OpenHarmony的ArkUI框架:TS扩展的声明式开发范式,关于语法和概念直接看官网官方文档地址: 基于TS扩展的声明式开发范式,因为OpenHarmony的API相对于HarmonyOS的API,功能上比较完善和成熟的,有些新的技术也早早接触到,所以本项目直接使用OpenHarmony SDK开发。

工具版本: DevEco Studio 3.0 Beta4

SDK版本: 3.1.6.6(API Version 8 Release)

OpenHarmony - ArkUI(TS)声明式开发之底部导航栏_中软国际

主要API

 路径绘制组件Path 属性方法: commands(" 路径绘制的命令 ")

命令 参数 解释
M x,y 移动到指定的点x,y
L x,y 绘制一条线到指定点x,y
Q x1,y1 x,y 绘制x,y的二次贝塞尔曲线,x1,y1是控制点
A rx,ry x-rotation flag1 flag2 x,y 画椭圆,解释如下

A命令解释:

  • rx,ry:指所在椭圆的半轴大小
  • x-rotation:指椭圆的X轴与水平方向顺时针方向夹角
  • flag1:有两个值,1表示大角度弧线,0为小角度弧线。
  • flag2:有两个值,确定从起点至终点的方向,1为顺时针,0为逆时针
  • x,y: 为终点坐标

1. 绘制底部背景

底部背景:使用Path组件,根据路径命令绘制,矩形中间掏空一个半圆。(示例代码)

@Entry
@Component
struct Index {
  @State pathValue: string = ''
  build() {
    Row() {
      Path()
        .width('100%').height(60).fill('#FFD33C').commands(this.pathValue)
        .onAreaChange((oldValue: Area, newValue: Area) => {
          // 获取组件的宽高
          const width = vp2px(parseInt(newValue.width.toString()))
          const height = vp2px(parseInt(newValue.height.toString()))
          // 更改路径绘制的命令
          this.pathValue = `M0,0 L${width/2 - vp2px(40)},0
            A1,1 0 0 0 ${width/2 + vp2px(40)},0
            L${width},0 L${width},${height} L0,${height}`
        })
    }
    .justifyContent(FlexAlign.Center)
    .width('100%').height('100%')
  }
}

2. 绘制实心圆

实心圆、设置圆角:borderRadius 圆角半径为宽高一半,设置位置position,使其居中显示。(示例代码)

@Entry
@Component
struct Index {
  private navWidth = 0
  private navHeight = 0
  @State offsetX: number = 0
  @State pathValue: string = ''

  build() {
    Column() {
      Stack({ alignContent: Alignment.TopStart }) {
        // 背景
        Path()
          .width('100%').height('100%').fill('#FFD33C').commands(this.pathValue)
          .onAreaChange((oldValue: Area, newValue: Area) => {
            this.navWidth = vp2px(parseInt(newValue.width.toString()))
            this.navHeight = vp2px(parseInt(newValue.height.toString()))
            this.updateData(px2vp(this.navWidth / 2))
          })
        // 实心圆
        Row()
          .width(60).height(60).borderRadius(30)
          .backgroundColor('#FFD33C')
          .position({ x: this.offsetX - vp2px(10), y: -30 })
          .shadow({ radius: 15, color: '#ffaaaaaa' })

      }.width('100%').height(60)
    }
    .justifyContent(FlexAlign.Center)
    .width('100%').height('100%')
  }
  // 更新数据
  updateData(x) {
    this.offsetX = x
    this.pathValue = `M0,0 L${vp2px(x) - vp2px(40)},0
            A1,1 0 0 0 ${vp2px(x) + vp2px(40)},0
            L${this.navWidth},0 L${this.navWidth},${this.navHeight} L0,${this.navHeight}`
  }
}

3. 圆位置改变动画

添加触摸事件,更改x坐标点,使用  显式动画 添加动画效果。(示例代码)

@Entry
@Component
struct Index {
  private navWidth = 0
  private navHeight = 0
  @State offsetX: number = 0
  @State pathValue: string = ''

  build() {
    Column() {
      Stack({ alignContent: Alignment.TopStart }) {
        // 背景
        Path()
          .width('100%').height('100%').fill('#FFD33C').commands(this.pathValue)
          .onAreaChange((oldValue: Area, newValue: Area) => {
            this.navWidth = vp2px(parseInt(newValue.width.toString()))
            this.navHeight = vp2px(parseInt(newValue.height.toString()))
            this.updateData(px2vp(this.navWidth / 2))
          })
          .onTouch((event: TouchEvent) => this.touchEvent(event))
        // 实心圆
        Row()
          .width(60).height(60).borderRadius(30)
          .backgroundColor('#FFD33C')
          .position({ x: this.offsetX - vp2px(10), y: -30 })
          .shadow({ radius: 15, color: '#ffaaaaaa' })

      }.width('100%').height(60)
    }
    .justifyContent(FlexAlign.Center)
    .width('100%').height('100%')
  }
  // 触摸事件
  touchEvent(event: TouchEvent) {
    let x = event.touches[0].x
    if (event.type === TouchType.Up) {
      // 显式动画 
      animateTo({ delay: 50, duration: 300 }, () => {
        this.updateData(x)
      })
    }
  }
  // 更新数据
  updateData(x) {
    this.offsetX = x
    this.pathValue = `M0,0 L${vp2px(x) - vp2px(40)},0
            A1,1 0 0 0 ${vp2px(x) + vp2px(40)},0
            L${this.navWidth},0 L${this.navWidth},${this.navHeight} L0,${this.navHeight}`
  }
}

4. 图标文字布局动画

添加点击事件,更改选中索引,设置item中的Y轴offset偏移量,使用  属性动画 添加动画效果。(示例代码)

@Entry
@Component
struct Index {
  @State itemWidth: number = 0
  @State selectIndex: number = 0
  private listItem = ['首页', '分类', '购物车', '我的']

  build() {
    Column() {
      Text().layoutWeight(1)
      Row() {
        ForEach(this.listItem, (item, index) => {
          Column() {
            Image($r('app.media.icon'))
              .width(30).height(30)
              .margin({ top: 15, bottom: 24 })
            Text(item)
              .fontSize(12)
          }.backgroundColor('#FFD33C')
          .width(this.itemWidth)
          .height('100%')
          .offset({ y: this.selectIndex === index ? -23 : 0 })// Y轴偏移量
          .animation({ duration: 350 })// 属性动画
          .onClick(() => {
            // 点击更改选中的索引  
            this.selectIndex = index
          })
        }, item => item)
      }.width('100%')
      .height(60)
      .backgroundColor('#FFD33C')
    }
    .width('100%')
    .height('100%')
    .onAreaChange((oldValue: Area, newValue: Area) => {
      this.itemWidth = parseInt(newValue.width.toString()) / 4
    })
  }
}

本项目只用到了两种动画的基础功能,难点主要是组件Path中路径绘制命令(本项目只用到几个命令)感兴趣的可以去查下其他命令,各种命令搭配使用能做出比较炫酷的效果。

以上只是示例代码,完整代码因为有资源图标,代码细节也做了优化,具体请查看项目地址。

项目地址: https://gitee.com/liangdidi/BottomNavigationDemo

每天进步一点点、需要付出努力亿点点。

更多原创内容请关注: 中软国际 HarmonyOS 技术团队

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

 想了解更多关于开源的内容,请访问:

 51CTO 开源基础软件社区

 https://ost.51cto.com/#bkwz


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK