Touch Tracking
April 4, 2026 · View on GitHub
Touch Tracking is a low-level, cross-platform multi-touch event system. It fires granular events — Pressed, Moved, Released, Entered, Exited, Cancelled — with precise coordinates for every finger/pointer on screen.
It is the foundation that SkiaScene builds on, but you can use it directly for any custom gesture or drawing use case.
Platform Support
| Platform | Implementation |
|---|---|
| Android | MotionEvent processing with touch slop and boundary detection |
| iOS | Custom UIGestureRecognizer subclass (TouchRecognizer) |
| macOS Catalyst | Same as iOS (shared .apple.cs implementation) |
| Windows | WinUI Pointer events (PointerPressed, PointerMoved, PointerReleased, PointerCanceled, PointerCaptureLost) |
XAML Namespace
xmlns:tracking="clr-namespace:Maui.FreakyEffects.TouchTracking;assembly=Maui.FreakyEffects"
Effect Registration
Make sure InitFreakyEffects() is called in MauiProgram.cs. See Getting Started.
TouchEffect
TouchEffect is a RoutingEffect that raises a TouchAction event for every pointer interaction on the attached view.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
Capture | bool | true | When true, touch events are captured to this view even when the pointer moves outside it |
Event
public event TouchActionEventHandler TouchAction;
TouchActionEventArgs
| Property | Type | Description |
|---|---|---|
Id | long | Unique identifier for this pointer. Stable across Pressed → Moved → Released for the same finger |
Type | TouchActionType | The action that occurred |
Location | TouchTrackingPoint | The pointer's position relative to the attached view in device-independent pixels |
IsInContact | bool | true for Pressed and Moved; false for Released, Exited, and Cancelled |
TouchActionType enum
| Value | When fired |
|---|---|
Pressed | Pointer touches/clicks down |
Moved | Pointer moves while in contact |
Released | Pointer lifts |
Entered | Pointer crosses into this view's bounds (when Capture is false) |
Exited | Pointer crosses out of this view's bounds (when Capture is false) |
Cancelled | Touch cancelled (e.g. incoming call, pointer capture lost) |
Usage
XAML
<ContentView x:Name="DrawSurface">
<ContentView.Effects>
<tracking:TouchEffect TouchAction="OnTouchAction" Capture="True" />
</ContentView.Effects>
</ContentView>
Code-Behind
void OnTouchAction(object sender, TouchActionEventArgs args)
{
switch (args.Type)
{
case TouchActionType.Pressed:
StartPath(args.Id, args.Location);
break;
case TouchActionType.Moved:
ExtendPath(args.Id, args.Location);
break;
case TouchActionType.Released:
case TouchActionType.Cancelled:
EndPath(args.Id);
break;
}
}
Attaching in Code
var effect = new TouchEffect { Capture = true };
effect.TouchAction += OnTouchAction;
myView.Effects.Add(effect);
Multi-Touch Example
Each active finger has its own Id. Track them in a dictionary keyed by Id to support simultaneous touches:
readonly Dictionary<long, SKPath> _paths = new();
void OnTouchAction(object sender, TouchActionEventArgs args)
{
var point = new SKPoint((float)args.Location.X, (float)args.Location.Y);
switch (args.Type)
{
case TouchActionType.Pressed:
var path = new SKPath();
path.MoveTo(point);
_paths[args.Id] = path;
break;
case TouchActionType.Moved:
if (_paths.TryGetValue(args.Id, out var p))
p.LineTo(point);
canvasView.InvalidateSurface();
break;
case TouchActionType.Released:
case TouchActionType.Cancelled:
_paths.Remove(args.Id);
break;
}
}
Boundary Crossing
When Capture is false, the system tracks which view the pointer is over and fires Entered/Exited events as the pointer crosses view boundaries. This is useful for implementing hover effects or drag-and-drop targets.
<HorizontalStackLayout>
<BoxView x:Name="ZoneA" Color="LightBlue" WidthRequest="150" HeightRequest="150">
<BoxView.Effects>
<tracking:TouchEffect TouchAction="OnZoneTouch" Capture="False" />
</BoxView.Effects>
</BoxView>
<BoxView x:Name="ZoneB" Color="LightCoral" WidthRequest="150" HeightRequest="150">
<BoxView.Effects>
<tracking:TouchEffect TouchAction="OnZoneTouch" Capture="False" />
</BoxView.Effects>
</BoxView>
</HorizontalStackLayout>
void OnZoneTouch(object sender, TouchActionEventArgs args)
{
var zone = (BoxView)sender;
zone.Color = args.Type switch
{
TouchActionType.Entered => Colors.Yellow,
TouchActionType.Exited => zone == ZoneA ? Colors.LightBlue : Colors.LightCoral,
_ => zone.Color
};
}
Supporting Types
TouchTrackingPoint
A lightweight struct representing a 2D coordinate:
public struct TouchTrackingPoint
{
public float X { get; }
public float Y { get; }
}
TouchTrackingRect
A lightweight struct for rectangular regions:
public struct TouchTrackingRect
{
public float Left { get; }
public float Top { get; }
public float Width { get; }
public float Height { get; }
public bool Contains(TouchTrackingPoint point);
}