チュートリアル – Android でタッチを使用する

作業アプリケーションで前のセクションの概念を使用する方法を見てみましょう。 4 つのアクティビティを含むアプリケーションを作成します。 最初のアクティビティは、さまざまな API を示すために他のアクティビティを起動するメニューまたはスイッチボードです。 次のスクリーンショットは、メイン アクティビティを示しています。

[Touch Me] ボタンを使用したスクリーンショットの例

最初のアクティビティであるタッチ サンプルでは、イベント ハンドラーを使用してビューにタッチする方法を示します。 ジェスチャ認識エンジン アクティビティでは、Android.View.Views をサブクラス化してイベントを処理する方法と、ピンチ ジェスチャを処理する方法を示します。 3 番目と最後のアクティビティであるカスタム ジェスチャでは、カスタム ジェスチャの使用方法が示されます。 手順に簡単に従って身につけることができるように、このチュートリアルをセクションに分けて、各セクションでアクティビティの 1 つに焦点を当てます。

タッチ サンプル アクティビティ

  • プロジェクト TouchWalkthrough_Start を開きます。 MainActivity は準備がすっかりできています。アクティビティにタッチ動作を実装するのは各自に任されています。 アプリケーションを実行して [タッチ サンプル] を選択すると、次のアクティビティが起動します。

  • アクティビティが起動したことを確認したので、ファイル TouchActivity.cs を開き、ImageViewTouch イベントのハンドラーを追加します。

    _touchMeImageView.Touch += TouchMeImageViewOnTouch;
    
  • 次に、次のメソッドを TouchActivity.cs に追加します。

    private void TouchMeImageViewOnTouch(object sender, View.TouchEventArgs touchEventArgs)
    {
        string message;
        switch (touchEventArgs.Event.Action & MotionEventActions.Mask)
        {
            case MotionEventActions.Down:
            case MotionEventActions.Move:
            message = "Touch Begins";
            break;
    
            case MotionEventActions.Up:
            message = "Touch Ends";
            break;
    
            default:
            message = string.Empty;
            break;
        }
    
        _touchInfoTextView.Text = message;
    }
    

上記のコードでは、Down および Move アクションを同じように扱うことに注意してください。 これは、ユーザーが ImageView から指を離さなくても、移動されたり、ユーザーが加えた圧力が変化したりする可能性があるためです。 これらの種類の変更によって Move アクションが生成されます。

ユーザーが ImageView にタッチするたびに、Touch イベントが発生し、次のスクリーンショットに示すようにハンドラーによって "Touch Begins" メッセージが画面に表示されます。

Touch Begins を使用したアクティビティのスクリーンショット

ユーザーが ImageView にタッチしている限り、"Touch Begins" が TextView に表示されます。 ユーザーが ImageView にタッチしなくなったら、次のスクリーンショットに示すように、Touch Ends メッセージが TextView に表示されます。

Touch Ends を使用したアクティビティのスクリーンショット

ジェスチャ認識エンジン アクティビティ

次に、ジェスチャ認識エンジン アクティビティを実装しましょう。 このアクティビティでは、画面の周りにビューをドラッグし、ピンチ操作によるズームを実装する 1 つの方法を示します。

  • アプリケーションに GestureRecognizer という名前の新しいアクティビティを追加します。 次のコードのようになるように、このアクティビティのコードを編集します。

    public class GestureRecognizerActivity : Activity
    {
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            View v = new GestureRecognizerView(this);
            SetContentView(v);
        }
    }
    
  • 新しい Android ビューをプロジェクトに追加し、GestureRecognizerView という名前を付けます。 このクラスに次の変数を追加します。

    private static readonly int InvalidPointerId = -1;
    
    private readonly Drawable _icon;
    private readonly ScaleGestureDetector _scaleDetector;
    
    private int _activePointerId = InvalidPointerId;
    private float _lastTouchX;
    private float _lastTouchY;
    private float _posX;
    private float _posY;
    private float _scaleFactor = 1.0f;
    
  • 次のコンストラクターを GestureRecognizerView に追加します。 このコンストラクターでは、アクティビティに ImageView を追加します。 この時点で、コードはまだコンパイルされません。ユーザーがピンチしたときの ImageView のサイズ変更に役立つクラス MyScaleListener を作成する必要があります。

    public GestureRecognizerView(Context context): base(context, null, 0)
    {
        _icon = context.Resources.GetDrawable(Resource.Drawable.Icon);
        _icon.SetBounds(0, 0, _icon.IntrinsicWidth, _icon.IntrinsicHeight);
        _scaleDetector = new ScaleGestureDetector(context, new MyScaleListener(this));
    }
    
  • アクティビティに画像を描画するには、次のスニペットに示すように、View クラスの OnDraw メソッドをオーバーライドする必要があります。 このコードは、_posX_posY によって指定された位置に ImageView を移動し、拡大縮小率に従って画像をサイズ変更します。

    protected override void OnDraw(Canvas canvas)
    {
        base.OnDraw(canvas);
        canvas.Save();
        canvas.Translate(_posX, _posY);
        canvas.Scale(_scaleFactor, _scaleFactor);
        _icon.Draw(canvas);
        canvas.Restore();
    }
    
  • 次に、ユーザーが ImageView をピンチしたときに、インスタンス変数 _scaleFactor を更新する必要があります。 MyScaleListener というクラスを追加します。 このクラスでは、ユーザーが ImageView をピンチしたときに Android によって発生するスケール イベントをリッスンします。 次の内部クラスを GestureRecognizerView に追加します。 このクラスは ScaleGesture.SimpleOnScaleGestureListener です。 このクラスは、ジェスチャのサブセットが役に立つときにリスナーでサブクラス化できる便利なクラスです。

    private class MyScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener
    {
        private readonly GestureRecognizerView _view;
    
        public MyScaleListener(GestureRecognizerView view)
        {
            _view = view;
        }
    
        public override bool OnScale(ScaleGestureDetector detector)
        {
            _view._scaleFactor *= detector.ScaleFactor;
    
            // put a limit on how small or big the image can get.
            if (_view._scaleFactor > 5.0f)
            {
                _view._scaleFactor = 5.0f;
            }
            if (_view._scaleFactor < 0.1f)
            {
                _view._scaleFactor = 0.1f;
            }
    
            _view.Invalidate();
            return true;
        }
    }
    
  • GestureRecognizerView でオーバーライドする必要がある次のメソッドは OnTouchEvent です。 次のコードは、このメソッドの完全な実装を示しています。 ここには多くのコードがあるため、少し時間を取って、ここで何が起こっているかを見てみましょう。 このメソッドで最初に行うことは、必要に応じてアイコンを拡大縮小することです。これは、_scaleDetector.OnTouchEvent を呼び出すことによって処理されます。 次に、このメソッドを呼び出したアクションを見つけます。

    • ユーザーが画面をタッチした場合は、X と Y の位置と、画面にタッチした最初のポインターの ID を記録します。

    • ユーザーが画面でタッチを移動した場合、ユーザーがポインターを移動した距離を確認します。

    • ユーザーが画面から指を離した場合は、ジェスチャの追跡を停止します。

    public override bool OnTouchEvent(MotionEvent ev)
    {
        _scaleDetector.OnTouchEvent(ev);
    
        MotionEventActions action = ev.Action & MotionEventActions.Mask;
        int pointerIndex;
    
        switch (action)
        {
            case MotionEventActions.Down:
            _lastTouchX = ev.GetX();
            _lastTouchY = ev.GetY();
            _activePointerId = ev.GetPointerId(0);
            break;
    
            case MotionEventActions.Move:
            pointerIndex = ev.FindPointerIndex(_activePointerId);
            float x = ev.GetX(pointerIndex);
            float y = ev.GetY(pointerIndex);
            if (!_scaleDetector.IsInProgress)
            {
                // Only move the ScaleGestureDetector isn't already processing a gesture.
                float deltaX = x - _lastTouchX;
                float deltaY = y - _lastTouchY;
                _posX += deltaX;
                _posY += deltaY;
                Invalidate();
            }
    
            _lastTouchX = x;
            _lastTouchY = y;
            break;
    
            case MotionEventActions.Up:
            case MotionEventActions.Cancel:
            // We no longer need to keep track of the active pointer.
            _activePointerId = InvalidPointerId;
            break;
    
            case MotionEventActions.PointerUp:
            // check to make sure that the pointer that went up is for the gesture we're tracking.
            pointerIndex = (int) (ev.Action & MotionEventActions.PointerIndexMask) >> (int) MotionEventActions.PointerIndexShift;
            int pointerId = ev.GetPointerId(pointerIndex);
            if (pointerId == _activePointerId)
            {
                // This was our active pointer going up. Choose a new
                // action pointer and adjust accordingly
                int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                _lastTouchX = ev.GetX(newPointerIndex);
                _lastTouchY = ev.GetY(newPointerIndex);
                _activePointerId = ev.GetPointerId(newPointerIndex);
            }
            break;
    
        }
        return true;
    }
    
  • 次に、アプリケーションを実行し、ジェスチャ認識エンジン アクティビティを開始します。 開始すると、画面は次のスクリーンショットのようになります。

    Android アイコンがあるジェスチャ認識エンジンのスタート画面

  • 次に、アイコンをタッチし、画面の周りにドラッグします。 ピンチ操作によるズームのジェスチャを試してみてください。 ある時点で、画面は次のスクリーンショットのようになります。

    画面の周りのジェスチャ移動アイコン

お疲れさまでした。Android アプリケーションでピンチ操作によるズームが実装されました。 ちょっと一息入れてから、カスタム ジェスチャを使用して、このチュートリアルの 3 番目と最後のアクティビティに進みましょう。

カスタム ジェスチャ アクティビティ

このチュートリアルの最後の画面では、カスタム ジェスチャを使用します。

このチュートリアルでは、ジェスチャ ライブラリはジェスチャ ツールを使用して既に作成されており、Resources/raw/gestures ファイル内のプロジェクトに追加されています。 このハウスキープ処理が片付いたので、チュートリアルの最後のアクティビティに取り掛かりましょう。

  • custom_gesture_layout.axml という名前のレイアウト ファイルを次の内容でプロジェクトに追加します。 プロジェクトには、Resources フォルダー内のすべての画像が既に含まれています。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />
        <ImageView
            android:src="@drawable/check_me"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="3"
            android:id="@+id/imageView1"
            android:layout_gravity="center_vertical" />
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />
    </LinearLayout>
    
  • 次に、新しいアクティビティをプロジェクトに追加し、CustomGestureRecognizerActivity.cs という名前を付けます。 次の 2 つのコード行に示すように、2 つのインスタンス変数をクラスに追加します。

    private GestureLibrary _gestureLibrary;
    private ImageView _imageView;
    
  • 次のコードのようになるように、このアクティビティの OnCreate メソッドを編集します。 このコードで何が起こっているのかを説明するために少し時間を取ります。 まず、GestureOverlayView のインスタンスを作成し、それをアクティビティのルート ビューとして設定します。 また、イベント ハンドラーを GestureOverlayViewGesturePerformed イベントに割り当てます。 次に、先ほど作成したレイアウト ファイルを拡張し、それを GestureOverlayView の子ビューとして追加します。 最後の手順では、変数 _gestureLibrary を初期化し、アプリケーション リソースからジェスチャ ファイルを読み込みます。 何らかの理由でジェスチャ ファイルを読み込めない場合、このアクティビティで実行できる操作はあまりないため、シャットダウンされます。

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
    
        GestureOverlayView gestureOverlayView = new GestureOverlayView(this);
        SetContentView(gestureOverlayView);
        gestureOverlayView.GesturePerformed += GestureOverlayViewOnGesturePerformed;
    
        View view = LayoutInflater.Inflate(Resource.Layout.custom_gesture_layout, null);
        _imageView = view.FindViewById<ImageView>(Resource.Id.imageView1);
        gestureOverlayView.AddView(view);
    
        _gestureLibrary = GestureLibraries.FromRawResource(this, Resource.Raw.gestures);
        if (!_gestureLibrary.Load())
        {
            Log.Wtf(GetType().FullName, "There was a problem loading the gesture library.");
            Finish();
        }
    }
    
  • 最後に、次のコード スニペットに示すように、メソッド GestureOverlayViewOnGesturePerformed を実装する必要があります。 GestureOverlayView によってジェスチャが検出されると、このメソッドがコールバックされます。 最初に、_gestureLibrary.Recognize() を呼び出して、ジェスチャに一致する IList<Prediction> オブジェクトを取得しようとします。 LINQ を少し使用して、ジェスチャのスコアが最も高い Prediction を取得します。

    スコアが十分に高い一致するジェスチャがない場合、イベント ハンドラーでは何もせずに終了します。 それ以外の場合は、予測の名前を確認し、ジェスチャの名前に基づいて表示される画像を変更します。

    private void GestureOverlayViewOnGesturePerformed(object sender, GestureOverlayView.GesturePerformedEventArgs gesturePerformedEventArgs)
    {
        IEnumerable<Prediction> predictions = from p in _gestureLibrary.Recognize(gesturePerformedEventArgs.Gesture)
        orderby p.Score descending
        where p.Score > 1.0
        select p;
        Prediction prediction = predictions.FirstOrDefault();
    
        if (prediction == null)
        {
            Log.Debug(GetType().FullName, "Nothing seemed to match the user's gesture, so don't do anything.");
            return;
        }
    
        Log.Debug(GetType().FullName, "Using the prediction named {0} with a score of {1}.", prediction.Name, prediction.Score);
    
        if (prediction.Name.StartsWith("checkmark"))
        {
            _imageView.SetImageResource(Resource.Drawable.checked_me);
        }
        else if (prediction.Name.StartsWith("erase", StringComparison.OrdinalIgnoreCase))
        {
            // Match one of our "erase" gestures
            _imageView.SetImageResource(Resource.Drawable.check_me);
        }
    }
    
  • アプリケーションを実行し、カスタム ジェスチャ認識エンジン アクティビティを起動します。 次のスクリーンショットのようになります。

    Check Me 画像が付いたスクリーンショット

    次に、画面上にチェックマークを描画します。表示されるビットマップは、次のスクリーンショットに示すような外観になります。

    描画されたチェックマーク。チェックマークが認識されます

    最後に、画面に落書きします。 チェックボックスは、次のスクリーンショットに示すように元のイメージに戻ります。

    画面の落書き。元の画像が表示されます

Xamarin.Android を使用して Android アプリケーションにタッチとジェスチャを統合する方法を説明しました。