Share via


逐步解說 - 在 Android 中使用觸控

讓我們看看如何使用工作應用程式中上一節的概念。 我們將建立具有四個活動的應用程式。 第一個活動會是功能表或切換板,將啟動其他活動來示範各種 API。 下列螢幕快照顯示主要活動:

[觸控我] 按鈕的範例螢幕快照

第一個活動觸控範例會示範如何使用事件處理程式來觸控檢視。 手勢辨識器活動將示範如何子類別 Android.View.Views 和處理事件,以及示範如何處理捏合手勢。 第三個和最後一個活動自定義 手勢會顯示如何使用自定義手勢。 為了讓事情更容易遵循並吸收,我們會將此逐步解說分成各節,每個區段都著重於其中一個活動。

觸控範例活動

  • 開啟專案 TouchWalkthrough_StartMainActivity 全都準備去 - 我們由我們執行活動中的觸控行為。 如果您執行應用程式並按兩下列 活動[觸控範例],則應該會啟動下列活動:

    顯示 Touch Begins 的活動螢幕快照

  • 既然我們已確認活動已啟動,請開啟檔案TouchActivity.cs,並新增 事件的ImageView處理程式Touch

    _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就會在 中TextView顯示 Touch Begins。 當使用者不再觸碰 ImageView時,[觸控結束] 訊息會顯示在 中TextView,如下列螢幕快照所示:

觸控結束的活動螢幕快照

手勢辨識器活動

現在,讓我們實作手勢辨識器活動。 此活動將示範如何在畫面周圍拖曳檢視,並說明實作捏合至縮放的一種方式。

  • 將新的活動新增至名為 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 至我們的活動。 此時,程式代碼仍然不會編譯 – 我們需要建立類別 MyScaleListener ,以協助 ImageView 調整使用者對它的大小:

    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));
    }
    
  • 若要在活動上繪製影像,我們需要覆寫 OnDraw View 類別的 方法,如下列代碼段所示。 此程式代碼會將 移至 ImageView_posX 指定的位置, _posY 並根據縮放比例調整影像大小:

    protected override void OnDraw(Canvas canvas)
    {
        base.OnDraw(canvas);
        canvas.Save();
        canvas.Translate(_posX, _posY);
        canvas.Scale(_scaleFactor, _scaleFactor);
        _icon.Draw(canvas);
        canvas.Restore();
    }
    
  • 接下來,我們需要更新實例變數_scaleFactor,因為使用者會使用 。ImageView 我們將新增名為 MyScaleListener的類別。 此類別會接聽當使用者捏出 時Android所引發的 ImageView縮放事件。 將下列內部類別新增至 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;
        }
    }
    
  • 我們需要覆寫的GestureRecognizerViewOnTouchEvent下一個方法是 。 下列程式代碼會列出這個方法的完整實作。 這裡有很多程序代碼,所以讓我們花一分鐘時間看看這裡發生了什麼事。 這個方法的第一件事是視需要調整圖示,這是藉由呼叫 _scaleDetector.OnTouchEvent來處理。 接下來,我們會嘗試找出呼叫此方法的動作:

    • 如果使用者使用 觸控螢幕,我們會記錄 X 和 Y 位置,以及觸碰螢幕的第一個指標標識碼。

    • 如果用戶在畫面上移動其觸控,我們就會找出用戶移動指標的距離。

    • 如果用戶已經將手指從螢幕上抬開,我們將會停止追蹤手勢。

    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應用程式中實作捏合縮放! 快速休息,並讓繼續前往本逐步解說中的第三個和最後一個活動 – 使用自定義手勢。

自定義手勢活動

本逐步解說的最後一個畫面將會使用自定義手勢。

為了本逐步解說的目的,已使用手勢工具建立手勢連結庫,並已新增至資源/原始/手勢檔案中的專案。 透過這一點管家工作的方式,讓我們繼續進行逐步解說中的最後一個活動。

  • 使用下列內容,將名為 custom_gesture_layout.axml 的配置檔新增至專案。 專案已在 [資源] 資料夾中擁有所有影像

    <?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。 將兩個實例變數新增至 類別,如下列兩行程式代碼所示:

    private GestureLibrary _gestureLibrary;
    private ImageView _imageView;
    
  • OnCreate編輯此活動的 方法,使其類似下列程序代碼。 讓我們花一分鐘時間來說明此程式代碼中發生什麼事。 我們做的第一件事是具現化 , GestureOverlayView 並將它設定為活動的根檢視。 我們也會將事件處理程式指派給 GesturePerformed 的事件 GestureOverlayView。 接下來,我們會擴充稍早建立的配置檔案,並將該檔案新增為 的 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);
        }
    }
    
  • 執行應用程式並啟動自定義手勢辨識器活動。 其看起來應該類似下列螢幕快照:

    [檢查我] 影像的螢幕快照

    現在在畫面上繪製複選標記,所顯示的點陣圖看起來應該像下一個螢幕快照所示:

    繪製複選標記,可辨識複選標記

    最後,在螢幕上繪製一個塗鴉。 複選框應該會變更回其原始影像,如下列螢幕快照所示:

    在螢幕上塗鴉,顯示原始影像

您現在已瞭解如何使用 Xamarin.Android 在 Android 應用程式中整合觸控和手勢。