一、应用概述

密码锁(Password Lock) 是一款模拟数字密码锁的应用。它模拟了真实密码锁的完整操作流程:设置密码 → 确认密码 → 解锁验证 → 错误锁定。用户通过数字键盘输入 4 位密码,系统根据当前阶段执行不同逻辑,错误次数超过 5 次后触发 5 秒锁定机制。

从技术角度看,这是系列教程中状态机设计最为复杂的一个应用。它包含四个明确的阶段(set / confirm / unlock / locked),每个阶段对用户输入的响应完全不同。此外,应用还引入了 Grid 网格布局构建数字键盘、Circle 组件作为密码点显示、setTimeout 一次性定时器等功能,是状态管理和布局设计的集大成者。

二、技术架构与组件树

2.1 整体架构

@Entry @Component struct index38
├── Column (根容器)
│   ├── Row (顶部导航栏)
│   └── Column (主内容区, 居中)
│       ├── Text (提示消息, 颜色动态变化)
│       ├── Row (密码点, 4个 Circle)
│       │   └── ForEach → Circle × 4
│       ├── Grid (数字键盘, columnsTemplate="1fr 1fr 1fr")
│       │   ├── GridItem × 9 ("1" ~ "9")
│       │   ├── GridItem ("")
│       │   ├── GridItem ("0")
│       │   └── GridItem ("⌫" 删除键)
│       └── Button ("🔄 重置")

2.2 状态变量设计

@State password: string = '';           // 当前输入的密码
@State savedPassword: string = '1234';  // 已保存的密码
@State stage: 'set' | 'confirm' | 'unlock' | 'locked' = 'set';
@State confirmPassword: string = '';    // 确认阶段暂存的密码
@State message: string = '设置4位数字密码';
@State attempts: number = 0;            // 错误尝试次数
@State locked: boolean = false;         // 是否锁定
lockTimer: number = -1;                 // 锁定定时器 ID

联合类型状态stage 使用了 TypeScript/ArkTS 的字符串联合类型'set' | 'confirm' | 'unlock' | 'locked'),将变量的取值范围限制在四个预定义字符串中。这种类型约束可以在编译阶段捕获拼写错误。

三、核心代码深度解析

3.1 四阶段状态机

                    ┌──────────┐
                    │ set (设置) │
                    └─────┬────┘
                          │ 输入4位密码
                          ▼
                    ┌──────────┐
                    │confirm(确认)│
                    └─────┬────┘
                      ┌───┴───┐
                      │       │
                  匹配  │       │  不匹配
                      │       │
                      ▼       ▼
                 ┌────────┐ ┌──────────┐
                 │ unlock │ │set (重新) │
                 │ (解锁) │ └──────────┘
                 └──┬─────┘
                ┌───┴───┐
                │       │
            正确  │       │  错误(≥5次)
                │       │
                ▼       ▼
          ┌─────────┐ ┌────────┐
          │解锁成功  │ │ locked │
          └─────────┘ │(锁定5s)│
                      └────────┘

这个状态机是密码锁的核心逻辑。pressNum 函数根据当前 stage 执行不同的分支:

pressNum(n: string) {
  if (this.locked) return;
  if (this.password.length >= 4) return;

  this.password += n;

  if (this.password.length === 4) {
    if (this.stage === 'set') {
      // 阶段1:设置密码
      this.savedPassword = this.password;
      this.confirmPassword = this.password;
      this.stage = 'confirm';
      this.message = '请再次输入确认';
      this.password = '';
    } else if (this.stage === 'confirm') {
      // 阶段2:确认密码
      if (this.password === this.confirmPassword) {
        this.stage = 'unlock';
        this.message = '✅ 密码设置成功!试试解锁';
        this.savedPassword = this.confirmPassword;
      } else {
        this.message = '❌ 两次密码不一致,重新设置';
        this.stage = 'set';
      }
      this.password = '';
    } else if (this.stage === 'unlock') {
      // 阶段3:解锁验证
      if (this.password === this.savedPassword) {
        this.message = '🔓 解锁成功!';
        this.locked = false;
      } else {
        this.attempts++;
        this.message = `❌ 密码错误 (${this.attempts}/5)`;
        if (this.attempts >= 5) {
          // 阶段4:锁定
          this.locked = true;
          this.message = '🔒 尝试次数过多,已锁定';
          this.lockTimer = setTimeout(() => {
            this.locked = false;
            this.attempts = 0;
            this.message = '可以重新尝试';
          }, 5000);
        }
      }
      this.password = '';
    }
  }
}

每个阶段的分析

阶段 set(设置密码)

  • 输入 4 位数字后自动进入 confirm 阶段
  • 密码同时保存在 savedPasswordconfirmPassword

阶段 confirm(确认密码)

  • 再次输入 4 位密码
  • confirmPassword 比对
  • 匹配 → 进入 unlock 阶段,显示成功消息
  • 不匹配 → 回到 set 阶段重新设置,显示错误消息

阶段 unlock(解锁验证)

  • 输入 4 位密码与 savedPassword 比对
  • 正确 → 解锁成功(locked = false
  • 错误 → attempts++,达到 5 次进入 locked 阶段

阶段 locked(锁定)

  • locked = true,输入被阻止(if (this.locked) return
  • setTimeout 5 秒后自动解锁
  • 解锁后恢复可输入状态,attempts 归零

3.2 密码点显示

Row({ space: 12 }) {
  ForEach([0, 1, 2, 3], (i: number) => {
    Circle()
      .width(16).height(16)
      .fill(this.password.length > i ? '#333' : '#ddd')
      .stroke('#bbb').strokeWidth(1)
  })
}.margin({ bottom: 24 })

四个 Circle 圆形代表四位密码的输入状态:

  • 已输入password.length > i):填充深色 #333
  • 未输入password.length <= i):填充浅色 #ddd

这种「圆点填充」的密码视觉反馈是行业标准做法——既让用户知道已输入了几位数字,又不会泄露密码内容。

3.3 Grid 网格数字键盘

Grid() {
  ForEach(['1', '2', '3', '4', '5', '6', '7', '8', '9', '', '0', '⌫'],
    (n: string, idx: number) => {
      GridItem() {
        if (n === '⌫') {
          Text('⌫').fontSize(28).fontColor('#F44336')
            .width('100%').height(60).textAlign(TextAlign.Center)
            .onClick(() => this.delete())
        } else if (n === '') {
          Text('')
        } else {
          Text(n).fontSize(28).fontWeight(FontWeight.Bold)
            .width('100%').height(60).textAlign(TextAlign.Center)
            .onClick(() => this.pressNum(n))
        }
      }
    })
}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('60px 60px 60px 60px')
.width(240).rowsGap(4).columnsGap(4)

Grid 关键属性

属性 说明
columnsTemplate '1fr 1fr 1fr' 三列等宽布局
rowsTemplate '60px 60px 60px 60px' 四行,每行 60px
rowsGap 4 行间距 4px
columnsGap 4 列间距 4px

'1fr' 是 Grid 布局中的弹性系数,表示将可用空间按比例分配。'1fr 1fr 1fr' 就是三等分。

键盘布局:12 个 GridItem 排成 4 行 × 3 列:

1  2  3
4  5  6
7  8  9
   0  ⌫

第 4 行第一个位置(索引 9)是空字符串,用于占位,因为标准数字键盘是 3×4 布局,左下角通常是空位或符号键。

删除键 使用红色(#F44336)与数字键区分,点击触发 delete() 方法:

delete() {
  this.password = this.password.slice(0, -1);
}

使用 slice(0, -1) 删除字符串的最后一个字符,实现退格功能。

3.4 重置功能

reset() {
  clearTimeout(this.lockTimer);
  this.password = '';
  this.stage = 'set';
  this.attempts = 0;
  this.locked = false;
  this.message = '设置4位数字密码';
}

重置将应用恢复到最初始的状态(set 阶段)。注意 clearTimeout(this.lockTimer) 清除锁定定时器——如果用户在被锁定时点击重置,应该立即解除锁定。

四、HarmonyOS 特性分析

4.1 Grid 网格布局 vs 其他布局

布局 适用场景 本例使用
Column/Row 线性排列 顶部导航、主框架
Stack 层叠重叠 未使用(密码锁不需要重叠)
Grid 二维网格 数字键盘
List 滚动列表 未使用

Grid 是最适合键盘布局的容器。相比用 Column + 多个 Row 模拟网格,GridcolumnsTemplate + rowsTemplate 可以精确控制行列分布,代码也更简洁。

4.2 setTimeout 一次性定时器

this.lockTimer = setTimeout(() => {
  this.locked = false;
  this.attempts = 0;
  this.message = '可以重新尝试';
}, 5000);

setInterval(周期性执行)不同,setTimeout 只在指定延迟后执行一次。它非常适合:

  • 延迟操作:密码锁的定时解锁
  • 动画序列:先 A 动画,500ms 后 B 动画
  • 防抖:用户停止输入 300ms 后再搜索

同样需要在 aboutToDisappearresetclearTimeout 清理。

4.3 Circle 组件作为状态指示器

密码锁使用 4 个 Circle 作为密码输入指示器,这与第 31 期模拟时钟使用 Circle 作为表盘和中心点的用法不同。同一个组件在不同场景中扮演不同角色,体现了 ArkTS 组件系统的高度灵活性。

五、UI/UX 设计与最佳实践

5.1 消息提示的动态颜色

Text(this.message).fontSize(16)
  .fontColor(
    this.message.includes('✅') ? '#4CAF50' : 
    this.message.includes('❌') ? '#F44336' : 
    '#666')

消息文字的颜色根据内容自动变化:

  • 包含 ✅ → 绿色(成功)
  • 包含 ❌ → 红色(失败)
  • 其他 → 灰色(中性)

这种「内容驱动颜色」的模式,无需为每种消息单独设置颜色变量,简洁且不易出错。

5.2 锁定状态的视觉反馈

locked = true 时:

  1. 所有按键操作被阻止(if (this.locked) return
  2. 消息显示锁定提示
  3. 用户点击任何数字键都无响应

这种「软禁用」比隐藏键盘更合适——用户看到键盘但不能操作,明白系统处于锁定状态。

5.3 用户体验流程

设置密码 → 确认密码 → 解锁验证 → (错误 5 次)→ 锁定 → 自动解锁

这个流程模拟了真实设备上设置和解锁密码的完整体验,让用户在一个页面内完成所有操作,无需跳转。

六、可扩展思路

  1. 更长的密码:支持 4-8 位可变长度密码。
  2. 生物识别:集成指纹或面部识别(@ohos.userIAM.userAuth)作为解锁方式。
  3. 安全键盘:每次打开键盘时随机排列数字键顺序,防止偷窥。
  4. 应用锁整合:将密码锁作为应用锁,保护其他应用的安全。
  5. 密码找回:设置安全问题或备用邮箱用于密码找回。

七、总结

密码锁应用以 124 行代码,实现了一个包含四阶段状态机的完整密码管理系统。它的技术亮点包括:

  • 联合类型状态机'set' | 'confirm' | 'unlock' | 'locked' 精确控制应用状态
  • Grid 网格键盘columnsTemplate + rowsTemplate 构建标准数字键盘
  • Circle 密码指示器:圆点填充状态反映密码输入进度
  • setTimeout 锁定机制:5 次错误后自动锁定 5 秒
  • 动态消息颜色:根据消息内容自动匹配颜色
  • slice 删除:字符串退格操作的简洁实现

这个应用的状态机架构是整个系列的亮点——四个阶段、每个阶段对输入的不同响应、错误阈值、定时解锁——这些概念可以直接应用于登录页面、支付验证、权限控制等真实场景。

下一期我们将从安全领域转向创意表达,探讨一个充满趣味性的应用——手势绘制(Drawing Canvas),看如何用 onTouch 事件构建涂鸦画板。


本文是 HarmonyOS 应用开发实战系列第 38 期,更多内容请关注专栏。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐