Read the original article:How to Implement a Pattern Password Lock?
How to Implement a Pattern Password Lock?
Requirement Description
How to implement the function of setting a pattern password lock?
Background Knowledge
- PatternLock is a component that allows users to input passwords using a 3x3 grid pattern, often used in password verification scenarios.
- Canvas is a drawing component for custom graphics rendering.
-
Grid is a grid container component where each item corresponds to a
GridItem, supporting various layouts.
Implementation Steps
-
Option 1: Use the built-in
PatternLockcomponent to implement password lock drawing. (Refer to the official documentation: Create a Pattern Password Lock). -
Option 2: Use a
Stackcomponent that contains bothCanvas(for drawing lines) andGrid(for rendering the circle nodes) to build a custom pattern lock.
Main Logic:
- Create
CanvasandGridinsideStackto render the pattern interface. - Add the
handleTouchmethod to theCanvascomponent. This is the core of the code:- While the finger moves across the screen, record each number passed through and store it in an array.
- When
TouchType.Upis triggered, callvalidatePasswordto check whether the input pattern is correct, and show appropriate text or toast messages.
Code Snippet / Configuration
import { promptAction } from '@kit.ArkUI';
@Entry
@Component
struct GestureLock {
@State points: number[][] = [
[50, 50], [150, 50], [250, 50],
[50, 150], [150, 150], [250, 150],
[50, 250], [150, 250], [250, 250]
];
@State path: number[] = [];
@State isDrawing: boolean = false;
@State isError: boolean = false;
@State shakeOffset: number = 0;
@State lineColor: string = '#0A59F7';
@State circleColor: string = '#CCCCCC';
@State currentX: number = 0;
@State currentY: number = 0;
// Default preset password
private presetPassword: number[] = [1, 2, 5, 8, 9];
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
build() {
Stack() {
Column() {
// Hint text
Text(this.getHintText())
.fontSize(20)
.fontColor(Color.Black)
.margin({ top: 20, bottom: 20 });
// Stack layout
Stack() {
// Canvas component
Canvas(this.canvasContext)
.width(300)
.height(300)
.backgroundColor(Color.Transparent)
.hitTestBehavior(HitTestMode.Transparent)
.onTouch((event: TouchEvent) => {
this.handleTouch(event);
})
.onReady(() => {
this.drawPath();
});
// Grid for password lock
Grid() {
ForEach(this.points, (point: number[], index: number) => {
// Render each circle node
GridItem() {
Column() {
Text()
.fontSize(20)
.fontColor(Color.Black)
}
.width(80)
.height(80)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.borderRadius(40)
.backgroundColor(this.getCircleColor(index))
.border({ width: 2, color: this.getCircleBorderColor(index) })
.animation({ duration: 300, curve: Curve.EaseInOut });
}
});
}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr')
.width(300)
.height(300)
.hitTestBehavior(HitTestMode.Transparent)
}
.translate({ x: this.shakeOffset, y: 0 })
.animation({ duration: 50, curve: Curve.EaseInOut })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.height('100%')
.backgroundColor(Color.White)
.zIndex(200)
.position({ x: 0, y: 0 })
}
// Change circle color while drawing
getCircleColor(index: number): string {
return this.path.includes(index) ? '#0A59F7' : '#CCCCCC';
}
// Circle border color
getCircleBorderColor(index: number): Color {
return Color.Black;
}
// Hint text
getHintText(): string {
if (this.isError) {
return 'Password incorrect, please try again';
} else {
return 'Please draw your gesture password';
}
}
// Draw path
drawPath() {
const context = this.canvasContext;
context.clearRect(0, 0, 300, 300);
context.strokeStyle = this.lineColor;
context.lineWidth = 3;
context.lineCap = 'round';
context.lineJoin = 'round';
if (this.path.length > 0) {
context.beginPath();
const start = this.points[this.path[0]];
context.moveTo(start[0], start[1]);
for (let i = 1; i < this.path.length; i++) {
const end = this.points[this.path[i]];
context.lineTo(end[0], end[1]);
}
if (this.isDrawing) {
context.lineTo(this.currentX, this.currentY);
}
context.stroke();
}
}
// Record path while finger moves
handleTouch(event: TouchEvent) {
const touch = event.touches[0];
const x = touch.x;
const y = touch.y;
if (event.type === TouchType.Down) {
this.isDrawing = true;
this.path = [];
this.isError = false;
this.lineColor = '#0A59F7';
this.drawPath();
}
if (this.isDrawing) {
this.currentX = x;
this.currentY = y;
this.points.forEach((point: number[], index: number) => {
const centerX = point[0];
const centerY = point[1];
const distance = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
if (distance <= 40 && !this.path.includes(index)) {
this.path.push(index);
}
});
this.drawPath();
}
if (event.type === TouchType.Up) {
this.isDrawing = false;
this.validatePassword();
}
}
// Validate input pattern
validatePassword() {
if (this.path.length === this.presetPassword.length &&
this.path.every((value: number, index: number) => value + 1 === this.presetPassword[index])) {
promptAction.openToast({ message: 'Password correct' });
setTimeout(() => {
this.reset();
}, 1000);
} else {
this.isError = true;
this.shake();
setTimeout(() => {
this.reset();
}, 1000);
promptAction.openToast({ message: 'Password incorrect, please try again' });
}
}
// Shake effect on error
shake() {
const shakeDistance = 10;
const shakeDuration = 50;
const shakeCount = 4;
let direction = 1;
for (let i = 0; i < shakeCount; i++) {
setTimeout(() => {
this.shakeOffset = direction * shakeDistance;
direction *= -1;
}, i * shakeDuration);
}
setTimeout(() => {
this.shakeOffset = 0;
}, shakeCount * shakeDuration);
}
// Reset path
reset() {
this.path = [];
this.isError = false;
this.lineColor = '#0A59F7';
this.drawPath();
}
}
Test Results
- When the user draws the correct pattern (
1 → 2 → 5 → 8 → 9), a toast appears: "Password correct". - When the user draws the wrong pattern, a toast appears: "Password incorrect, please try again", and the interface shakes.
Limitations or Considerations
- Security considerations: Gesture-based passwords are less secure compared to alphanumeric ones.
- Need to handle edge cases like partial drawings or unintended touches.
Related Documents or Links
https://developer.huawei.com/consumer/en/doc/harmonyos-references/scroll-and-swipe

Top comments (0)