#夏日挑战赛# OpenHarmony - ArkUI(TS)开发翻页时钟 原创 精华

中软小助手
发布于 2022-7-19 14:22
浏览
2收藏

作者:梁青松

本文正在参加星光计划3.0–夏日挑战赛

项目介绍

本项目基于OpenHarmony的ArkUI框架:TS扩展的声明式开发范式,关于语法和概念直接看官网官方文档地址:基于TS扩展的声明式开发范式

工具版本: DevEco Studio 3.0 Beta4

SDK版本: 3.1.6.6(API Version 8 Release)

效果演示

#夏日挑战赛# OpenHarmony - ArkUI(TS)开发翻页时钟-鸿蒙开发者社区

主要知识点

  1. UI状态:@Prop@Link@Watch

  2. 形状裁剪属性: clip

  3. 显式动画:animateTo

实现思路

时钟翻页效果,用到四个Text组件,使用堆叠容器Stack,底层:用到两个裁剪过后的Text上下显示;顶层:也是用两个裁剪后的Text做动画效果,进行X轴角度旋转。

1. 裁剪Text

裁剪前

#夏日挑战赛# OpenHarmony - ArkUI(TS)开发翻页时钟-鸿蒙开发者社区

裁剪后

#夏日挑战赛# OpenHarmony - ArkUI(TS)开发翻页时钟-鸿蒙开发者社区

使用形状裁剪属性clip

  • 裁剪Text上半部:从坐标(0,0)往下裁剪,clip(new Rect({ width: this.width, height: this.height / 2 }))
  • 裁剪Text下半部:从坐标(0,height / 2)往下裁剪,clip(new Path().commands(this.bottomPath))
@Entry
@Component
struct Test {
  private width = 90
  private height = 110
  private fontSize = 70
  private defaultBgColor = '#ffe6e6e6'
  private borderRadius = 10

  // 下半部裁剪路径
  private bottomPath = `M0 ${vp2px(this.height / 2)}
  L${vp2px(this.width)} ${vp2px(this.height / 2)}
  L${vp2px(this.width)} ${vp2px(this.height)}
  L0 ${vp2px(this.height)} Z`

  build() {
    Row() {

      Text('24')
        .width(this.width)
        .height(this.height)
        .fontColor(Color.Black)
        .fontSize(this.fontSize)
        .textAlign(TextAlign.Center)
        .borderRadius(this.borderRadius)
        .backgroundColor(this.defaultBgColor)
        .clip(new Rect({ width: this.width, height: this.height / 2 }))

      Text('25')
        .margin({left:20})
        .width(this.width)
        .height(this.height)
        .fontColor(Color.Black)
        .fontSize(this.fontSize)
        .textAlign(TextAlign.Center)
        .borderRadius(this.borderRadius)
        .backgroundColor(this.defaultBgColor)
        .clip(new Path().commands(this.bottomPath))

    }.width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

2. 放入堆叠容器

四个裁剪后的Text放入到堆叠容器中(代码片段

    Stack() {
      // 底层文字上部
      Text(this.newValue)
        ......
        .clip(new Rect({ width: this.width, height: this.height / 2 }))

      // 底层文字下部
      Text(this.oldValue)
        ......
        .clip(new Path().commands(this.bottomPath))

      // 顶层文字上部动画
      Text(this.oldValue)
        ......
        .clip(new Rect({ width: this.width, height: this.height / 2 }))
        .rotate({ x: 1, centerY: '50%', angle: this.angleTop })

      // 顶层文字下部动画
      Text(this.newValue)
        ......
        .margin({ top: 3 })
        .clip(new Path().commands(this.bottomPath))
        .rotate({ x: 1, centerY: '50%', angle: this.angleBottom })
    }

3. 使用显式动画

先顶层上部的动画,上部旋转角度从0到90停止,接下来执行顶层下部的动画,下部旋转角度从-90到0停止,停止完后重置初始状态,上部旋转角度 = 0、下部旋转角度 = -90(代码片段

  /**
   * 启动顶层文字上部动画
   */
  startTopAnimate() {
    animateTo({
      duration: 400,
      onFinish: () => {
        this.startBottomAnimate()
        this.animateBgColor = '#ffededed'
      }
    }, () => {
      this.angleTop = 90
      this.animateBgColor = '#ffc5c5c5'
    })
  }

  /**
   * 启动顶层文字下部动画
   */
  startBottomAnimate() {
    animateTo({
      duration: 400,
      onFinish: () => {
        this.angleTop = 0
        this.angleBottom = -90
        this.animateBgColor = this.defaultBgColor
        this.oldValue = this.newValue
      }
    }, () => {
      this.angleBottom = 0
      this.animateBgColor = this.defaultBgColor
    })
  }

4. 组件封装

翻页逻辑封装成组件,提供给外部调用,根据外部传入的双向数据绑定:newValue,监听数据变化,有变化则启动翻页动画(代码片段

@Component
export struct FlipPage {
  // 顶层上部动画角度
  @State angleTop: number = 0
  // 顶层下部动画角度
  @State angleBottom: number = -90
  // 旧值
  @Prop oldValue: string
  // 新值,加入监听
  @Link @Watch('valueChange') newValue: string

  /**
   * 监听新值变化
   */
  valueChange() {
    if (this.oldValue === this.newValue) return
    this.startTopAnimate()
  }

  build() {
    Stack() {
      // 底层文字上部
      Text(this.newValue)
        ......
        .clip(new Rect({ width: this.width, height: this.height / 2 }))

      // 底层文字下部
      Text(this.oldValue)
        ......
        .clip(new Path().commands(this.bottomPath))

      // 顶层文字上部动画
      Text(this.oldValue)
        ......
        .clip(new Rect({ width: this.width, height: this.height / 2 }))
        .rotate({ x: 1, centerY: '50%', angle: this.angleTop })

      // 顶层文字下部动画
      Text(this.newValue)
        ......
        .margin({ top: 3 })
        .clip(new Path().commands(this.bottomPath))
        .rotate({ x: 1, centerY: '50%', angle: this.angleBottom })
    }
  }
  /**
  * 启动顶层文字上部动画
  */
  startTopAnimate() {
    ......
  }

5. 外部调用

界面加载成功后,开启循环定时器setInterval、间隔1秒更新时间。更改newValue的值,翻页组件内部进行动画翻页。

import { FlipPage } from '../componet/FlipPage'

@Entry
@Component
struct Index {
  // 小时-旧值
  @State oldHours: string = ''
  // 小时-新值
  @State newHours: string = ''
  // 分钟-旧值
  @State oldMinutes: string = ''
  // 分钟-新值
  @State newMinutes: string = ''
  // 秒数-旧值
  @State oldSeconds: string = ''
  // 秒数-新值
  @State newSeconds: string = ''

  @Builder Colon() {
    Column() {
      Circle().width(8).height(8).fill(Color.Black)
      Circle().width(8).height(8).fill(Color.Black).margin({ top: 10 })
    }.padding(10)
  }

  build() {
    Row() {
      // 翻页组件-显示小时
      FlipPage({ oldValue: this.oldHours, newValue: $newHours })
      // 冒号
      this.Colon()
      // 翻页组件-显示分钟
      FlipPage({ oldValue: this.oldMinutes, newValue: $newMinutes })
      // 冒号
      this.Colon()
      // 翻页组件-显示秒数
      FlipPage({ oldValue: this.oldSeconds, newValue: $newSeconds })
    }
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
    .onAppear(() => {
      // 开启定时器
      this.initDate()
      setInterval(() => {
        this.updateDate()
      }, 1000)
    })
  }

  /**
   * 初始化时间
   */
  initDate() {
    let date = new Date()
    // 设置小时
    this.oldHours = this.format(date.getHours())
    // 设置分钟
    this.oldMinutes = this.format(date.getMinutes())
    // 设置秒数
    this.oldSeconds = this.format(date.getSeconds())
    // 设置新的秒数
    this.newSeconds = date.getSeconds() + 1 === 60 ? '00' : this.format(date.getSeconds() + 1)
  }

  /**
   * 更新时间
   */
  updateDate() {
    let date = new Date()
    console.log(`${date.getHours()}时${date.getMinutes()}分${date.getSeconds()}秒`)
    // 当新值改变,才有动画
    if (date.getSeconds() === 59) {
      this.newSeconds = '00'
      this.newMinutes = date.getMinutes() + 1 === 60 ? '00' : this.format(date.getMinutes() + 1)
      if (date.getMinutes() === 59) {
        this.newHours = date.getHours() + 1 === 24 ? '00' : this.format(date.getHours() + 1)
      }
    } else {
      this.newSeconds = this.format(date.getSeconds() + 1)
    }
  }

  /**
   * 不足十位前面补零
   */
  format(param) {
    let value = '' + param
    if (param < 10) {
      value = '0' + param
    }
    return value
  }
}

总结

根据上面的实现思路和5个步骤流程,相信你也掌握了翻页时钟原理,拆分成一步一步还是很简单的,最主要还是对API的熟悉和声明式语法的掌握。HarmonyOS的API是根据OpenHarmony去更新的,两者区别语法都一样,只是OpenHarmony的API比较新,功能比较完善和成熟的,所以本项目直接使用OpenHarmony SDK开发。

项目地址:OpenHarmony - ArkUI(TS)开发翻页时钟

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

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-11-22 10:29:43修改
9
收藏 2
回复
举报
3条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

学到了非常有用的动画制作方式,看老师结尾的总结,也就是说如果学鸿蒙还是直接学OpenHarmony比较好吗?

回复
2022-7-19 14:46:59
红叶亦知秋
红叶亦知秋 回复了 l平凡之路l
抱歉,此内容已被作者删除

感谢老师回复

回复
2022-7-19 16:09:19
隐身暴富
隐身暴富

松哥666

回复
2022-7-22 17:06:28
回复
    相关推荐