演练:在 Xamarin.iOS 中使用触控

本演练演示如何编写响应不同类型的触控事件的代码。 每个示例都包含在单独的屏幕中:

每个部分都包含从头开始编写代码的说明。

请按照以下说明将代码添加到情节提要,并了解 iOS 中可用的不同类型的触控事件。 或者,打开已完成的示例以查看是否一切正常。

触控示例

在此示例中,我们将演示一些触控 API。 请按照以下步骤添加实现触控事件所需的代码:

  1. 打开项目 Touch_Start。 首先运行项目,以确保一切正常,然后触摸“触控示例”按钮。 应该会出现类似下面的屏幕(尽管没有一个按钮可以正常工作):

    Sample app run with non-working buttons

  2. 编辑文件 TouchViewController.cs 并将以下两个实例变量添加到类 TouchViewController

    #region Private Variables
    private bool imageHighlighted = false;
    private bool touchStartedInside;
    #endregion
    
  3. 实现 TouchesBegan 方法,如下面的代码所示:

    public override void TouchesBegan(NSSet touches, UIEvent evt)
    {
        base.TouchesBegan(touches, evt);
    
        // If Multitouch is enabled, report the number of fingers down
        TouchStatus.Text = string.Format ("Number of fingers {0}", touches.Count);
    
        // Get the current touch
        UITouch touch = touches.AnyObject as UITouch;
        if (touch != null)
        {
            // Check to see if any of the images have been touched
            if (TouchImage.Frame.Contains(touch.LocationInView(TouchView)))
            {
                // Fist image touched
                TouchImage.Image = UIImage.FromBundle("TouchMe_Touched.png");
                TouchStatus.Text = "Touches Began";
            } else if (touch.TapCount == 2 && DoubleTouchImage.Frame.Contains(touch.LocationInView(TouchView)))
            {
                // Second image double-tapped, toggle bitmap
                if (imageHighlighted)
                {
                    DoubleTouchImage.Image = UIImage.FromBundle("DoubleTapMe.png");
                    TouchStatus.Text = "Double-Tapped Off";
                }
                else
                {
                    DoubleTouchImage.Image = UIImage.FromBundle("DoubleTapMe_Highlighted.png");
                    TouchStatus.Text = "Double-Tapped On";
                }
                imageHighlighted = !imageHighlighted;
            } else if (DragImage.Frame.Contains(touch.LocationInView(View)))
            {
                // Third image touched, prepare to drag
                touchStartedInside = true;
            }
        }
    }
    

    此方法的工作原理是检查 UITouch 对象,如果存在,则根据触控发生的位置执行某些操作:

    • 在 TouchImage 内 – 在标签中显示文本 Touches Began 并更改图像。
    • 在 DoubleTouchImage 内 – 更改手势是双击时显示的图像。
    • 在 DragImage 内 – 设置一个标志,指示触控已启动。 方法 TouchesMoved 将使用此标志来确定是否应在屏幕上移动 DragImage,正如我们在下一步中看到的那样。

    上述代码仅处理单个触控,如果用户在屏幕上移动手指,则仍然没有行为。 若要响应移动,请实现 TouchesMoved,如下面的代码所示:

    public override void TouchesMoved(NSSet touches, UIEvent evt)
    {
        base.TouchesMoved(touches, evt);
        // get the touch
        UITouch touch = touches.AnyObject as UITouch;
        if (touch != null)
        {
            //==== IMAGE TOUCH
            if (TouchImage.Frame.Contains(touch.LocationInView(TouchView)))
            {
                TouchStatus.Text = "Touches Moved";
            }
    
            //==== IMAGE DRAG
            // check to see if the touch started in the drag me image
            if (touchStartedInside)
            {
                // move the shape
                float offsetX = touch.PreviousLocationInView(View).X - touch.LocationInView(View).X;
                float offsetY = touch.PreviousLocationInView(View).Y - touch.LocationInView(View).Y;
                DragImage.Frame = new RectangleF(new PointF(DragImage.Frame.X - offsetX, DragImage.Frame.Y - offsetY), DragImage.Frame.Size);
            }
        }
    }
    

    此方法获取 UITouch 对象,然后检查触控发生的位置。 如果触控发生在 TouchImage 中,则会在屏幕上显示文本“触控已移动”。

    如果 touchStartedInside 为 true,则我们知道用户的手指在 DragImage 上,并且正在移动它。 当用户在屏幕上移动手指时,代码将移动 DragImage

  4. 当用户将手指从屏幕上移开,或者 iOS 取消了触控事件时,我们需要进行相应的处理。 为此,我们将实现 TouchesEndedTouchesCancelled,如下所示:

    public override void TouchesCancelled(NSSet touches, UIEvent evt)
    {
        base.TouchesCancelled(touches, evt);
    
        // reset our tracking flags
        touchStartedInside = false;
        TouchImage.Image = UIImage.FromBundle("TouchMe.png");
        TouchStatus.Text = "";
    }
    
    public override void TouchesEnded(NSSet touches, UIEvent evt)
    {
        base.TouchesEnded(touches, evt);
        // get the touch
        UITouch touch = touches.AnyObject as UITouch;
        if (touch != null)
        {
            //==== IMAGE TOUCH
            if (TouchImage.Frame.Contains(touch.LocationInView(TouchView)))
            {
                TouchImage.Image = UIImage.FromBundle("TouchMe.png");
                TouchStatus.Text = "Touches Ended";
            }
        }
        // reset our tracking flags
        touchStartedInside = false;
    }
    

    这两种方法都会将 touchStartedInside 标志重置为 false。 TouchesEnded 还将在屏幕上显示 TouchesEnded

  5. 此时,“触控示例”屏幕已完成。 请注意,当你与每张图片交互时,屏幕会如何变化,如以下屏幕截图所示:

    The starting app screen

    The screen after the user drags a button

手势识别器示例

上一部分演示了如何使用触控事件在屏幕上拖动对象。 在本部分中,我们将删除触控事件,并演示如何使用以下手势识别器:

  • UIPanGestureRecognizer 用于在屏幕上拖动图像。
  • UITapGestureRecognizer 用于响应屏幕上的双击。

请按照以下步骤实现手势识别器:

  1. 编辑文件 GestureViewController.cs 并添加以下实例变量:

    #region Private Variables
    private bool imageHighlighted = false;
    private RectangleF originalImageFrame = RectangleF.Empty;
    #endregion
    

    我们需要此实例变量来跟踪图像的先前位置。 平移手势识别器将使用 originalImageFrame 值来计算在屏幕上重新绘制图像所需的偏移量。

  2. 将以下方法添加到控制器:

    private void WireUpDragGestureRecognizer()
    {
        // Create a new tap gesture
        UIPanGestureRecognizer gesture = new UIPanGestureRecognizer();
    
        // Wire up the event handler (have to use a selector)
        gesture.AddTarget(() => HandleDrag(gesture));  // to be defined
    
        // Add the gesture recognizer to the view
        DragImage.AddGestureRecognizer(gesture);
    }
    

    此代码实例化 UIPanGestureRecognizer 实例,并将其添加到视图中。 请注意,我们以方法 HandleDrag 的形式为手势分配目标 – 下一步中提供了此方法。

  3. 若要实现 HandleDrag,请将以下代码添加到控制器:

    private void HandleDrag(UIPanGestureRecognizer recognizer)
    {
        // If it's just began, cache the location of the image
        if (recognizer.State == UIGestureRecognizerState.Began)
        {
            originalImageFrame = DragImage.Frame;
        }
    
        // Move the image if the gesture is valid
        if (recognizer.State != (UIGestureRecognizerState.Cancelled | UIGestureRecognizerState.Failed
            | UIGestureRecognizerState.Possible))
        {
            // Move the image by adding the offset to the object's frame
            PointF offset = recognizer.TranslationInView(DragImage);
            RectangleF newFrame = originalImageFrame;
            newFrame.Offset(offset.X, offset.Y);
            DragImage.Frame = newFrame;
        }
    }
    

    上面的代码将首先检查手势识别器的状态,然后在屏幕上移动图像。 有了此代码,控制器现在可以支持在屏幕上拖动图像。

  4. 添加一个 UITapGestureRecognizer 将更改在 DoubleTouchImage 中显示的图像。 将以下方法添加到 GestureViewController 控制器:

    private void WireUpTapGestureRecognizer()
    {
        // Create a new tap gesture
        UITapGestureRecognizer tapGesture = null;
    
        // Report touch
        Action action = () => {
            TouchStatus.Text = string.Format("Image touched at: {0}",tapGesture.LocationOfTouch(0, DoubleTouchImage));
    
            // Toggle the image
            if (imageHighlighted)
            {
                DoubleTouchImage.Image = UIImage.FromBundle("DoubleTapMe.png");
            }
            else
            {
                DoubleTouchImage.Image = UIImage.FromBundle("DoubleTapMe_Highlighted.png");
            }
            imageHighlighted = !imageHighlighted;
        };
    
        tapGesture = new UITapGestureRecognizer(action);
    
        // Configure it
        tapGesture.NumberOfTapsRequired = 2;
    
        // Add the gesture recognizer to the view
        DoubleTouchImage.AddGestureRecognizer(tapGesture);
    }
    

    此代码与 UIPanGestureRecognizer 的代码非常相似,但不同的是,我们使用了 Action 作为目标而不是委托。

  5. 我们需要做的最后一件事是修改 ViewDidLoad,以便调用刚刚添加的方法。 更改 ViewDidLoad,使其类似于以下代码:

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();
    
        Title = "Gesture Recognizers";
    
        // Save initial state
        originalImageFrame = DragImage.Frame;
    
        WireUpTapGestureRecognizer();
        WireUpDragGestureRecognizer();
    }
    

    另请注意,我们初始化 originalImageFrame 的值。

  6. 运行应用程序,并与两个图像进行交互。 以下屏幕截图是这些交互的一个示例:

    This screenshot shows a drag interaction

自定义手势识别器

在本部分中,我们将应用前面部分中的概念来生成自定义手势识别器。 自定义手势识别器将创建 UIGestureRecognizer 的子类,并在用户在屏幕上绘制“V”时识别,然后切换位图。 以下屏幕截图是此屏幕的示例:

The app will recognize when the user draws a V on the screen

请按照以下步骤创建自定义手势识别器:

  1. 向名为 CheckmarkGestureRecognizer 的项目添加新类,使其如以下代码所示:

    using System;
    using CoreGraphics;
    using Foundation;
    using UIKit;
    
    namespace Touch
    {
        public class CheckmarkGestureRecognizer : UIGestureRecognizer
        {
            #region Private Variables
            private CGPoint midpoint = CGPoint.Empty;
            private bool strokeUp = false;
            #endregion
    
            #region Override Methods
            /// <summary>
            ///   Called when the touches end or the recognizer state fails
            /// </summary>
            public override void Reset()
            {
                base.Reset();
    
                strokeUp = false;
                midpoint = CGPoint.Empty;
            }
    
            /// <summary>
            ///   Is called when the fingers touch the screen.
            /// </summary>
            public override void TouchesBegan(NSSet touches, UIEvent evt)
            {
                base.TouchesBegan(touches, evt);
    
                // we want one and only one finger
                if (touches.Count != 1)
                {
                    base.State = UIGestureRecognizerState.Failed;
                }
    
                Console.WriteLine(base.State.ToString());
            }
    
            /// <summary>
            ///   Called when the touches are cancelled due to a phone call, etc.
            /// </summary>
            public override void TouchesCancelled(NSSet touches, UIEvent evt)
            {
                base.TouchesCancelled(touches, evt);
                // we fail the recognizer so that there isn't unexpected behavior
                // if the application comes back into view
                base.State = UIGestureRecognizerState.Failed;
            }
    
            /// <summary>
            ///   Called when the fingers lift off the screen
            /// </summary>
            public override void TouchesEnded(NSSet touches, UIEvent evt)
            {
                base.TouchesEnded(touches, evt);
                //
                if (base.State == UIGestureRecognizerState.Possible && strokeUp)
                {
                    base.State = UIGestureRecognizerState.Recognized;
                }
    
                Console.WriteLine(base.State.ToString());
            }
    
            /// <summary>
            ///   Called when the fingers move
            /// </summary>
            public override void TouchesMoved(NSSet touches, UIEvent evt)
            {
                base.TouchesMoved(touches, evt);
    
                // if we haven't already failed
                if (base.State != UIGestureRecognizerState.Failed)
                {
                    // get the current and previous touch point
                    CGPoint newPoint = (touches.AnyObject as UITouch).LocationInView(View);
                    CGPoint previousPoint = (touches.AnyObject as UITouch).PreviousLocationInView(View);
    
                    // if we're not already on the upstroke
                    if (!strokeUp)
                    {
                        // if we're moving down, just continue to set the midpoint at
                        // whatever point we're at. when we start to stroke up, it'll stick
                        // as the last point before we upticked
                        if (newPoint.X >= previousPoint.X && newPoint.Y >= previousPoint.Y)
                        {
                            midpoint = newPoint;
                        }
                        // if we're stroking up (moving right x and up y [y axis is flipped])
                        else if (newPoint.X >= previousPoint.X && newPoint.Y <= previousPoint.Y)
                        {
                            strokeUp = true;
                        }
                        // otherwise, we fail the recognizer
                        else
                        {
                            base.State = UIGestureRecognizerState.Failed;
                        }
                    }
                }
    
                Console.WriteLine(base.State.ToString());
            }
            #endregion
        }
    }
    

    State 属性更改为 RecognizedEnded 时,将调用 Reset 方法。 这是在自定义手势识别器中重置任何内部状态集的时间。 现在,该类可以在用户下次与应用程序交互时重新开始,并准备好重新尝试识别手势。

  2. 现已定义自定义手势识别器 (CheckmarkGestureRecognizer),编辑 CustomGestureViewController.cs 文件并添加以下两个实例变量:

    #region Private Variables
    private bool isChecked = false;
    private CheckmarkGestureRecognizer checkmarkGesture;
    #endregion
    
  3. 若要实例化和配置手势识别器,请将以下方法添加到控制器:

    private void WireUpCheckmarkGestureRecognizer()
    {
        // Create the recognizer
        checkmarkGesture = new CheckmarkGestureRecognizer();
    
        // Wire up the event handler
        checkmarkGesture.AddTarget(() => {
            if (checkmarkGesture.State == (UIGestureRecognizerState.Recognized | UIGestureRecognizerState.Ended))
            {
                if (isChecked)
                {
                    CheckboxImage.Image = UIImage.FromBundle("CheckBox_Unchecked.png");
                }
                else
                {
                    CheckboxImage.Image = UIImage.FromBundle("CheckBox_Checked.png");
                }
                isChecked = !isChecked;
            }
        });
    
        // Add the gesture recognizer to the view
        View.AddGestureRecognizer(checkmarkGesture);
    }
    
  4. 编辑 ViewDidLoad,以便调用 WireUpCheckmarkGestureRecognizer,如以下代码片段所示:

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();
    
        // Wire up the gesture recognizer
        WireUpCheckmarkGestureRecognizer();
    }
    
  5. 运行应用程序,并尝试在屏幕上绘制“V”。 你应当会看到显示的图像发生了更改,如以下屏幕截图所示:

    The button checked

    The button unchecked

上述三个部分演示了在 iOS 中响应触控事件的不同方法:使用触控事件、内置手势识别器或自定义手势识别器。