Windows タッチ&ジェスチャ研究室 Vol.02 ~Silverlightでのストレッチ/ピンチ~

前回の記事で、Sliverlightでマルチタッチを実装するには、Touchクラスを使用することを説明しました。
Touchクラスは、タッチされた座標等の生のデータを取得するクラスなので、ジェスチャーに対応したアプリを開発するには、生のデータを加工する必要があります。
今回は、Silverlightで2本の指を使用したストレッチ(2本の指の間隔を広げて拡大する)と、ピンチ(2本の指の間隔を狭めて縮小する)のサンプル アプリケーションを紹介します。
充分なテストをしている訳ではありませんので、実装時に参考する程度にしてください。

=======================
public partial class MainPage : UserControl
{
    private Point FirstFingerOrigin = new Point();
    private Point FirstFingerStart = new Point();
    private Point FirstFingerEnd = new Point();
    private Point FirstFingerFinish = new Point();
    private int FirstFingerID = -1;

    private Point SecondFingerOrigin = new Point();
    private Point SecondFingerStart = new Point();
    private Point SecondFingerEnd = new Point();
    private Point SecondFingerFinish = new Point();
    private int SecondFingerID = -1;

    private Point FirstFingerTranslation = new Point();
    private Point SecondFingerTranslation = new Point();
    private Point Scale = new Point();
    private double TotalScale;

    public MainPage()
    {
        InitializeComponent();

        Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);
    }

    void Touch_FrameReported(object sender, TouchFrameEventArgs e)
    {
        TouchPointCollection touchPoints = e.GetTouchPoints(null);

        foreach (var touchPoint in touchPoints)
        {
            switch (touchPoint.Action)
            {
                case TouchAction.Down:
                    if (FirstFingerID == -1)
                    {
                        // 最初のタッチのID
                        FirstFingerID = touchPoint.TouchDevice.Id;
                        FirstFingerOrigin = touchPoint.Position;

                        FirstFingerStart = FirstFingerOrigin;
                        FirstFingerTranslation.X = 0;
                        FirstFingerTranslation.Y = 0;
                    }
                    else if (SecondFingerID == -1)
                    {
                        // 2番めのタッチのID
                        SecondFingerID = touchPoint.TouchDevice.Id;
                        SecondFingerOrigin = touchPoint.Position;

                        SecondFingerStart = SecondFingerOrigin;
                        SecondFingerTranslation.X = 0;
                        SecondFingerTranslation.Y = 0;
                    }
                    break;
                    
                case TouchAction.Move:
                    if (FirstFingerID == touchPoint.TouchDevice.Id)
                    {
                        FirstFingerEnd = touchPoint.Position;
 
                       // 指が移動しているときに何かするときは、ここに実装する

                        FirstFingerStart = FirstFingerEnd;
                    }
                    else if (SecondFingerID == touchPoint.TouchDevice.Id)
                    {
                        SecondFingerEnd = touchPoint.Position;

                        // 指が移動しているときに何かするときは、ここに実装する

                        SecondFingerStart = SecondFingerEnd;
                    }
                    break;

                case TouchAction.Up:
                    if (FirstFingerID == touchPoint.TouchDevice.Id)
                    {
                        FirstFingerFinish = touchPoint.Position;

                        FirstFingerTranslation.X =
FirstFingerFinish.X - FirstFingerOrigin.X;
                        FirstFingerTranslation.Y =
FirstFingerFinish.Y - FirstFingerOrigin.Y;

                        FirstFingerID = -1;
                    }
                    else if (SecondFingerID == touchPoint.TouchDevice.Id)
                    {
                        SecondFingerFinish = touchPoint.Position;

                        SecondFingerTranslation.X =
SecondFingerFinish.X - SecondFingerOrigin.X;
                        SecondFingerTranslation.Y =
SecondFingerFinish.Y - SecondFingerOrigin.Y;

                        SecondFingerID = -1;
                    }

                    // 1つめの指、2つめの指の両方が動いていたとき
                    if ((FirstFingerTranslation.X != 0 ||
FirstFingerTranslation.Y != 0) &&
                        (SecondFingerTranslation.X != 0 ||
SecondFingerTranslation.Y != 0))
                    {
                        Point originDistance = new Point();
                        Point finishDistance = new Point();

                        // タッチしたときの2つの指の距離
                        originDistance.X =
SecondFingerOrigin.X - FirstFingerOrigin.X;
                        originDistance.Y =
SecondFingerOrigin.Y - FirstFingerOrigin.Y;

                        // 離したときの2つの指の距離
                        finishDistance.X =
SecondFingerFinish.X - FirstFingerFinish.X;
                        finishDistance.Y =
SecondFingerFinish.Y - FirstFingerFinish.Y;

                        // 縮小率または拡大率
                        Scale.X = finishDistance.X / originDistance.X;
                        Scale.Y = finishDistance.Y / originDistance.Y;

                        // 三平方の定理を使い、距離を計算
                        double distance1, distance2;

                        distance1 = Math.Sqrt(Math.Pow(originDistance.X, 2) +
                            Math.Pow(originDistance.Y, 2));

                        distance2 = Math.Sqrt(Math.Pow(finishDistance.X, 2) +
                            Math.Pow(finishDistance.Y, 2));

                        TotalScale = distance2 / distance1;
                    }

                    // 結果を表示させるためのプログラム
                    // テキストボックスを3つ貼り付け、そこに情報を表示している
textBlock1.Text = Scale.X.ToString();
                    textBlock2.Text = Scale.Y.ToString();
                    textBlock3.Text = TotalScale.ToString();

                    break;
            }
        }
    }
}
=======================

このプログラムを試すときは、まずTextBlockコントロールを3つ貼り付け、そしてMainPageクラスを上記のプログラムで書き換えてください。

結論としては、TotalScaleというフィールドに、拡大率(または縮小率)が入ります。1より大きい場合は拡大、1より小さい場合は縮小です。
単純に、ストレッチがピンチかを検知する場合は、TotalScaleのみを使えば検知できます。
水平方向または垂直方向の拡大率を使いたい場合は、Scale.XまたはScale.Yをご使用ください。

このプログラムでは、画面をタッチしたときのイベント発生時の、Down、Move、Upのそれぞれの処理を行っています。
指を動かしている最中にも画面の拡大/縮小を反映させたいときは、Moveの処置を行っているときに実装します。

主なフィールドの説明は、以下のとおりです。
  FirstFingerOrigin : 最初の指がタッチしたときの座標
  FirstFingerStart : 前回のMoveが呼ばれた時の座標 (Moveの処理を行うときに使用)
  FirstFingerEnd : Moveが呼ばれたときの座標 (Moveの処理を行うときに使用)
  FirstFingerFinish : 最初の指が離れたときの座標
Secondで始まるフィールドは、2本目にタッチした指に関する情報です。

Silverlightでも、Windows Phone 7の場合は、同じ機能をもったManipulationという機能がありますので、そちらをご使用ください。

[Windows タッチ&ジェスチャ研究室]
Vol.01 ~Silverlightのマルチタッチ検出~

マイクロソフト
田中達彦