Xamarin.iOS 中的多点触控手指跟踪

本文档演示如何跟踪多个手指的触摸事件

有时,多点触控应用程序需要跟踪各个手指在屏幕上同时移动的情况。 一种典型的应用是手指绘画程序。 你希望用户不仅能够使用单根手指进行绘画,而且还能同时使用多根手指进行绘画。 当程序处理多个触摸事件时,它需要区分这些手指。

当手指首次触摸屏幕时,iOS 会为该手指创建一个 UITouch 对象。 当手指在屏幕上移动然后从屏幕上抬起时,该对象保持不变,此时该对象被释放。 为了跟踪手指,程序应避免直接存储此 UITouch 对象。 它可以改用类型为 IntPtrHandle 属性来唯一标识这些 UITouch 对象。

跟踪各个手指的程序几乎始终维护一个用于触摸跟踪的字典。 对于 iOS 程序,字典键是标识特定手指的 Handle 值。 字典值取决于应用程序。 在示例程序中,每个手指笔划(从触摸到释放)都与一个对象相关联,该对象包含渲染该手指绘制的线条所需的所有信息。 该程序为此定义了一个小型 FingerPaintPolyline 类:

class FingerPaintPolyline
{
    public FingerPaintPolyline()
    {
        Path = new CGPath();
    }

    public CGColor Color { set; get; }

    public float StrokeWidth { set; get; }

    public CGPath Path { private set; get; }
}

每条折线都有颜色、笔划宽度和 iOS 图形 CGPath 对象,用于在绘制线条时累积和渲染线条的多个点。

下面所示的所有其余代码都包含在名为 FingerPaintCanvasViewUIView 派生类中。 当一根或多根手指主动绘制对象时,该类维护一个 FingerPaintPolyline 类型的对象字典:

Dictionary<IntPtr, FingerPaintPolyline> inProgressPolylines = new Dictionary<IntPtr, FingerPaintPolyline>();

该字典允许视图根据 UITouch 对象的 Handle 属性快速获取与每个手指关联的 FingerPaintPolyline 信息。

FingerPaintCanvasView 类还为已完成的折线维护一个 List 对象:

List<FingerPaintPolyline> completedPolylines = new List<FingerPaintPolyline>();

List 中的对象顺序与它们的绘制顺序相同。

FingerPaintCanvasView 重写 View 定义的五个方法:

各种 Touches 重写累积构成折线的点。

[Draw] 重写先绘制已完成的折线,然后绘制正在完成的折线:

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);
        }
    }
}

每个 Touches 重写都可能报告多个手指的操作,由存储在方法的 touches 参数中的一个或多个 UITouch 对象指示。 TouchesBegan 重写循环访问这些对象。 对于每个 UITouch 对象,该方法都会创建并初始化一个新的 FingerPaintPolyline 对象,包括存储从 LocationInView 方法获取的手指的初始位置。 使用 UITouch 对象的 Handle 属性作为字典键,将此 FingerPaintPolyline 对象添加到 InProgressPolylines 字典中:

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();
}

该方法最后调用 SetNeedsDisplay 以生成对 Draw 重写的调用并更新屏幕。

当一根或多根手指在屏幕上移动时,View 会多次调用其 TouchesMoved 重写。 此重写以类似方式循环访问存储在 touches 参数中的 UITouch 对象,并将手指的当前位置添加到图形路径中:

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();
}

touches 集合仅包含自上次调用 TouchesBeganTouchesMoved 以来已移动的手指的 UITouch 对象。 如果需要与当前与屏幕接触的所有手指相对应的 UITouch 对象,可以通过该方法的 UIEvent 参数的 AllTouches 属性获取该信息。

TouchesEnded 重写有两个任务。 它必须将最后一个点添加到图形路径,并将 FingerPaintPolyline 对象从 inProgressPolylines 字典传输到 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();
}

只需丢弃字典中的 FingerPaintPolyline 对象即可处理 TouchesCancelled 重写:

public override void TouchesCancelled(NSSet touches, UIEvent evt)
{
    base.TouchesCancelled(touches, evt);

    foreach (UITouch touch in touches.Cast<UITouch>())
    {
        inProgressPolylines.Remove(touch.Handle);
    }
    SetNeedsDisplay();
}

总而言之,此处理允许示例程序跟踪各个手指并将结果绘制在屏幕上:

跟踪各个手指并将结果绘制在屏幕上

现在,你已了解如何跟踪屏幕上的各个手指并区分它们。