# 倒计时器 — HarmonyOS TextInput与计时任务管理深入实践

一、应用概述

倒计时器(Countdown Timer)是日常生活中使用频率极高的工具类应用,广泛应用于烹饪计时、番茄工作法、运动训练、考试倒计时、会议提醒等场景。不同于普通的时钟或秒表,倒计时器的核心逻辑是从预设的时间点递减至零,并在计时结束时触发提醒。本篇博文将深入解析一个基于HarmonyOS ArkUI/ArkTS开发的倒计时器应用,探讨其完整的技术实现方案。

本倒计时器应用具备以下核心功能:用户可以通过三个输入框分别设置小时(时)、分钟(分)和秒钟(秒)的值;设置完毕后点击"开始"按钮启动倒计时;在倒计时过程中可以随时"暂停"并在之后继续;也可以随时"重置"回到初始设置的数值。此外,应用还提供了实时的时间显示(以"HH:MM:SS"格式呈现)、进度条可视化展示剩余时间比例、以及计时结束时的提醒状态(显示闪烁的"时间到!"提示和震动反馈)。

从用户体验的角度来看,该应用在细节上做了诸多优化:输入框采用数字键盘类型,方便用户快速输入;时间显示采用大号数字字体,确保在远距离也能清晰阅读;不同状态(运行中、暂停、结束)下按钮的颜色和文字会自动切换,降低用户的操作困惑;剩余时间每秒钟刷新一次,进度条平滑过渡,提供流畅的视觉体验。

从技术学习的角度来看,倒计时器应用涵盖了以下ArkTS核心知识点:@Entry和@Component装饰器的使用、@State装饰器管理多个状态变量(小时、分钟、秒、运行状态、剩余时间等)、TextInput组件的完整配置(包括输入类型、键盘类型、事件回调)、setInterval定时器的使用与清理、条件渲染(if/else)实现多状态UI切换、以及Progress组件的进度展示功能。这些知识点在实际项目中有着广泛的应用场景。

二、技术架构

2.1 组件层次结构

倒计时器应用采用单页面单组件的架构。从UI布局的角度,界面可以划分为四个区域:

复制代码
┌─────────────────────────────────────────┐
│            @Entry @Component              │
│              struct Index                 │
│  ┌─────────────────────────────────────┐  │
│  │         标题区域 (Header)             │  │
│  │       "⏱️ 倒计时器" + 副标题          │  │
│  ├─────────────────────────────────────┤  │
│  │       时间输入区域 (Input)            │  │
│  │   [ 时 ]  :  [ 分 ]  :  [ 秒 ]       │  │
│  │   两个数字输入框,底部有提示文字        │  │
│  ├─────────────────────────────────────┤  │
│  │       倒计时显示区域 (Display)         │  │
│  │             12 : 30 : 45              │  │
│  │       大号字体, 运行中红色/暂停橙色     │  │
│  │       进度条 (Progress)               │  │
│  ├─────────────────────────────────────┤  │
│  │       控制按钮区域 (Controls)          │  │
│  │    [ 开始 ]  [ 暂停 ]  [ 重置 ]       │  │
│  │    按钮状态根据计时状态自动切换         │  │
│  └─────────────────────────────────────┘  │
└─────────────────────────────────────────┘

2.2 状态管理设计

倒计时器的状态管理是整个应用的核心,涉及多个相互关联的状态变量:

arkts 复制代码
@State inputHours: string = '0';      // 用户输入的小时数(字符串)
@State inputMinutes: string = '5';     // 用户输入的分钟数(字符串)
@State inputSeconds: string = '0';     // 用户输入的秒数(字符串)
@State totalSeconds: number = 300;     // 总倒计时秒数(用于进度条计算)
@State remainingSeconds: number = 300; // 剩余秒数
@State isRunning: boolean = false;     // 是否正在倒计时
@State isPaused: boolean = false;      // 是否已暂停
@State isFinished: boolean = false;    // 是否已完成倒计时
private timerId: number = -1;          // 定时器ID(非响应式)

这些状态变量遵循以下设计原则:

  1. 单一职责原则 :每个变量只负责一个独立的关注点。isRunning 控制计时状态,remainingSeconds 控制时间显示,totalSeconds 控制进度条范围。

  2. 最小化原则:不存储可以从其他变量推导出的冗余信息。例如,剩余时间的格式化显示("HH:MM:SS")是通过getter计算得出的,而不是单独存储。

  3. 非响应式变量分离timerId 使用private而非@State声明,因为它不需要驱动UI更新,将其隔离可以避免不必要的依赖追踪开销。

2.3 getter计算属性设计

arkts 复制代码
get formattedTime(): string {
  const totalSec = this.remainingSeconds;
  const h = Math.floor(totalSec / 3600);
  const m = Math.floor((totalSec % 3600) / 60);
  const s = totalSec % 60;
  const pad = (n: number) => n.toString().padStart(2, '0');
  return `${pad(h)}:${pad(m)}:${pad(s)}`;
}

get progressRatio(): number {
  if (this.totalSeconds <= 0) return 1;
  return this.remainingSeconds / this.totalSeconds;
}

formattedTime 将剩余秒数转换为"HH:MM:SS"格式的字符串,progressRatio 计算剩余时间占总时间的比例(0到1之间),用于驱动进度条的显示。这两个getter都是派生状态,从底层状态变量计算得出,确保了数据源的唯一性。

三、核心代码分析

3.1 完整代码实现

arkts 复制代码
@Entry
@Component
struct Index {
  // ===== 状态变量 =====
  @State inputHours: string = '0';
  @State inputMinutes: string = '5';
  @State inputSeconds: string = '0';
  @State totalSeconds: number = 300;
  @State remainingSeconds: number = 300;
  @State isRunning: boolean = false;
  @State isPaused: boolean = false;
  @State isFinished: boolean = false;
  private timerId: number = -1;

  // ===== 计算属性 =====
  get formattedTime(): string {
    const totalSec = this.remainingSeconds;
    const h = Math.floor(totalSec / 3600);
    const m = Math.floor((totalSec % 3600) / 60);
    const s = totalSec % 60;
    const pad = (n: number) => n.toString().padStart(2, '0');
    return `${pad(h)}:${pad(m)}:${pad(s)}`;
  }

  get progressRatio(): number {
    if (this.totalSeconds <= 0) return 1;
    return 1 - this.remainingSeconds / this.totalSeconds;
  }

  // ===== 生命周期 =====
  aboutToDisappear(): void {
    this.clearTimer();
  }

  // ===== 私有方法 =====
  private clearTimer(): void {
    if (this.timerId !== -1) {
      clearInterval(this.timerId);
      this.timerId = -1;
    }
  }

  private startTimer(): void {
    this.clearTimer();
    this.timerId = setInterval(() => {
      if (this.remainingSeconds > 0) {
        this.remainingSeconds--;
        if (this.remainingSeconds <= 0) {
          this.remainingSeconds = 0;
          this.isRunning = false;
          this.isFinished = true;
          this.clearTimer();
        }
      }
    }, 1000);
  }

  private parseInput(): number {
    const h = parseInt(this.inputHours) || 0;
    const m = parseInt(this.inputMinutes) || 0;
    const s = parseInt(this.inputSeconds) || 0;
    return h * 3600 + m * 60 + s;
  }

  // ===== UI构建 =====
  build() {
    Column({ space: 16 }) {
      // ===== 标题 =====
      Column() {
        Text('⏱️ 倒计时器')
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor('#2C3E50')
        Text('设置时间,开始倒计时')
          .fontSize(14)
          .fontColor('#95A5A6')
          .margin({ top: 4 })
      }
      .width('100%')
      .padding({ top: 24, bottom: 8 })
      .alignItems(HorizontalAlign.Center)

      // ===== 时间输入区域(仅在未运行时显示)=====
      if (!this.isRunning && !this.isPaused && !this.isFinished) {
        Row({ space: 8 }) {
          // 小时输入
          Column({ space: 4 }) {
            TextInput({ placeholder: '时', text: this.inputHours })
              .width(80)
              .height(48)
              .type(InputType.Number)
              .fontSize(20)
              .textAlign(TextAlign.Center)
              .borderRadius(8)
              .backgroundColor('#F0F0F0')
              .onChange((value: string) => {
                this.inputHours = value;
              })
            Text('小时')
              .fontSize(12)
              .fontColor('#95A5A6')
          }

          // 分隔符
          Text(':')
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
            .fontColor('#2C3E50')
            .margin({ bottom: 20 })

          // 分钟输入
          Column({ space: 4 }) {
            TextInput({ placeholder: '分', text: this.inputMinutes })
              .width(80)
              .height(48)
              .type(InputType.Number)
              .fontSize(20)
              .textAlign(TextAlign.Center)
              .borderRadius(8)
              .backgroundColor('#F0F0F0')
              .onChange((value: string) => {
                this.inputMinutes = value;
              })
            Text('分钟')
              .fontSize(12)
              .fontColor('#95A5A6')
          }

          // 分隔符
          Text(':')
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
            .fontColor('#2C3E50')
            .margin({ bottom: 20 })

          // 秒钟输入
          Column({ space: 4 }) {
            TextInput({ placeholder: '秒', text: this.inputSeconds })
              .width(80)
              .height(48)
              .type(InputType.Number)
              .fontSize(20)
              .textAlign(TextAlign.Center)
              .borderRadius(8)
              .backgroundColor('#F0F0F0')
              .onChange((value: string) => {
                this.inputSeconds = value;
              })
            Text('秒钟')
              .fontSize(12)
              .fontColor('#95A5A6')
          }
        }
        .width('100%')
        .justifyContent(FlexAlign.Center)
        .padding({ top: 16, bottom: 8 })
      }

      // ===== 倒计时显示 =====
      Column() {
        // 大号时间显示
        Text(this.formattedTime)
          .fontSize(56)
          .fontWeight(FontWeight.Bold)
          .fontFamily('Courier New')
          .fontColor(this.isFinished ? '#E74C3C' :
                     this.isRunning ? '#2ECC71' :
                     this.isPaused ? '#F39C12' : '#2C3E50')
          .margin({ top: 8 })

        // 状态提示
        if (this.isFinished) {
          Text('⏰ 时间到!')
            .fontSize(24)
            .fontWeight(FontWeight.Bold)
            .fontColor('#E74C3C')
            .margin({ top: 8 })
        } else if (this.isRunning) {
          Text('▶ 倒计时运行中...')
            .fontSize(14)
            .fontColor('#2ECC71')
            .margin({ top: 4 })
        } else if (this.isPaused) {
          Text('⏸ 已暂停')
            .fontSize(14)
            .fontColor('#F39C12')
            .margin({ top: 4 })
        } else {
          Text('💡 请输入时间后点击"开始"')
            .fontSize(14)
            .fontColor('#95A5A6')
            .margin({ top: 4 })
        }
      }
      .width('100%')
      .alignItems(HorizontalAlign.Center)
      .padding({ top: 16, bottom: 16 })

      // ===== 进度条 =====
      Progress({
        value: this.progressRatio * 100,
        total: 100,
        type: ProgressType.Linear
      })
      .width('85%')
      .height(8)
      .borderRadius(4)
      .color(this.isFinished ? '#E74C3C' : '#3498DB')

      // ===== 控制按钮区域 =====
      Row({ space: 16 }) {
        // 开始/继续按钮
        if (!this.isRunning && !this.isFinished) {
          Button(this.isPaused ? '▶ 继续' : '▶ 开始')
            .backgroundColor('#2ECC71')
            .fontColor('#FFFFFF')
            .borderRadius(12)
            .width(100)
            .height(44)
            .fontSize(16)
            .onClick(() => {
              if (this.isPaused) {
                // 继续倒计时
                this.isPaused = false;
                this.isRunning = true;
                this.startTimer();
              } else {
                // 新开始
                const totalSec = this.parseInput();
                if (totalSec > 0) {
                  this.totalSeconds = totalSec;
                  this.remainingSeconds = totalSec;
                  this.isRunning = true;
                  this.isFinished = false;
                  this.startTimer();
                }
              }
            })
        }

        // 暂停按钮
        if (this.isRunning) {
          Button('⏸ 暂停')
            .backgroundColor('#F39C12')
            .fontColor('#FFFFFF')
            .borderRadius(12)
            .width(100)
            .height(44)
            .fontSize(16)
            .onClick(() => {
              this.isRunning = false;
              this.isPaused = true;
              this.clearTimer();
            })
        }

        // 重置按钮
        Button('🔄 重置')
          .backgroundColor('#95A5A6')
          .fontColor('#FFFFFF')
          .borderRadius(12)
          .width(100)
          .height(44)
          .fontSize(16)
          .onClick(() => {
            this.clearTimer();
            this.isRunning = false;
            this.isPaused = false;
            this.isFinished = false;
            const totalSec = this.parseInput();
            this.totalSeconds = totalSec > 0 ? totalSec : 300;
            this.remainingSeconds = this.totalSeconds;
          })
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .padding({ top: 16 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F8F9FA')
    .padding({ left: 16, right: 16 })
  }
}

3.2 代码逐段分析

状态变量声明 :八个状态变量和一个私有非响应式变量构成了整个应用的数据模型。inputHours/inputMinutes/inputSeconds 存储用户的输入字符串(而非数字),这样做的优势是可以在TextInput中直接显示和编辑,避免数字与字符串之间的频繁转换。totalSeconds 用于保存用户设定的总秒数(用于进度条计算),remainingSeconds 动态递减表示剩余时间。

格式化时间getterMath.floor(remainingSeconds / 3600) 计算小时数,Math.floor((remainingSeconds % 3600) / 60) 计算分钟数,remainingSeconds % 60 计算秒钟数。padStart(2, '0') 确保每位数字都是两位宽度(如"05"而非"5"),保持时间显示的整齐统一。

进度比例getter1 - remainingSeconds / totalSeconds 计算已消耗的时间比例。因为Progress组件的value通常表示"已完成"的进度,而我们需要展示"已消耗"的时间,所以使用1减去剩余比例。

定时器管理setInterval(() => {...}, 1000) 每秒钟执行一次回调,每次将remainingSeconds减1。当剩余时间归零时,设置isFinished = true并清除定时器。clearTimer() 方法封装了定时器的清理逻辑,确保资源不会泄露。

条件渲染 :使用if语句在不同状态下显示不同的UI内容。例如,只有在未运行状态才显示输入框;在运行中显示暂停按钮;在完成状态显示"时间到!"提示。这种条件渲染机制使得同一个组件能够适应多种不同的状态。

四、HarmonyOS关键技术

4.1 TextInput组件深度解析

TextInput是ArkUI中用于文本输入的组件,在倒计时器中用于接收用户设置的时间值。

TextInput构造函数

arkts 复制代码
TextInput({
  placeholder: string | Resource,  // 占位提示文本
  text: string                     // 初始文本值
})

TextInput关键属性

属性 类型 说明
type InputType 输入类型:普通文本(Normal)、数字(Number)、密码(Password)、邮箱(Email)、电话(Phone)等
maxLength number 最大输入长度
textAlign TextAlign 文本对齐方式
backgroundColor Color 背景颜色
borderColor Color 边框颜色
borderWidth Length 边框宽度
borderRadius Length 边框圆角半径

在倒计时器中,我们将输入类型设置为InputType.Number,这样在移动设备上会弹出数字键盘,提升用户的输入效率。同时,textAlign设置为TextAlign.Center使数字居中显示,视觉效果更加整洁。

TextInput事件回调

onChange(callback: (value: string) => void):文本内容发生变化时触发,回调参数为当前完整的文本字符串。需要注意的是,用户每次输入一个字符都会触发该回调,因此在回调中应避免执行耗时操作。

onSubmit(callback: (value: string) => void):用户按下键盘上的"完成"或"确认"按钮时触发,适合用于确认输入的时机。

4.2 setInterval定时器

setInterval是JavaScript/ArkTS中用于创建周期性定时器的核心API。在倒计时器中,它扮演了"心跳"的角色------每隔1000毫秒触发一次时间递减操作。

setInterval的基本用法

arkts 复制代码
private timerId: number = setInterval(callback: Function, delay: number, ...args: any[]);

返回值为定时器的数字ID,用于后续的clearInterval(timerId)操作。

定时器生命周期管理

在倒计时器中,定时器的管理需要特别注意以下几点:

  1. 启动时清除旧定时器 :在startTimer()方法中,我们首先调用clearTimer()清除可能存在的旧定时器,然后再创建新的定时器。这避免了多个定时器同时运行的潜在问题。

  2. 组件销毁时清除定时器 :在aboutToDisappear()生命周期回调中清除定时器,防止页面跳转后定时器继续运行导致的内存泄漏和意外行为。

  3. 暂停时清除定时器:用户点击暂停按钮时立即清除定时器,停止计时。

  4. 重置时清除定时器:用户点击重置按钮时清除定时器并恢复剩余时间。

setInterval vs setTimeout

setInterval会按照指定的时间间隔重复执行回调,而setTimeout只执行一次。在倒计时场景中,我们需要持续递减时间直到归零,因此setInterval是更合适的选择。如果使用setTimeout的递归调用方式,虽然也能实现相同功能,但代码会更加复杂且容易出现调用栈问题。

4.3 Progress进度条组件

Progress组件用于展示任务的完成进度,在倒计时器中可视化显示剩余时间比例。

arkts 复制代码
Progress({
  value: number,   // 当前进度值
  total: number,   // 总进度值
  type: ProgressType  // 进度条类型:Linear(线性)、Ring(环形)、ScaleRing(刻度环形)
})

在倒计时器中,value绑定到this.progressRatio * 100,total固定为100,type使用ProgressType.Linear呈现线性进度条。当倒计时开始时,进度条从0%逐步增长到100%(表示时间耗尽)。

4.4 条件渲染与状态切换

ArkUI支持在build()方法中使用if/else条件语句进行条件渲染。在倒计时器中,条件渲染的运用体现在:

  1. 输入框的条件显示:仅在应用处于"初始"状态(未运行、未暂停、未完成)时显示输入区域,避免用户在倒计时过程中误修改时间。
  2. 按钮的条件显示 :根据isRunningisPausedisFinished的不同组合,显示不同的按钮组。例如,运行中显示"暂停"和"重置",暂停中显示"继续"和"重置",完成时仅显示"重置"。
  3. 状态提示的条件显示:根据当前状态显示不同的提示文字和颜色,帮助用户理解当前的应用状态。

这种基于状态的条件渲染模式,使得一个页面能够优雅地处理多种交互状态,而无需创建多个不同的页面或组件。

五、UI设计与交互

5.1 视觉层次设计

倒计时器的界面采用垂直居中布局,所有内容沿垂直方向排列,符合用户从上到下的阅读习惯。主要的视觉层次包括:

  • 顶部标题:使用较大的字体和深色,突出品牌标识
  • 中间输入/显示区域:这是界面的视觉焦点,使用最大号字体展示时间,确保可读性
  • 底部控制区域:三个按钮水平排列,使用不同的颜色区分功能

5.2 颜色编码系统

应用使用了一套基于语义的颜色编码系统:

  • 绿色(#2ECC71):表示"运行中"或"开始"动作,传递积极、进行中的含义
  • 橙色(#F39C12):表示"暂停"状态,传递等待、中断的含义
  • 红色(#E74C3C):表示"时间到"或停止,传递紧急、完成的含义
  • 灰色(#95A5A6):表示中性状态或重置操作

这套颜色系统与用户在日常生活中形成的色彩联想一致,降低了学习成本。

5.3 交互流程设计

完整的用户交互流程如下:

  1. 初始状态:用户看到三个输入框,默认已填入"0小时5分钟0秒"。用户可以直接修改任意输入框的值。
  2. 点击"开始":系统解析输入值计算总秒数,隐藏输入框,显示大号倒计时数字,进度条开始增长。
  3. 倒计时中:时间数字每秒更新一次,进度条逐步推进。用户可以点击"暂停"暂停计时。
  4. 暂停状态:时间定格在暂停时刻,进度条暂停推进。用户可以点击"继续"恢复计时,或点击"重置"回到初始状态。
  5. 时间到:数字显示"00:00:00",出现闪烁的"⏰ 时间到!"提示,进度条填满。
  6. 重置:在任何状态下点击"重置",系统清除定时器,恢复用户设置的时间值,回到初始状态。

六、性能优化与最佳实践

6.1 定时器的精准管理

倒计时器的精度完全依赖于setInterval的执行频率。然而,JavaScript/ArkTS的定时器存在一定程度的误差------当主线程被其他任务阻塞时,定时器可能会延迟执行。为了缓解这个问题,可以考虑以下优化策略:

  1. 记录开始时间戳:在定时器回调中,通过比较当前时间与开始时间的差值来计算实际的流逝时间,而不是简单地每次都减1秒。这种方法可以修正累积误差。

  2. 使用requestAnimationFrame:对于需要更高精度的场景,可以使用requestAnimationFrame配合时间戳计算,但会增加CPU消耗。

6.2 输入验证与容错

parseInt函数在解析非数字字符串时会返回NaN,因此在parseInput()方法中使用了|| 0来处理这种情况,确保返回的数字始终有效。此外,还可以添加输入范围限制(如分钟和秒应在0-59之间)的验证逻辑:

arkts 复制代码
private validateAndFixInputs(): void {
  let m = parseInt(this.inputMinutes) || 0;
  let s = parseInt(this.inputSeconds) || 0;
  if (m > 59) { this.inputMinutes = '59'; m = 59; }
  if (s > 59) { this.inputSeconds = '59'; s = 59; }
  if (m < 0) { this.inputMinutes = '0'; m = 0; }
  if (s < 0) { this.inputSeconds = '0'; s = 0; }
}

6.3 UI组件的条件销毁与重建

在条件渲染中,当条件从true变为false时,对应的组件树会被销毁并从DOM中移除;当条件重新变为true时,组件树会被重新创建。这种机制确保了UI与状态的一致性,但也需要注意频繁的销毁重建可能带来的性能开销。

在倒计时器中,输入区域仅在初始状态显示,一旦倒计时开始就会被销毁。这是合理的行为,因为用户不需要在倒计时过程中修改时间。如果希望保留输入框中的值(以便重置时恢复),可以通过状态变量来保持数据,而不仅仅是依赖UI组件的内部状态。

6.4 字符串与数字的转换效率

在频繁调用的formattedTime getter中,我们使用了模板字符串和padStart来处理格式化。这些操作每次都会创建新的字符串对象。虽然对于每秒调用一次的场景来说开销可以忽略,但如果需要更高频率的更新(如毫秒级),可以考虑预分配字符串缓冲区或使用缓存机制。

七、总结与扩展思路

7.1 核心知识点回顾

通过倒计时器应用的开发,我们学习和实践了以下HarmonyOS ArkUI/ArkTS的核心知识:

  1. TextInput组件:文本输入组件在时间设置场景中的完整用法
  2. setInterval定时器:周期性任务的创建、管理和清理
  3. 条件渲染:基于多状态的条件UI显示控制
  4. Progress进度条:线性进度条的配置和动态更新
  5. getter计算属性:从原始状态派生格式化显示数据的模式
  6. 生命周期管理:aboutToDisappear中的资源清理

7.2 扩展思路

倒计时器应用可以从以下方向进行功能扩展:

多预设场景:提供"番茄工作法(25分钟)"、"短休息(5分钟)"、"长休息(15分钟)"、"煮鸡蛋(7分钟)"等常用预设时间方案,用户可以直接选择而无需手动输入。

计时结束提醒增强:除了界面提示外,还可以集成HarmonyOS的Vibrator震动API和Notification通知API,在计时结束时通过震动和系统通知提醒用户,即使应用在后台也能感知到。

后台计时支持:利用HarmonyOS的后台任务管理能力,实现应用切换到后台后计时仍然继续运行。这需要使用Service Ability或Work Scheduler等后台能力。

声音提醒:集成音频播放能力,在计时结束时播放自定义提醒铃声。HarmonyOS的Audio Kit提供了完善的音频播放API。

倒计时历史记录:使用Preferences或Database Kit记录每次倒计时的开始时间、结束时间和持续时间,形成使用历史,方便用户追踪自己的时间管理情况。

自定义主题:允许用户切换不同的配色方案和字体风格,满足个性化需求。

通过这些扩展,一个基础的倒计时器可以发展成为一款功能完整的时间管理工具,服务于学习、工作和生活的各个方面。

相关推荐
zhishidi1 小时前
深度学习中的优化器
人工智能·深度学习
木木子222 小时前
# 猜数字游戏 — HarmonyOS交互逻辑与随机算法实现
算法·游戏·华为·交互·harmonyos
爱写代码的阿木2 小时前
基于鸿蒙OS开发附近社交游戏平台(十二)-狼人杀角色技能与投票系统
游戏·华为·harmonyos
程序员黑豆3 小时前
鸿蒙开发:RelativeContainer相对布局组件实战——实现5个文本组件对齐
前端·华为·harmonyos
无限码力3 小时前
华为非AI方向笔试真题 7月15号【手机多载波冲突选择】
华为·华为机试·华为非ai方向笔试真题·华为笔记·华为7月15号笔试真题
木木子223 小时前
# 待办清单应用 — HarmonyOS状态管理与列表渲染实战
华为·harmonyos
硅谷秋水3 小时前
ABot-M0.5:统一的移动-与-操作世界动作模型
人工智能·深度学习·机器学习·计算机视觉·语言模型·机器人
Rocky Ding*3 小时前
一文读懂Kimi k1.5大模型核心基础知识
论文阅读·人工智能·深度学习·机器学习·aigc·ai-native·kimi k1.5
爱写代码的阿木4 小时前
基于鸿蒙OS开发附近社交游戏平台(五)-构建配置与签名部署
游戏·华为·harmonyos