多点触控手指跟踪
本主题演示如何跟踪来自多个手指的触摸事件
有时,多点触控应用程序需要跟踪各个手指在屏幕上同时移动的情况。 一种典型的应用是手指绘画程序。 你希望用户不仅能够使用单根手指进行绘画,而且还能同时使用多根手指进行绘画。 当程序处理多个触摸事件时,它需要区分哪些事件对应于每个手指。 Android 提供用于此目的的 ID 代码,但获取和处理该代码可能有点棘手。
对于与特定手指关联的所有事件,ID 代码保持不变。 当手指首次触摸屏幕时,会分配 ID 代码,并在手指从屏幕中抬起后变为无效。 这些 ID 代码通常是非常小的整数,Android 会将其重新用于以后的触摸事件。
跟踪各个手指的程序几乎始终维护一个用于触摸跟踪的字典。 字典键是标识特定手指的 ID 代码。 字典值取决于应用程序。 在 FingerPaint 示例中,每个手指笔划(从触摸到释放)都与一个对象关联,该对象包含呈现该手指绘制的线条所需的所有信息。 该程序为此定义了一个小型 FingerPaintPolyline
类:
class FingerPaintPolyline
{
public FingerPaintPolyline()
{
Path = new Path();
}
public Color Color { set; get; }
public float StrokeWidth { set; get; }
public Path Path { private set; get; }
}
每个折线都有一种颜色、笔划宽度和一个 Android 图形 Path
对象,用于在绘制线条时累积和呈现线条的多个点。
下面所示代码的其余部分包含在名为 FingerPaintCanvasView
的 View
派生体中。 当一根或多根手指主动绘制对象时,该类维护一个 FingerPaintPolyline
类型的对象字典:
Dictionary<int, FingerPaintPolyline> inProgressPolylines = new Dictionary<int, FingerPaintPolyline>();
此字典允许视图快速获取与特定手指关联的 FingerPaintPolyline
信息。
FingerPaintCanvasView
类还为已完成的折线维护一个 List
对象:
List<FingerPaintPolyline> completedPolylines = new List<FingerPaintPolyline>();
此 List
中的对象顺序与它们的绘制顺序相同。
FingerPaintCanvasView
重写由 View
定义的两种方法:OnDraw
和 OnTouchEvent
。
在其 OnDraw
重写中,视图绘制已完成的折线,然后绘制正在进行的折线。
OnTouchEvent
方法的重写首先从 ActionIndex
属性获取 pointerIndex
值。 此 ActionIndex
值区分多个手指,但它在多个事件中不一致。 因此,请使用 pointerIndex
从 GetPointerId
方法获取指针 id
值。 此 ID 在多个事件中一致:
public override bool OnTouchEvent(MotionEvent args)
{
// Get the pointer index
int pointerIndex = args.ActionIndex;
// Get the id to identify a finger over the course of its progress
int id = args.GetPointerId(pointerIndex);
// Use ActionMasked here rather than Action to reduce the number of possibilities
switch (args.ActionMasked)
{
// ...
}
// Invalidate to update the view
Invalidate();
// Request continued touch input
return true;
}
请注意,重写使用 switch
语句中的 ActionMasked
属性,而不是 Action
属性。 原因如下:
处理多点触控时,Action
属性的值为 MotionEventsAction.Down
,第一根手指触摸屏幕,然后 Pointer2Down
的值和 Pointer3Down
值,因为第二根和第三根手指也会触摸屏幕。 当第四根和第五根手指接触时,Action
属性具有数值,这些数值甚至与 MotionEventsAction
枚举的成员不对应! 你需要检查值中的位标志的值来解释它们的含义。
同样,当手指与屏幕保持接触时,Action
属性具有第二和第三根手指的 Pointer2Up
值和 Pointer3Up
值,以及第一根手指的 Up
。
ActionMasked
属性采用较少的值,因为它旨在与 ActionIndex
属性结合使用,以区分多个手指。 当手指触摸屏幕时,该属性只能等于第一根手指的 MotionEventActions.Down
,以及后续手指的 PointerDown
。 当手指离开屏幕时,ActionMasked
具有后续手指的 Pointer1Up
值,以及第一根手指的 Up
值。
使用 ActionMasked
时,ActionIndex
区分后续手指触摸和离开屏幕,但通常不需要使用该值,只是作为 MotionEvent
对象中其他方法的参数。 对于多点触控,上述代码中调用了其中最重要的方法之一 GetPointerId
。 该方法返回一个值,该值可用于字典键将特定事件与手指相关联。
示例中的 OnTouchEvent
重写通过创建新的 FingerPaintPolyline
对象并将其添加到字典中,以相同的方式处理 MotionEventActions.Down
和 PointerDown
事件:
public override bool OnTouchEvent(MotionEvent args)
{
// Get the pointer index
int pointerIndex = args.ActionIndex;
// Get the id to identify a finger over the course of its progress
int id = args.GetPointerId(pointerIndex);
// Use ActionMasked here rather than Action to reduce the number of possibilities
switch (args.ActionMasked)
{
case MotionEventActions.Down:
case MotionEventActions.PointerDown:
// Create a Polyline, set the initial point, and store it
FingerPaintPolyline polyline = new FingerPaintPolyline
{
Color = StrokeColor,
StrokeWidth = StrokeWidth
};
polyline.Path.MoveTo(args.GetX(pointerIndex),
args.GetY(pointerIndex));
inProgressPolylines.Add(id, polyline);
break;
// ...
}
// ...
}
请注意,pointerIndex
还用于获取视图中手指的位置。 所有触摸信息都与 pointerIndex
值相关联。 id
通过多个消息唯一标识手指,以便用于创建字典条目。
同样,OnTouchEvent
重写还通过将已完成的折线传输到 completedPolylines
集合来处理 MotionEventActions.Up
和 Pointer1Up
,以便在 OnDraw
重写期间绘制它们。 该代码还会从字典中删除 id
条目:
public override bool OnTouchEvent(MotionEvent args)
{
// ...
switch (args.ActionMasked)
{
// ...
case MotionEventActions.Up:
case MotionEventActions.Pointer1Up:
inProgressPolylines[id].Path.LineTo(args.GetX(pointerIndex),
args.GetY(pointerIndex));
// Transfer the in-progress polyline to a completed polyline
completedPolylines.Add(inProgressPolylines[id]);
inProgressPolylines.Remove(id);
break;
case MotionEventActions.Cancel:
inProgressPolylines.Remove(id);
break;
}
// ...
}
现在来看棘手的部分。
在上下事件之间,通常有许多 MotionEventActions.Move
事件。 这些绑定在对 OnTouchEvent
的单个调用中,它们必须以不同于 Down
和 Up
事件的方式进行处理。 必须忽略之前从 ActionIndex
属性获取的 pointerIndex
值。 相反,该方法必须通过循环 0 和 PointerCount
属性来获取多个 pointerIndex
值,然后为每个 pointerIndex
值获取 id
:
public override bool OnTouchEvent(MotionEvent args)
{
// ...
switch (args.ActionMasked)
{
// ...
case MotionEventActions.Move:
// Multiple Move events are bundled, so handle them differently
for (pointerIndex = 0; pointerIndex < args.PointerCount; pointerIndex++)
{
id = args.GetPointerId(pointerIndex);
inProgressPolylines[id].Path.LineTo(args.GetX(pointerIndex),
args.GetY(pointerIndex));
}
break;
// ...
}
// ...
}
这种类型的处理允许示例跟踪各个手指并在屏幕上绘制结果:
现在,你已了解如何跟踪屏幕上的各个手指并区分它们。