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

Posted 开源基础软件社区官方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#夏日挑战赛# OpenHarmony - ArkUI(TS)开发翻页时钟相关的知识,希望对你有一定的参考价值。

作者:梁青松

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

项目介绍

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

工具版本: DevEco Studio 3.0 Beta4

SDK版本: 3.1.6.6(API Version 8 Release)

效果演示

主要知识点

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

  2. 形状裁剪属性: clip

  3. 显式动画:animateTo

实现思路

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

1. 裁剪Text

裁剪前

裁剪后

使用形状裁剪属性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开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

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

51CTO 开源基础软件社区

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

以上是关于#夏日挑战赛# OpenHarmony - ArkUI(TS)开发翻页时钟的主要内容,如果未能解决你的问题,请参考以下文章

#夏日挑战赛# OpenHarmony基于JS实现的贪吃蛇

#夏日挑战赛#FFH分布式数据服务简单实现(OpenHarmony JS UI)

#夏日挑战赛# 用OpenHarmony eTS 实现一个Huawei app标准布局

#夏日挑战赛# HarmonyOS 实现一个手绘板

#夏日挑战赛#OHOS构建自定义服务实战

#夏日挑战赛#ELT.ZIP啃论文俱乐部——学术科研方法论沉淀辑