Choosing Pressure Sensitivity for a Cross‑Platform Stylus App: A Decision Guide
Decide how to expose stylus pressure and tilt in a drawing app. Compare native APIs, resolution, palm rejection, and EMR support. Walk through an Android MotionEvent implementation and validation steps.
22 Apr 2026, 22:17 UTC

Decision Context
When building a drawing or note‑taking application that targets multiple tablet platforms, the first engineering decision is whether and how to expose stylus pressure, tilt, and tool modifiers. These inputs drive line width, opacity, shading, and even mode switching in professional art tools. The decision must balance user expectations, device capabilities, and development effort.
Constraints
- Device Heterogeneity – Pressure resolution varies from 1024 to 4096, and some tablets only expose a coarse range.
- API Exposure – Windows Ink, Android MotionEvent, and iOS UIKit differ in the data they provide and how to enable palm rejection.
- EMR vs. Active – Battery‑free EMR styluses reduce maintenance but often have lower resolution and higher latency.
- Performance – High‑frequency pressure updates can tax CPU/GPU if not throttled.
- User Base – Professional artists expect fine granularity; casual users may not notice differences.
Supported Options
| API / Framework | Pressure Resolution | Tilt Support | Palm Rejection | EMR |
|---|---|---|---|---|
| Windows Ink (Win32) | 1024 | Yes (x, y, z) | Yes (hardware + software) | No |
| Android MotionEvent | 1024 (varies by device) | Yes (getAxisValue) | Yes (MotionEvent.ACTION_HOVER_MOVE) | No |
| iOS UIKit (UIEvent) | 1024 | Yes (touches with force & azimuthAngle) | Yes (built‑in) | No |
| Qt (QTabletEvent) | 1024–4096 | Yes (xTilt, yTilt) | Yes (Qt::TabletEvent::TabletEnterProximity) | No |
| Flutter (RawGestureDetector) | Depends on platform API | Yes (via platform channel) | Yes (platform dependent) | No |
Trade‑Offs
- Resolution vs. Consistency – 4096‑level devices offer smoother line weight but are rare. Using the maximum reported by the device and normalizing to 0‑1 keeps UI consistent across hardware.
- Cross‑Platform Complexity – Native APIs give the most accurate data but require separate code paths. A unified abstraction layer can hide platform differences but adds maintenance overhead.
- Palm Rejection – Hardware filtering is fast but not 100% reliable. Software heuristics (e.g., ignoring touches with pressure < 0.05) reduce false positives but may clip light strokes.
- EMR Support – Battery‑free styluses lower user friction but may have higher latency. If your app is latency‑sensitive (e.g., real‑time shading), prefer active styluses.
- Developer Effort – Implementing full tilt and barrel button mapping across platforms can be time‑consuming. Prioritize features that deliver the most value to your target users.
Concrete Implementation: Android MotionEvent
Below is a minimal Android example that reads pressure, tilt, and barrel button state from a stylus. It runs in a custom View subclass and draws a line whose width is proportional to pressure.
public class StylusDrawingView extends View {
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Path path = new Path();
private float lastX, lastY;
private static final float MAX_PRESSURE = 1024f; // typical max
public StylusDrawingView(Context ctx) { super(ctx); init(); }
private void init() {
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
int toolType = e.getToolType(0);
if (toolType != MotionEvent.TOOL_TYPE_STYLUS) return false;
float x = e.getX();
float y = e.getY();
float pressure = e.getPressure(); // 0.0‑1.0
float azimuth = e.getAxisValue(MotionEvent.AXIS_TILT); // degrees
int buttonState = e.getButtonState();
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(x, y);
lastX = x; lastY = y;
break;
case MotionEvent.ACTION_MOVE:
float strokeWidth = 1 + pressure * 10; // map to 1‑11 px
paint.setStrokeWidth(strokeWidth);
path.quadTo(lastX, lastY, (x + lastX) / 2, (y + lastY) / 2);
lastX = x; lastY = y;
invalidate();
break;
case MotionEvent.ACTION_UP:
path.lineTo(x, y);
invalidate();
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, paint);
}
}
Key points:
- Use
MotionEvent.getPressure()which returns a normalized value; multiply by the device’s maximum pressure if you need raw counts. - Tilt is accessed via
getAxisValue(MotionEvent.AXIS_TILT). The value is in degrees; you can map it to a brush angle. - Barrel buttons are reported in
buttonState. For example,MotionEvent.BUTTON_STYLUS_PRIMARYorBUTTON_STYLUS_SECONDARYcan toggle eraser mode. - Enable palm rejection by setting
setFlags(View.SHOW_IME_WITH_HARDWARE_KEYBOARD)and filtering events with low pressure orisFromSource(InputDevice.SOURCE_TOUCHSCREEN)checks.
Validation Steps
- Run the app on a device that supports stylus input (e.g., Samsung Galaxy Tab S series).
- Open the view, press the stylus lightly; the line should be thin (~1 px). Increase pressure; the line should thicken smoothly up to ~11 px.
- Turn on the device’s “Show touches” debug option to visually confirm that the app receives only stylus events, not palm touches.
- Use
adb logcatto print pressure values during drawing; verify that they span the full 0‑1 range. - Test tilt by rotating the stylus; the line should tilt if you modify the paint’s shader or color based on the azimuth value.
Limitations & Practical Checks
- Not all Android devices expose 1024‑level pressure; some report 0‑1 only. Call
InputDevice.getMotionRange(MotionEvent.AXIS_PRESSURE)to query the actual maximum. - Driver updates can change the reported range. After a firmware upgrade, re‑run the validation steps.
- EMR styluses may have higher latency; if your app requires real‑time feedback, consider a battery‑powered stylus.
- Cross‑platform consistency is hard to guarantee. If you ship to iOS and Windows, keep the UI logic identical but map the raw values to a common 0‑1 scale.
Conclusion
Deciding whether to expose stylus pressure, tilt, and modifiers hinges on the target user base and platform consistency. For a professional drawing app, implement full pressure and tilt using the native APIs and normalize the values. Prioritize palm rejection and test on real hardware. The Android example above shows how to capture and use these inputs; similar logic applies to Windows Ink and iOS UIKit with minor API adjustments.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.