鸿蒙原生 ArkTS 布局实践:PatternLock 九宫格锁屏组件深度解析
鸿蒙原生 ArkTS 布局实践:PatternLock 九宫格锁屏组件深度解析



一、引言
在移动应用开发中,手势锁屏(九宫格图案锁)是一种非常成熟且广泛使用的身份验证方式。用户通过在 3×3 的点阵上绘制自定义路径来完成密码的设置与验证。与传统的数字密码相比,手势图案具有视觉直观、操作便捷、记忆成本低等优势。
HarmonyOS NEXT 在 ArkUI 框架中提供了 PatternLock 组件,原生支持九宫格图案锁功能。本文将以一个完整的示例应用为蓝本,从零开始讲解如何在鸿蒙应用中使用 PatternLock 组件构建一套具有设置密码 → 确认密码 → 验证密码完整流程的图案锁屏系统。
1.1 适用场景
- 应用锁:进入应用前需要验证身份
- 支付确认:敏感操作前的二次确认
- 隐私空间:访问加密文件或私密内容
- 儿童模式:限制儿童进入特定功能区域
- 锁屏替换:自定义系统锁屏界面
二、PatternLock 组件概述
PatternLock 是 HarmonyOS ArkUI 框架提供的专用于手势图案输入的容器组件。它在内部维护了一个 3×3 的圆点矩阵,用户通过手指在矩阵上滑动,系统会自动记录经过的圆点顺序,并在松开手指时通过回调函数返回路径数据。
2.1 组件类继承关系
Component → PatternLock
PatternLock 直接继承自 Component,是一个独立的容器组件,通常不需要与其他布局组件嵌套使用。
2.2 核心属性一览
| 属性 | 类型 | 说明 |
|---|---|---|
sideLength |
number |
组件边长(vp),决定整个九宫格的大小 |
circleRadius |
number |
圆点半径(vp) |
regularColor |
ResourceColor |
未选中圆点的颜色 |
selectedColor |
ResourceColor |
选中圆点的颜色 |
activeColor |
ResourceColor |
手指滑动经过时圆点的高亮颜色 |
pathColor |
ResourceColor |
圆点之间连线的颜色 |
pathStrokeWidth |
number |
连线的线宽(vp) |
autoReset |
boolean |
手势结束后是否自动清除连线状态 |
backgroundColor |
ResourceColor |
组件背景色 |
enabled |
boolean |
是否响应触摸事件 |
2.3 核心事件
| 事件 | 签名 | 说明 |
|---|---|---|
onPatternComplete |
(input: number[]) => void |
用户完成手势时触发,返回圆点索引数组 |
2.4 控制器方法
PatternLockController 提供了编程式控制能力:
| 方法 | 说明 |
|---|---|
reset() |
清除当前绘制的路径连线 |
三、项目结构与环境配置
3.1 工程概览
我们的示例项目采用 HarmonyOS NEXT Stage 模型,目录结构如下:
aa15/
├── AppScope/ # 应用全局配置
│ ├── app.json5
│ └── resources/base/element/string.json
├── entry/ # 应用主模块
│ ├── src/main/ets/
│ │ ├── entryability/EntryAbility.ets # Ability 入口
│ │ ├── entrybackupability/
│ │ └── pages/
│ │ ├── Index.ets # 默认首页
│ │ └── PatternLockDemo.ets # ★ 本文主角
│ ├── src/main/resources/ # 资源文件
│ └── build-profile.json5 # 模块构建配置
├── build-profile.json5 # 应用级构建配置
├── hvigor/hvigor-config.json5 # Hvigor 配置
└── oh-package.json5 # 包管理配置
3.2 关键构建配置
build-profile.json5(应用级):
{
"app": {
"signingConfigs": [],
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "6.1.0(23)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
}
]
}
}
本项目基于 HarmonyOS NEXT API 24 构建,采用 Stage 模型("apiType": "stageMode"),这是 HarmonyOS 从 API 9 开始推荐的应用开发模型。
3.3 页面路由注册
任何页面必须在 main_pages.json 中注册才能通过路由跳转访问:
{
"src": [
"pages/Index",
"pages/PatternLockDemo"
]
}
注意 main_pages.json 所在目录为 entry/src/main/resources/base/profile/。注册后,通过 router.pushUrl 或 windowStage.loadContent 即可加载该页面。
四、核心代码深度解析
4.1 类型定义与枚举
/** 圆点索引类型,0~8 对应 3×3 矩阵的从左到右、从上到下排列 */
type Point = [number, number];
/** 应用状态枚举:贯穿整个密码设置与验证流程 */
enum PatternMode {
SET_PASSWORD = 0, // 首次设置新密码
CONFIRM_PASSWORD = 1, // 再次确认密码
VERIFY_PASSWORD = 2, // 验证密码
DONE = 3 // 已完成
}
设计意图: 使用枚举而非字符串常量来定义模式状态,利用 ArkTS 的类型系统在编译期捕获拼写错误。四个状态构成一个完整的状态机,流转路径为:
SET_PASSWORD → CONFIRM_PASSWORD → DONE
↓ (不一致则退回)
SET_PASSWORD
VERIFY_PASSWORD → DONE
↓ (错误则重试)
VERIFY_PASSWORD
4.2 组件结构与状态管理
@Entry
@Component
struct PatternLockDemo {
// 响应式状态 —— 变更自动触发 UI 重渲染
@State private currentMode: PatternMode = PatternMode.SET_PASSWORD;
@State private firstPattern: number[] = [];
@State private savedPassword: number[] = [];
@State private hintText: string = '请绘制手势密码(至少连接4个点)';
@State private hintColor: Color = Color.Black;
@State private lockSideLength: number = 300;
@State private isLocked: boolean = false;
@State private resetKey: number = 0;
@State private attemptCount: number = 0;
// 非响应式成员 —— PatternLock 控制器
private patternLockController: PatternLockController = new PatternLockController();
ArkTS 响应式机制要点:
@State装饰的变量变更时,框架会自动重新渲染依赖该变量的 UI 部分PatternLockController不属于 UI 状态,故无需@State装饰resetKey递增后通过.key()属性绑定,实现强制重建组件实例的效果
在 ArkTS 中,@State 的行为与 Vue 的 ref/reactive 或 React 的 useState 相似,但采用了更高效的编译时依赖追踪,无需手动标记依赖关系。
4.3 PatternLock 组件装配详解
PatternLock(this.patternLockController)
.key(this.resetKey.toString()) // 强制重建组件的"钥匙"
.sideLength(this.lockSideLength) // 组件边长 300vp
.circleRadius(18) // 每个圆点半径 18vp
.regularColor(Color.Gray) // 未选中 → 灰色
.selectedColor(Color.Blue) // 选中 → 蓝色
.activeColor(Color.Blue) // 滑动经过 → 蓝色
.pathColor(Color.Blue) // 连线 → 蓝色
.pathStrokeWidth(4) // 连线粗细 4vp
.autoReset(false) // 手动控制重置
.backgroundColor(Color.White) // 白色背景
.borderRadius(20) // 圆角 20vp
.enabled(!this.isLocked) // 锁定后禁用触摸
.onPatternComplete((input: number[]) => {
this.handlePatternComplete(input)
})
4.3.1 链式调用的设计模式
ArkTS 组件的属性设置全部采用链式调用模式,每个属性 setter 返回组件实例本身(Builder 模式),这允许我们以流畅的语法在一处完成所有属性和事件的配置。
4.3.2 参数传递与容器注册
PatternLock(this.patternLockController) 将控制器注册到组件实例。此后,通过 this.patternLockController.reset() 即可从代码层面清除绘制路径。这种"控制器模式"是 HarmonyOS ArkUI 的常见设计,类似的还有 Scroller、ListController、GridController 等。
4.3.3 关键回调数据格式
onPatternComplete 的回调参数 input: number[] 是一个整数数组,元素值为 0~8,按以下规则映射:
0 (0,0) 1 (0,1) 2 (0,2)
3 (1,0) 4 (1,1) 5 (1,2)
6 (2,0) 7 (2,1) 8 (2,2)
例如,绘制一个"L"形路径(左上→中上→右上→中下),回调返回 [0, 1, 2, 5]。索引的排列顺序精确反映了手指移动的轨迹,因此两次"相同"的图案必须保证索引数组的长度和顺序完全一致。
4.4 状态机核心逻辑
private handlePatternComplete(points: number[]): void {
// 至少连接 4 个点
if (points.length < 4) {
this.setHint('至少连接 4 个点,请重试', Color.Red)
setTimeout(() => this.clearPattern(), 800)
return
}
if (this.currentMode === PatternMode.SET_PASSWORD) {
// 第①步:记录第一次绘制的图案
this.firstPattern = [...points]
this.setHint('图案已记录,请再次绘制以确认', Color.Black)
this.currentMode = PatternMode.CONFIRM_PASSWORD
this.clearPattern()
} else if (this.currentMode === PatternMode.CONFIRM_PASSWORD) {
// 第②步:比对两次绘制是否一致
if (this.arePatternsEqual(this.firstPattern, points)) {
this.savedPassword = [...points]
this.setHint('✅ 密码设置成功!', Color.Green)
this.currentMode = PatternMode.DONE
this.isLocked = true
} else {
this.setHint('❌ 两次绘制不一致,请重新设置', Color.Red)
this.currentMode = PatternMode.SET_PASSWORD
this.firstPattern = []
this.clearPattern()
}
} else if (this.currentMode === PatternMode.VERIFY_PASSWORD) {
// 验证模式:比对输入与已保存密码
this.attemptCount++
if (this.arePatternsEqual(this.savedPassword, points)) {
this.setHint(`✅ 密码验证通过!(已尝试 ${this.attemptCount} 次)`, Color.Green)
this.isLocked = true
this.currentMode = PatternMode.DONE
} else {
this.setHint(`❌ 密码错误,请重试(已尝试 ${this.attemptCount} 次)`, Color.Red)
this.clearPattern()
}
}
}
4.4.1 状态机图解
┌──────────────┐
│ SET_PASSWORD │◄──── 初始进入
└──────┬───────┘
│ 绘制图案(≥4点)
▼
┌──────────────┐
│CONFIRM_PASSWORD│
└──────┬───────┘
/ \
/ \
一致 / \ 不一致
/ \
▼ ▼
┌────────┐ ┌──────────────┐
│ DONE │ │ SET_PASSWORD │ (退回重设)
└────────┘ └──────────────┘
┌─────────────────┐
│ VERIFY_PASSWORD │◄──── 模式切换进入
└────────┬────────┘
/ \
/ \
正确 / \ 错误
/ \
▼ ▼
┌────────┐ ┌──────────────┐
│ DONE │ │ VERIFY_PASSWORD │ (重试)
└────────┘ └──────────────┘
4.4.2 安全性考量
实际生产环境中,手势密码的安全性需要额外注意:
- 最少连接数:至少 4 个点的限制防止过于简单的图案被轻易猜中
- 尝试次数限制:可扩展实现连续 5 次错误后锁定 30 秒的策略
- 防窥探:路径不应在屏幕上持久显示,
autoReset应设为true或手动快速清除 - 存储安全:密码应使用哈希(如 SHA-256)后存储,而非明文
4.5 路径比对算法
private arePatternsEqual(a: number[], b: number[]): boolean {
if (a.length !== b.length) return false
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false
}
return true
}
这是一个严格逐位比对算法。之所以不使用 JSON.stringify 或 join(',') 后字符串比较,是因为:
- 性能:提前通过
length检查短路,避免不必要的字符串构造 - 类型安全:纯数值比较,避免隐式类型转换导致的潜在问题
- 可读性:逻辑一目了然,维护成本低
在 ArkTS 中,由于 === 的严格相等语义,数组无法直接比较引用。因此必须逐元素比对。如果未来需要支持"旋转不变性"(图案旋转 90° 后依然匹配),改造比对算法时也需要修改此函数。
4.6 界面构建:@Builder 的组件化拆分
@Builder
private buildTitleBar() {
Row() {
Button({ type: ButtonType.Normal, stateEffect: true }) {
Text('← 返回').fontSize(16).fontColor(Color.Blue)
}
.backgroundColor(Color.Transparent)
.onClick(() => { router.back() })
Blank()
Text('图案锁屏')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(Color.Black)
Blank()
Text('').fontSize(16).width(60) // 对称占位
}
.width('100%')
.height(48)
}
4.6.1 Builder 方法设计原则
- 单一职责:每个
@Builder方法只构建一个逻辑区域(标题栏 / 按钮栏 / 提示区域) - 无状态参数:Builder 方法内部通过
this访问组件状态,无需额外传参 - 复用性:Builder 定义在 struct 内部,可在
build()中多次调用
@Builder 是 ArkTS 特有的装饰器,相当于"轻量级子组件",没有独立的状态管理,不产生额外的组件树开销,非常适合拆分 UI 片段。
4.7 重置与清除策略
private clearPattern(): void {
this.patternLockController.reset()
}
ArkUI 提供了两种清除图案路径的方式:
| 方式 | 实现 | 优缺点 |
|---|---|---|
| 控制器 reset | controller.reset() |
原地清除,无动画中断;推荐 |
| key 重建 | this.resetKey++ → .key(key) |
完全销毁旧实例再创建,适合需要彻底重置状态的场景 |
两种方式在示例中都有涉及:clearPattern() 使用控制器方式,而 .key() 属性也保留作为兜底策略。在实际项目中推荐统一使用控制器方式,因为重建组件会带来额外的布局计算开销。
4.8 模式切换逻辑
private switchMode(): void {
if (this.savedPassword.length > 0) {
if (this.currentMode === PatternMode.VERIFY_PASSWORD ||
this.currentMode === PatternMode.DONE) {
this.currentMode = PatternMode.SET_PASSWORD
this.firstPattern = []
this.isLocked = false
this.attemptCount = 0
this.setHint('请绘制新的手势密码', Color.Black)
} else {
this.currentMode = PatternMode.VERIFY_PASSWORD
this.isLocked = false
this.attemptCount = 0
this.setHint('请绘制密码以验证身份', Color.Black)
}
} else {
this.setHint('请先设置一个密码', Color.Gray)
}
this.patternLockController.reset()
}
这里的核心保护逻辑是:仅当存在已保存密码时,才允许切换到验证模式。 如果用户尚未设置密码就试图切换,系统会给出友好提示而非静默失败。这种"防御式编程"思想在整个应用中贯穿始终。
4.9 描述文本的 Adapter 模式
private getModeDescription(): string {
if (this.currentMode === PatternMode.SET_PASSWORD)
return '模式:设置密码 — 绘制新的手势图案'
else if (this.currentMode === PatternMode.CONFIRM_PASSWORD)
return '模式:确认密码 — 再次绘制以确认'
else if (this.currentMode === PatternMode.VERIFY_PASSWORD)
return '模式:验证密码 — 输入密码解锁'
else
return '✅ 已完成'
}
这里实际上应用了**适配器模式(Adapter Pattern)**的思想:将机器友好的枚举值(PatternMode)转换为人类可读的描述文本。在 ArkTS 中,由于不支持计算属性名(Computed Property Names)的枚举反向映射,因此手动编写 getXxx() 方法是更可靠的选择。
五、完整代码
以下为完整的 PatternLockDemo.ets 文件(合并了所有上述分析):
/**
* PatternLock 九宫格锁屏 —— 鸿蒙原生 ArkTS 布局示例
*
* 场景:手势锁屏图案组件的使用场景。
* 核心技术:PatternLock(图案锁),用于设置和验证手势密码。
*/
import { router } from '@kit.ArkUI';
enum PatternMode {
SET_PASSWORD = 0,
CONFIRM_PASSWORD = 1,
VERIFY_PASSWORD = 2,
DONE = 3
}
@Entry
@Component
struct PatternLockDemo {
@State private currentMode: PatternMode = PatternMode.SET_PASSWORD;
@State private firstPattern: number[] = [];
@State private savedPassword: number[] = [];
@State private hintText: string = '请绘制手势密码(至少连接4个点)';
@State private hintColor: Color = Color.Black;
@State private lockSideLength: number = 300;
@State private isLocked: boolean = false;
@State private resetKey: number = 0;
@State private attemptCount: number = 0;
private patternLockController: PatternLockController = new PatternLockController();
build() {
Column() {
this.buildTitleBar()
Column() {
Text(this.hintText)
.fontSize(16).fontColor(this.hintColor)
.textAlign(TextAlign.Center)
.margin({ top: 20, bottom: 30 })
.lineHeight(22).width('100%')
PatternLock(this.patternLockController)
.key(this.resetKey.toString())
.sideLength(this.lockSideLength)
.circleRadius(18)
.regularColor(Color.Gray)
.selectedColor(Color.Blue)
.activeColor(Color.Blue)
.pathColor(Color.Blue)
.pathStrokeWidth(4)
.autoReset(false)
.backgroundColor(Color.White)
.borderRadius(20)
.margin({ top: 10 })
.enabled(!this.isLocked)
.onPatternComplete((input: number[]) => {
this.handlePatternComplete(input)
})
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
this.buildBottomButtons()
}
.width('100%')
.height('100%')
.padding({ left: 24, right: 24, top: 48 })
.backgroundColor(Color.White)
}
@Builder
private buildTitleBar() {
Row() {
Button({ type: ButtonType.Normal, stateEffect: true }) {
Text('← 返回').fontSize(16).fontColor(Color.Blue)
}
.backgroundColor(Color.Transparent)
.onClick(() => { router.back() })
Blank()
Text('图案锁屏').fontSize(20).fontWeight(FontWeight.Bold).fontColor(Color.Black)
Blank()
Text('').fontSize(16).width(60)
}
.width('100%').height(48)
}
@Builder
private buildBottomButtons() {
Column({ space: 12 }) {
Button({ type: ButtonType.Capsule, stateEffect: true }) {
Text(this.getModeSwitchButtonText()).fontSize(16).fontColor(Color.White)
}
.width('100%').height(44).backgroundColor(Color.Blue)
.enabled(!this.isLocked)
.onClick(() => { this.switchMode() })
Row() {
Button({ type: ButtonType.Capsule, stateEffect: true }) {
Text('清除绘制').fontSize(14).fontColor(Color.Gray)
}
.height(40).backgroundColor(Color.White)
.border({ width: 1, color: Color.Gray })
.onClick(() => { this.clearPattern() })
Blank()
Button({ type: ButtonType.Capsule, stateEffect: true }) {
Text('重置状态').fontSize(14).fontColor(Color.White)
}
.height(40).backgroundColor(Color.Red)
.onClick(() => { this.resetAll() })
}
.width('100%')
Text(this.getModeDescription())
.fontSize(12).fontColor(Color.Gray)
.textAlign(TextAlign.Center).width('100%')
.margin({ top: 8, bottom: 24 })
}
.width('100%')
}
private handlePatternComplete(points: number[]): void {
if (points.length < 4) {
this.setHint('至少连接 4 个点,请重试', Color.Red)
setTimeout(() => this.clearPattern(), 800)
return
}
if (this.currentMode === PatternMode.SET_PASSWORD) {
this.firstPattern = [...points]
this.setHint('图案已记录,请再次绘制以确认', Color.Black)
this.currentMode = PatternMode.CONFIRM_PASSWORD
this.clearPattern()
} else if (this.currentMode === PatternMode.CONFIRM_PASSWORD) {
if (this.arePatternsEqual(this.firstPattern, points)) {
this.savedPassword = [...points]
this.setHint('✅ 密码设置成功!', Color.Green)
this.currentMode = PatternMode.DONE
this.isLocked = true
} else {
this.setHint('❌ 两次绘制不一致,请重新设置', Color.Red)
this.currentMode = PatternMode.SET_PASSWORD
this.firstPattern = []
this.clearPattern()
}
} else if (this.currentMode === PatternMode.VERIFY_PASSWORD) {
this.attemptCount++
if (this.arePatternsEqual(this.savedPassword, points)) {
this.setHint(`✅ 密码验证通过!(已尝试 ${this.attemptCount} 次)`, Color.Green)
this.isLocked = true
this.currentMode = PatternMode.DONE
} else {
this.setHint(`❌ 密码错误,请重试(已尝试 ${this.attemptCount} 次)`, Color.Red)
this.clearPattern()
}
}
}
private arePatternsEqual(a: number[], b: number[]): boolean {
if (a.length !== b.length) return false
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false
}
return true
}
private clearPattern(): void {
this.patternLockController.reset()
}
private resetAll(): void {
this.currentMode = PatternMode.SET_PASSWORD
this.firstPattern = []
this.savedPassword = []
this.attemptCount = 0
this.isLocked = false
this.patternLockController.reset()
this.setHint('请绘制手势密码(至少连接4个点)', Color.Black)
}
private switchMode(): void {
if (this.savedPassword.length > 0) {
if (this.currentMode === PatternMode.VERIFY_PASSWORD || this.currentMode === PatternMode.DONE) {
this.currentMode = PatternMode.SET_PASSWORD
this.firstPattern = []
this.isLocked = false
this.attemptCount = 0
this.setHint('请绘制新的手势密码', Color.Black)
} else {
this.currentMode = PatternMode.VERIFY_PASSWORD
this.isLocked = false
this.attemptCount = 0
this.setHint('请绘制密码以验证身份', Color.Black)
}
} else {
this.setHint('请先设置一个密码', Color.Gray)
}
this.patternLockController.reset()
}
private setHint(text: string, color: Color): void {
this.hintText = text
this.hintColor = color
}
private getModeSwitchButtonText(): string {
if (this.savedPassword.length === 0) return '请先设置密码'
else if (this.currentMode === PatternMode.VERIFY_PASSWORD || this.currentMode === PatternMode.DONE)
return '切换为「重新设置密码」'
else return '切换为「验证密码」'
}
private getModeDescription(): string {
if (this.currentMode === PatternMode.SET_PASSWORD)
return '模式:设置密码 — 绘制新的手势图案'
else if (this.currentMode === PatternMode.CONFIRM_PASSWORD)
return '模式:确认密码 — 再次绘制以确认'
else if (this.currentMode === PatternMode.VERIFY_PASSWORD)
return '模式:验证密码 — 输入密码解锁'
else return '✅ 已完成'
}
}
六、PatternLock 使用最佳实践
6.1 视觉设计建议
- 圆点大小适配:
circleRadius建议在 15~22vp 之间。过小导致触摸精度下降,过大则相邻圆点间距不足,误触率升高。 - 颜色搭配:选中色与未选中色应有明显对比度。推荐
selectedColor使用品牌主色,regularColor使用灰色系。 - 边距与对齐:
PatternLock默认居中对齐。建议外层容器设置明确的尺寸约束,避免组件拉伸变形。
6.2 交互体验优化
| 优化点 | 方案 |
|---|---|
| 触控反馈 | 结合 onPatternComplete 回调添加振动反馈(vibrator API) |
| 错误提示 | 错误时路径线条切换为红色并闪烁,辅助文字提示 |
| 最小长度提示 | 在圆点上方显示数字标签,或动态显示已连接点数 |
| 无障碍支持 | 为每个圆点添加 accessibilityText,描述当前绘制进度 |
6.3 常见编译错误与解决方案
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
Argument of type ... not assignable to PatternLockController |
错误地将 { sideLength } 对象传入构造函数 |
改为 PatternLock(new PatternLockController()),属性通过链式调用设置 |
onPatternComplete 类型不匹配 |
回调参数类型写为 Point[] 而非 number[] |
更正签名:(input: number[]) => void |
'key' can only be used for testing directories |
在正式代码中使用 .key() 属性 |
该警告可忽略;或用 controller.reset() 替代 |
| Unknown resource name | 引用了当前 API 版本不存在的系统资源 | 使用 Color 枚举常量或自定义 $r('app.color.xxx') 资源 |
6.4 性能优化
- 避免频繁重建:
.key()递增会销毁整个组件树再重建,频繁调用可能导致卡顿。优先使用controller.reset()。 - 最小化 State 粒度:不要将 PatternLock 的全部配置属性都标记为
@State,静态不变的属性直接写在build()中即可。 - 延迟加载:如果 PatternLock 页面不是首页,可使用
LazyForEach或条件渲染控制其加载时机。
七、扩展:从示例到生产
7.1 持久化存储
生产环境中,密码需要持久化存储。HarmonyOS 提供了多种存储方案:
import { preferences } from '@kit.ArkData';
// 保存密码(SHA-256 哈希后)
const hashed = await hashPassword(pattern.join('-'))
await preferences.put(globalThis.context, 'lock_pattern', hashed)
// 验证时比对哈希
const stored = await preferences.get(globalThis.context, 'lock_pattern', '')
const same = stored === await hashPassword(input.join('-'))
7.2 生物认证降级
PatternLock 可与生物认证(指纹/人脸)配合使用:
import { userAuth } from '@kit.UserAuthenticationKit';
async function authenticateWithBio(): Promise<boolean> {
const auth = userAuth.getUserAuthInstance({
challenge: new Uint8Array([...]),
authType: [userAuth.UserAuthType.FINGERPRINT, userAuth.UserAuthType.FACE]
})
const result = await auth.auth()
return result.result === userAuth.AuthResultCode.SUCCESS
}
当生物认证失败时,降级到 PatternLock 验证。
7.3 多设备协同
PatternLock 可与 HarmonyOS 的分布式能力结合,实现跨设备解锁:
import { distributedDeviceManager } from '@kit.DistributedServiceKit';
// 在可信设备上验证图案
const devices = distributedDeviceManager.getTrustedDeviceListSync()
// 通过 RPC 将图案发送到目标设备验证
八、小结
本文从鸿蒙原生 PatternLock 组件入手,完整地演示了如何构建一个具有设置密码、确认密码、验证密码完整流程的九宫格锁屏应用。核心要点包括:
- 组件层级:
PatternLock+PatternLockController的双层架构 - 数据流:
onPatternComplete回调返回的number[]索引数组作为数据载体 - 状态机:四阶段枚举驱动 UI 状态流转,保证用户体验的严谨性
- 组件化:
@Builder拆分 UI 片段,保持主build()方法简洁 - 响应式:
@State装饰器驱动的自动 UI 刷新
PatternLock 虽然只是一个单一组件,但它背后涉及的状态管理、事件回调、组件组合、UI 响应式更新等概念,代表了鸿蒙 ArkTS 开发的核心范式。掌握了 PatternLock 的使用方式,也就掌握了 @Entry/@Component/@State/@Builder 等装饰器在真实业务场景中的配合方式——这些知识与经验可以复用到几乎所有 HarmonyOS NEXT 应用开发中。
九、参考资源
更多推荐


所有评论(0)