Seguimiento de dedos multitáctil en Xamarin.iOS
En este documento se muestra cómo realizar un seguimiento de eventos táctiles con varios dedos
Hay ocasiones en las que una aplicación multitáctil necesita realizar un seguimiento de los dedos de forma individual a medida que se mueven simultáneamente en la pantalla. Una aplicación típica es un programa de pintura de dedos. Querrá que el usuario pueda dibujar con un solo dedo, pero también con varios dedos a la vez. A medida que el programa procese varios eventos táctiles, deberá distinguir entre estos dedos.
Cuando un dedo toque por primera vez la pantalla, iOS creará un objeto UITouch
para ese dedo. Este objeto será el mismo que el del movimiento del dedo en la pantalla y, a continuación, se elevará de la pantalla, en cuyo punto se eliminará el objeto. Para realizar un seguimiento de los dedos, un programa debería evitar almacenar este objeto UITouch
directamente. En su lugar, puede usar la propiedad Handle
de tipo IntPtr
para identificar de forma única estos objetos UITouch
.
Casi siempre, un programa que realiza un seguimiento de los dedos de forma individual mantiene un diccionario para el seguimiento táctil. Para un programa de iOS, la clave de diccionario es el valor Handle
que identifica un dedo determinado. El valor del diccionario dependerá de la aplicación. En el programa de muestra, cada trazo de dedo (desde la entrada táctil hasta la liberación) está asociado a un objeto que contiene toda la información necesaria para representar la línea dibujada con ese dedo. El programa define una clase pequeña FingerPaintPolyline
para este propósito:
class FingerPaintPolyline
{
public FingerPaintPolyline()
{
Path = new CGPath();
}
public CGColor Color { set; get; }
public float StrokeWidth { set; get; }
public CGPath Path { private set; get; }
}
Cada Polilínea tiene un color, un ancho de trazo y un objeto CGPath
gráfico de iOS para acumular y representar varios puntos de la línea a medida que se dibuja.
El resto del código que se muestra a continuación se incluye en un derivado UIView
denominado FingerPaintCanvasView
. Esa clase mantiene un diccionario de objetos de tipo FingerPaintPolyline
durante el tiempo en que uno o varios dedos dibujan activamente:
Dictionary<IntPtr, FingerPaintPolyline> inProgressPolylines = new Dictionary<IntPtr, FingerPaintPolyline>();
Este diccionario permite la vista para obtener rápidamente la información FingerPaintPolyline
asociada a cada dedo en función de la propiedad Handle
del objeto UITouch
.
La clase FingerPaintCanvasView
también mantiene un objeto List
para las Polilíneas que se hayan completado:
List<FingerPaintPolyline> completedPolylines = new List<FingerPaintPolyline>();
Los objetos de este objeto List
están en el mismo orden en que se dibujaron.
FingerPaintCanvasView
invalida cinco métodos definidos por View
:
Las distintas invalidaciones de Touches
acumulan los puntos que componen las Polilíneas.
La invalidación [Draw
] dibuja las Polilíneas completadas y, a continuación, las Polilíneas en curso:
public override void Draw(CGRect rect)
{
base.Draw(rect);
using (CGContext context = UIGraphics.GetCurrentContext())
{
// Stroke settings
context.SetLineCap(CGLineCap.Round);
context.SetLineJoin(CGLineJoin.Round);
// Draw the completed polylines
foreach (FingerPaintPolyline polyline in completedPolylines)
{
context.SetStrokeColor(polyline.Color);
context.SetLineWidth(polyline.StrokeWidth);
context.AddPath(polyline.Path);
context.DrawPath(CGPathDrawingMode.Stroke);
}
// Draw the in-progress polylines
foreach (FingerPaintPolyline polyline in inProgressPolylines.Values)
{
context.SetStrokeColor(polyline.Color);
context.SetLineWidth(polyline.StrokeWidth);
context.AddPath(polyline.Path);
context.DrawPath(CGPathDrawingMode.Stroke);
}
}
}
Cada una de las invalidaciones Touches
informa potencialmente de las acciones de varios dedos, indicadas por uno o varios objetos UITouch
almacenados en el argumento touches
al método. Las invalidaciones de TouchesBegan
recorren en bucle estos objetos. Para cada objeto UITouch
, el método creará e inicializará un nuevo objeto FingerPaintPolyline
, incluyendo el almacenamiento de la ubicación inicial del dedo obtenido del método LocationInView
. Este objeto FingerPaintPolyline
se agregará al diccionario InProgressPolylines
mediante la propiedad Handle
del objeto UITouch
como clave de diccionario:
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt);
foreach (UITouch touch in touches.Cast<UITouch>())
{
// Create a FingerPaintPolyline, set the initial point, and store it
FingerPaintPolyline polyline = new FingerPaintPolyline
{
Color = StrokeColor,
StrokeWidth = StrokeWidth,
};
polyline.Path.MoveToPoint(touch.LocationInView(this));
inProgressPolylines.Add(touch.Handle, polyline);
}
SetNeedsDisplay();
}
El método concluye llamando a SetNeedsDisplay
a para generar una llamada a la invalidación Draw
y para actualizar la pantalla.
A medida que el dedo o los dedos se mueven en la pantalla, View
obtiene varias llamadas a su invalidación TouchesMoved
. Esta invalidación recorrerá de forma similar los objetos UITouch
almacenados en el argumento touches
y agregará la ubicación actual del dedo a la ruta de acceso de grafo:
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
base.TouchesMoved(touches, evt);
foreach (UITouch touch in touches.Cast<UITouch>())
{
// Add point to path
inProgressPolylines[touch.Handle].Path.AddLineToPoint(touch.LocationInView(this));
}
SetNeedsDisplay();
}
La colección touches
contiene solo los objetos UITouch
de los dedos que se movieron desde la última llamada a TouchesBegan
o TouchesMoved
. Si alguna vez necesitase objetos UITouch
correspondientes a todos los dedos en contacto con la pantalla en ese momento, esa información estará disponible a través de la propiedad AllTouches
del argumento UIEvent
para el método.
La invalidación TouchesEnded
tiene dos trabajos. Debe agregar el último punto a la ruta de acceso de grafos y transferir el objeto FingerPaintPolyline
del diccionario inProgressPolylines
a la lista completedPolylines
:
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
base.TouchesEnded(touches, evt);
foreach (UITouch touch in touches.Cast<UITouch>())
{
// Get polyline from dictionary and remove it from dictionary
FingerPaintPolyline polyline = inProgressPolylines[touch.Handle];
inProgressPolylines.Remove(touch.Handle);
// Add final point to path and save with completed polylines
polyline.Path.AddLineToPoint(touch.LocationInView(this));
completedPolylines.Add(polyline);
}
SetNeedsDisplay();
}
La invalidación TouchesCancelled
se controla simplemente abandonando el objeto FingerPaintPolyline
en el diccionario:
public override void TouchesCancelled(NSSet touches, UIEvent evt)
{
base.TouchesCancelled(touches, evt);
foreach (UITouch touch in touches.Cast<UITouch>())
{
inProgressPolylines.Remove(touch.Handle);
}
SetNeedsDisplay();
}
De forma completa, este procesamiento permite al programa de muestra realizar un seguimiento de los dedos de forma individual y dibujar los resultados en la pantalla:
Ya vio cómo se puede realizar un seguimiento de los dedos de forma individual en la pantalla y distinguirlos.