HarmonyOS Pen Kit: Integrating Stylus Squeeze, Double-Tap & Color Picker
This tutorial demonstrates complete integration of HarmonyOS Pen Kit's stylusInteraction module for squeeze and double-tap events, and imageFeaturePicker for color picking, covering callback reference management, lifecycle-aware registration with try-catch protection, event object fields, and UI feedback patterns including status visualization, counters, and toast notifications.
Pen Kit Basics
HarmonyOS Pen Kit ( @kit.Penkit) comprises two independent core modules:
stylusInteraction : Handles stylus-specific interaction events such as squeeze and double-tap.
imageFeaturePicker : Provides screen color picking at specified coordinates.
Both modules can be used separately or combined to build complete stylus-assisted tools.
Supported Event Types
The stylusInteraction module currently exposes two events, both requiring the pen to be in a hover (hover) state — i.e., the tip is near but not pressing the screen:
squeeze : Triggered when the user lightly pinches the pen barrel button while hovering. Callback receives a SqueezeEvent object.
doubleTap : Triggered when the user double-taps the pen barrel while hovering. Callback receives a DoubleTapEvent object.
Note: Ordinary tap state does not fire these events; hover is mandatory.
Callback Reference Management
Why Save Callback References
Registering listeners with stylusInteraction.on() and removing them with stylusInteraction.off() requires passing the exact same function reference . If anonymous arrow functions are created inline during registration and again during removal, the references differ and cancellation silently fails, causing memory leaks or duplicate triggers.
Correct pattern: declare callbacks as component properties and initialize them in aboutToAppear:
private squeezeCallback: (event: stylusInteraction.SqueezeEvent) => void = () => {};
private doubleTapCallback: (event: stylusInteraction.DoubleTapEvent) => void = () => {};
// In aboutToAppear:
this.squeezeCallback = (event) => { /* handle squeeze */ };
this.doubleTapCallback = (event) => { /* handle doubleTap */ };Tip: Writing () => {} directly inside on() and another () => {} inside off() creates two distinct references; removal will fail silently.
Initialization Timing
The article recommends a layered lifecycle approach: aboutToAppear: Initialize callback references (assign to member properties). onPageShow: Register event listeners (start listening when page becomes visible). onPageHide: Cancel event listeners (stop listening when page hidden). aboutToDisappear: Fallback cancellation to prevent leaks on destruction.
This design binds listening strictly to page visibility, so background pages consume no resources, and returning to the page seamlessly restores monitoring.
Event Registration and Cancellation
Registering Listeners
Each registration must be wrapped in an independent try-catch block because unsupported devices or missing permissions throw exceptions that would crash the app if uncaught. Separate try-catch ensures one failure does not block the other.
try {
stylusInteraction.on('squeeze', this.squeezeCallback);
} catch (e) { /* handle */ }
try {
stylusInteraction.on('doubleTap', this.doubleTapCallback);
} catch (e) { /* handle */ }On success, set an isListening flag to true for state tracking.
Cancelling Listeners
Before calling off(), check the isListening flag to avoid meaningless cancellations. After removal, reset the flag to false.
if (this.isListening) {
stylusInteraction.off('squeeze', this.squeezeCallback);
stylusInteraction.off('doubleTap', this.doubleTapCallback);
this.isListening = false;
}Event Object Fields
Both SqueezeEvent and DoubleTapEvent share a core field: timestamp (number): Event occurrence timestamp in milliseconds. Useful for calculating intervals between operations or displaying the last action time in the UI.
Color Picker Integration
Working Principle
imageFeaturePicker.pickForResult(x, y)accepts screen coordinates, shows a system color-picker overlay, and returns a Promise resolving to a PickedColorInfo object. The key is obtaining correct screen coordinates: a button's onClick callback provides a ClickEvent with displayX and displayY representing absolute screen coordinates, which match pickForResult 's parameter requirements.
.onClick((event) => {
imageFeaturePicker.pickForResult(event.displayX, event.displayY)
.then((colorInfo: imageFeaturePicker.PickedColorInfo) => {
// colorInfo.color holds the picked color (e.g., #FF5733)
})
.catch((err: BusinessError) => {
// Handle user cancellation or device unsupported
});
})Return Value
PickedColorInfocontains: color: Picked color value, typically a hex string such as #FF5733.
Tip: When the user taps Cancel, the Promise rejects with a specific error code in err.code . Distinguish "user cancelled" from genuine errors in the catch block to avoid misreporting cancellation as failure.
UI State Feedback Design
Listening Status Visualization
A status line at the page bottom shows real-time monitoring state with color coding:
Green ( #00C853): Listening active; stylus events can be captured.
Red ( #FF5252): Listening stopped; typical when page moves to background.
This visual cue is invaluable during debugging to confirm listener health.
Event Counters
Two independent counters track cumulative triggers: squeezeCount: Total squeeze events. doubleTapCount: Total double-tap events.
Each event updates lastEventInfo with the latest event type and timestamp. A "Reset Counters" button clears both counts for clean multi-round testing.
Toast Instant Feedback
On every captured stylus event, a short toast appears via getUIContext().getPromptAction().showToast(). Key details:
Uses getUIContext() (recommended in V2 components) instead of global promptAction.
Duration set to 1500 ms to avoid blocking subsequent interactions.
Message includes current count so users instantly see which occurrence this is.
Complete Lifecycle Flow
The page lifecycle management follows this sequence:
Page creation ( aboutToAppear ) : Initialize callback references; no listeners registered yet.
Page show ( onPageShow ) : Register squeeze and doubleTap listeners; set isListening = true.
User operates stylus : Callbacks fire, counters increment, UI updates, toast shows.
Page hide ( onPageHide ) : Cancel both listeners; set isListening = false; stop resource consumption.
Re-enter page ( onPageShow again) : Re-register listeners seamlessly.
Page destroy ( aboutToDisappear ) : Fallback cancellation to prevent leaks.
Core advantages:
Listeners strictly bound to page visibility — zero background resource usage.
Fixed callback references guarantee reliable register/unregister pairing.
Independent try-catch per registration — single-point failure does not affect the whole.
Summary
HarmonyOS Pen Kit usage boils down to three pillars:
Save callback references in aboutToAppear.
Pair registration and cancellation within the page lifecycle.
Guard every registration with try-catch.
Mastering these enables safe, stable integration of stylus squeeze, double-tap, and advanced features like the color picker, delivering a smoother stylus experience.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
51CTO HarmonyOS Developer Community
The HarmonyOS Developer Community is a learning-oriented community for developers to learn, communicate, ask questions, and share.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
