演练:在 Xamarin.iOS 中使用触控
本演练演示如何编写响应不同类型的触控事件的代码。 每个示例都包含在单独的屏幕中:
- 触控示例 – 如何响应触控事件。
- 手势识别器示例 – 如何使用内置手势识别器。
- 自定义手势识别器示例 – 如何生成自定义手势识别器。
每个部分都包含从头开始编写代码的说明。
请按照以下说明将代码添加到情节提要,并了解 iOS 中可用的不同类型的触控事件。
触控示例
在此示例中,我们将演示一些触控 API。 请按照以下步骤添加实现触控事件所需的代码:
打开项目 Touch_Start。 首先运行项目,以确保一切正常,然后触摸“触控示例”按钮。 应该会出现类似下面的屏幕(尽管没有一个按钮可以正常工作):
编辑文件 TouchViewController.cs 并将以下两个实例变量添加到类
TouchViewController
:#region Private Variables private bool imageHighlighted = false; private bool touchStartedInside; #endregion
实现
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
。- 在 TouchImage 内 – 在标签中显示文本
当用户将手指从屏幕上移开,或者 iOS 取消了触控事件时,我们需要进行相应的处理。 为此,我们将实现
TouchesEnded
和TouchesCancelled
,如下所示: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
。此时,“触控示例”屏幕已完成。 请注意,当你与每张图片交互时,屏幕会如何变化,如以下屏幕截图所示:
手势识别器示例
上一部分演示了如何使用触控事件在屏幕上拖动对象。 在本部分中,我们将删除触控事件,并演示如何使用以下手势识别器:
UIPanGestureRecognizer
用于在屏幕上拖动图像。UITapGestureRecognizer
用于响应屏幕上的双击。
请按照以下步骤实现手势识别器:
编辑文件 GestureViewController.cs 并添加以下实例变量:
#region Private Variables private bool imageHighlighted = false; private RectangleF originalImageFrame = RectangleF.Empty; #endregion
我们需要此实例变量来跟踪图像的先前位置。 平移手势识别器将使用
originalImageFrame
值来计算在屏幕上重新绘制图像所需的偏移量。将以下方法添加到控制器:
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
的形式为手势分配目标 – 下一步中提供了此方法。若要实现 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; } }
上面的代码将首先检查手势识别器的状态,然后在屏幕上移动图像。 有了此代码,控制器现在可以支持在屏幕上拖动图像。
添加一个
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
作为目标而不是委托。我们需要做的最后一件事是修改
ViewDidLoad
,以便调用刚刚添加的方法。 更改 ViewDidLoad,使其类似于以下代码:public override void ViewDidLoad() { base.ViewDidLoad(); Title = "Gesture Recognizers"; // Save initial state originalImageFrame = DragImage.Frame; WireUpTapGestureRecognizer(); WireUpDragGestureRecognizer(); }
另请注意,我们初始化
originalImageFrame
的值。运行应用程序,并与两个图像进行交互。 以下屏幕截图是这些交互的一个示例:
自定义手势识别器
在本部分中,我们将应用前面部分中的概念来生成自定义手势识别器。 自定义手势识别器将创建 UIGestureRecognizer
的子类,并在用户在屏幕上绘制“V”时识别,然后切换位图。 以下屏幕截图是此屏幕的示例:
请按照以下步骤创建自定义手势识别器:
向名为
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
属性更改为Recognized
或Ended
时,将调用 Reset 方法。 这是在自定义手势识别器中重置任何内部状态集的时间。 现在,该类可以在用户下次与应用程序交互时重新开始,并准备好重新尝试识别手势。现已定义自定义手势识别器 (
CheckmarkGestureRecognizer
),编辑 CustomGestureViewController.cs 文件并添加以下两个实例变量:#region Private Variables private bool isChecked = false; private CheckmarkGestureRecognizer checkmarkGesture; #endregion
若要实例化和配置手势识别器,请将以下方法添加到控制器:
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); }
编辑
ViewDidLoad
,以便调用WireUpCheckmarkGestureRecognizer
,如以下代码片段所示:public override void ViewDidLoad() { base.ViewDidLoad(); // Wire up the gesture recognizer WireUpCheckmarkGestureRecognizer(); }
运行应用程序,并尝试在屏幕上绘制“V”。 你应当会看到显示的图像发生了更改,如以下屏幕截图所示:
上述三个部分演示了在 iOS 中响应触控事件的不同方法:使用触控事件、内置手势识别器或自定义手势识别器。