TouchHelper API
Complete reference for TouchHelper lifecycle, methods, and callbacks,
with working Kotlin examples.
Lifecycle state machine
TouchHelper must progress through states in order. Skipping or reversing
steps produces silent failures (no pen events, empty regions, or crashes).
┌──────────┐
│ Created │ TouchHelper.create(view, callback)
└─────┬────┘
│
┌─────▼──────┐
│ Configured │ setLimitRect(limitRects, excludeRects)
└─────┬──────┘
│
┌─────▼──────┐
│ Opened │ openRawDrawing()
└─────┬──────┘
│
┌─────▼──────┐
│ Active │ setRawDrawingEnabled(true) ← pen events flow
└─────┬──────┘
│ pause/resume
┌─────▼──────┐
│ Paused │ setRawDrawingEnabled(false) ← pen events suspended
└─────┬──────┘
│
┌─────▼──────┐
│ Closed │ closeRawDrawing() ← native thread stopped
└────────────┘
Creating the helper
val helper: TouchHelper = TouchHelper.create(view, callback)
view must be attached to a window and have non-zero dimensions. Create
the helper inside a ViewTreeObserver.OnGlobalLayoutListener to guarantee
these conditions.
Setting the active region
fun setLimitRect(view: View, helper: TouchHelper) {
val screenRect = Rect()
val excludeRects = mutableListOf<Rect>()
if (!view.getGlobalVisibleRect(screenRect) || screenRect.isEmpty) return
// Exclude system gesture strips so swipe-up/down still work.
// Use insets from WindowInsetsCompat; fall back to safe minimums in
// fullscreen mode where insets report zero.
val density = view.resources.displayMetrics.density
val w = view.rootView.width
val h = view.rootView.height
val insets = ViewCompat.getRootWindowInsets(view)
?.getInsets(WindowInsetsCompat.Type.systemGestures())
val bottom = maxOf(insets?.bottom ?: 0, (40 * density).toInt())
val top = maxOf(insets?.top ?: 0, (24 * density).toInt())
excludeRects.add(Rect(0, h - bottom, w, h)) // home gesture strip
excludeRects.add(Rect(0, 0, w, top)) // notification pull-down
helper.setLimitRect(mutableListOf(screenRect), excludeRects)
}
Important
setLimitRect takes screen-absolute coordinates. The SDK maps these
to the digitizer’s native coordinate space internally via
EpdController.mapToRawTouchPoint. Passing view-local coordinates
(Rect(0, 0, view.width, view.height)) produces an incorrect
(possibly empty) hardware event filter.
Opening and enabling
After setLimitRect, open and enable in sequence:
helper.setLimitRect(limitRects, excludeRects)
helper.openRawDrawing()
helper.setRawDrawingRenderEnabled(true) // SDK renders A2 live
helper.setPenUpRefreshEnabled(false) // suppress pen-up blink
helper.setRawDrawingEnabled(true) // start accepting pen events
Handling view size changes
If the view is resized (e.g. keyboard shown, split-screen), update the region:
override fun onSizeChanged(w: Int, h: Int, oldW: Int, oldH: Int) {
super.onSizeChanged(w, h, oldW, oldH)
if (w <= 0 || h <= 0 || touchHelper == null) return
touchHelper?.apply {
setRawDrawingEnabled(false)
closeRawDrawing()
setLimitRect(view, this)
openRawDrawing()
setRawDrawingEnabled(true)
}
}
The close → setLimitRect → open sequence is required by the SDK; updating
setLimitRect without closing first has no effect.
Activity lifecycle integration
// Activity or Fragment:
override fun onResume() {
super.onResume()
inkView.onResume()
}
override fun onPause() {
inkView.onPause()
super.onPause()
}
// InkView:
fun onResume() = touchHelper?.setRawDrawingEnabled(true)
fun onPause() = touchHelper?.setRawDrawingEnabled(false)
Cleanup
override fun onDetachedFromWindow() {
touchHelper?.closeRawDrawing()
touchHelper = null
super.onDetachedFromWindow()
}
Implementing RawInputCallback
All eight abstract methods must be overridden (the SDK uses an abstract class, not an interface):
val callback = object : RawInputCallback() {
// Pen touches surface
override fun onBeginRawDrawing(b: Boolean, p: TouchPoint) {}
// Called for each sampled point during the stroke
override fun onRawDrawingTouchPointMoveReceived(p: TouchPoint) {
// Track points for your own preview rendering if needed.
// The SDK's A2 waveform already renders the stroke visually;
// you only need to record points here if you draw on your own Canvas.
}
// Pen lifts
override fun onEndRawDrawing(b: Boolean, p: TouchPoint) {}
// Full stroke delivered on pen-up
override fun onRawDrawingTouchPointListReceived(list: TouchPointList) {
val points = list.points.map { Point(it.x, it.y, it.pressure) }
// Commit to your model, then call postInvalidate() so the Canvas
// is repainted before the SDK's A2 buffer clears.
commitStroke(points)
postInvalidate()
}
// Eraser-end equivalents (implement as no-ops if erasing not supported)
override fun onBeginRawErasing(b: Boolean, p: TouchPoint) {}
override fun onEndRawErasing(b: Boolean, p: TouchPoint) {}
override fun onRawErasingTouchPointMoveReceived(p: TouchPoint) {}
override fun onRawErasingTouchPointListReceived(list: TouchPointList) {}
}
TouchPointList
TouchPointList.points returns a List<TouchPoint>. Each TouchPoint
has:
Field |
Type |
Notes |
|---|---|---|
|
|
Screen-absolute X, matches |
|
|
Screen-absolute Y |
|
|
0.0 – 1.0 (normalised from digitizer range) |
|
|
Contact area (pen tip size) |
|
|
Milliseconds since boot |
Handling the Any? type wrapper
If your module compiles without the Onyx SDK (e.g. a shared module used by
both Boox and non-Boox product variants), store TouchHelper as Any?
and cast at the call site. This avoids NoClassDefFoundError on devices
where the SDK is absent:
// Stored as Any? so InkView.class has no direct TouchHelper type reference
private var touchHelper: Any? = null
// At call site, inside a try/catch:
@Suppress("UNCHECKED_CAST")
(touchHelper as? TouchHelper)?.setRawDrawingEnabled(true)
Encapsulate all SDK calls in a separate object (e.g. OnyxPen) that
is only referenced from Boox-specific code paths:
object OnyxPen {
fun setRawDrawingEnabled(helper: Any?, enabled: Boolean) {
try {
@Suppress("UNCHECKED_CAST")
(helper as TouchHelper?)?.setRawDrawingEnabled(enabled)
} catch (_: Throwable) {}
}
// … other thin wrappers
}
Pen shape classes
onyxsdk-pen ships several Neo*Pen classes that apply different stroke
rendering styles using the SDK’s own A2 renderer:
NeoBallpointPenNeoBrushPen(may be unstable on some devices)NeoCharcoalPen/NeoCharcoalPenV2(may crash, disabled by default in some third-party apps)NeoFountainPenNeoMarkerPen(may be unstable)NeoPencilPenNeoSquarePen
These are used with MultiViewTouchHelper or passed to TouchHelper as
brush configuration. If you render strokes on your own Canvas (setting
setRawDrawingRenderEnabled(false)), these classes are not relevant.
MultiViewTouchHelper
com.onyx.android.sdk.pen.multiview.MultiViewTouchHelper manages multiple
view regions simultaneously and handles view lifecycle events automatically.
It is used by Onyx’s own note application when multiple canvas views are on
screen. For single-view use cases, plain TouchHelper is sufficient.