共用方式為


多點觸控手指追蹤

本主題示範如何追蹤多指的觸控事件

有時,多觸控應用程式需要追蹤個別手指,因為它們同時在螢幕上移動。 一個典型的應用程式是手指繪製程式。 您希望用戶能夠使用單指繪製,但也想要一次使用多根手指繪製。 當您的程式處理多個觸控事件時,它必須區分哪些事件對應至每個手指。 Android 會為此提供標識碼,但取得和處理該程式代碼可能有點棘手。

對於與特定手指相關聯的所有事件,標識碼會維持不變。 當手指第一次觸碰螢幕時,會指派標識碼,並在手指從螢幕抬起之後變成無效。 這些標識碼通常是非常小的整數,而Android會針對稍後的觸控事件重複使用這些標識碼。

幾乎一律是追蹤個別手指的程式會維護觸控追蹤的字典。 字典索引鍵是識別特定手指的標識碼。 字典值取決於應用程式。 在 Finger 小畫家 範例中,每個手指筆劃(從觸控到釋放)都會與對象相關聯,其中包含使用該手指繪製線條所需的所有資訊。 程式會為此目的定義小型 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兩個方法:OnDrawOnTouchEvent。 在覆 OnDraw 寫中,檢視會繪製已完成的聚合線條,然後繪製進行中的聚合線條。

方法的覆寫會 OnTouchEvent 從 屬性取得 pointerIndexActionIndex 開始。 這個 ActionIndex 值區分多個手指,但它在多個事件之間並不一致。 基於這個理由,您可以使用 pointerIndex 來從 GetPointerId 方法取得指標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;
}

請注意,覆寫會使用 ActionMasked 語句中的 switch 屬性,而不是 Action 屬性。 原因如下:

當您處理多點觸控時, Action 屬性的值會讓第一個 MotionEventsAction.Down 手指觸碰螢幕,然後將 Pointer2DownPointer3Down 的值設定為 和 ,做為第二和第三個手指也會觸碰螢幕。 當第四根和第五根手指接觸時, Action 屬性具有數值,甚至不會對應至列舉的成員 MotionEventsAction ! 您必須檢查值中的位旗標值,以解譯它們的意義。

同樣地,當手指與螢幕保持接觸時, Action 屬性具有 Pointer2UpPointer3Up 的值,以及第二根和第三根手指的值,以及 Up 第一根手指。

屬性 ActionMasked 會佔用較少的值數目,因為它的目的是要與 屬性搭配 ActionIndex 使用,以區分多指。 當手指觸碰螢幕時,屬性只能等於 MotionEventActions.Down 第一根手指和 PointerDown 後續手指。 當手指離開螢幕時,ActionMasked針對後續手指和Up第一根手指具有的值Pointer1Up

使用 ActionMasked時,會 ActionIndex 區分後續手指來觸控和離開螢幕,但您通常不需要使用該值,但除了做為物件中其他方法的 MotionEvent 自變數。 針對多點觸控,上述程式代碼會呼叫其中一個最重要的方法 GetPointerId 。 該方法會傳回值,您可以用於字典索引鍵,將特定事件與手指產生關聯。

OnTouchEvent範例中的覆寫會藉由建立新的 FingerPaintPolyline 物件並將它新增至字典,以相同方式處理 MotionEventActions.DownPointerDown 事件:

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寫也會藉MotionEventActions.Up由將完成的聚合線條傳送至completedPolylines集合來處理 和 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 事件的方式處理。 pointerIndex必須忽略先前從 ActionIndex 屬性取得的值。 相反地,方法必須藉由迴圈介於 0 和 PointerCount 屬性之間取得多個pointerIndex值,然後取得id每個值pointerIndex的 :

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;
        // ...
    }
    // ...        
}

這種類型的處理可讓範例追蹤個別手指,並在畫面上繪製結果:

來自 Finger 的範例螢幕快照 小畫家 範例

您現在已瞭解如何追蹤螢幕上的個別手指,並區分它們。