Xamarin 中的触摸事件和手势Touch Events and Gestures in Xamarin.iOS

在 iOS 应用程序中了解触摸事件和触控 Api,这一点很重要,因为它们是与设备的所有物理交互的核心。It is important to understand the touch events and touch APIs in an iOS application, as they are central to all physical interactions with the device. 所有触摸交互都涉及一个 UITouch 对象。All touch interactions involve a UITouch object. 在本文中,我们将了解如何使用 UITouch 类及其 Api 来支持触摸。In this article we will learn how to use the UITouch class and its APIs to support touch. 稍后,我们将在知识中展开以了解如何支持手势。Later, we will expand on our knowledge to learn how to support gestures.

启用触控Enabling Touch

UIKit 中的控件-来自 UIControl 的子类–依赖于用户交互,它们具有 UIKit 内置的手势,因此不需要启用 Touch。Controls in UIKit – those subclassed from UIControl – are so dependant on user interaction that they have gestures built in to UIKit and therefore it is not necessary to enable Touch. 已启用该功能。It is already enabled.

但是,UIKit 中的许多视图默认情况下未启用触摸。However, many of the views in UIKit do not have touch enabled by default. 可以通过两种方法在控件上启用触摸。There are two ways to enable touch on a control. 第一种方法是在 iOS 设计器的属性板中检查 "已启用用户交互" 复选框,如以下屏幕截图所示:The first way is to check the User Interaction Enabled checkbox in the Property Pad of the iOS Designer, as shown in the following screenshot:

对于 UIView 类,还可以使用控制器将 UserInteractionEnabled 属性设置为 true。We can also use a controller to set the UserInteractionEnabled property to true on a UIView class. 如果 UI 是在代码中创建的,则此项是必需的。This is required if the UI is created in code.

下面的代码行是一个示例:The following line of code is an example:

imgTouchMe.UserInteractionEnabled = true;

触摸事件Touch Events

用户触摸屏幕、移动手指或删除手指时,会出现三个触摸阶段。There are three phases of touch that occur when the user touches the screen, moves their finger, or removes their finger. 这些方法是在 UIResponder中定义的,这是 UIView 的基类。These methods are defined in UIResponder, which is the base class for UIView. iOS 将覆盖 UIView 上的关联方法和 UIViewController 来处理触控:iOS will override the associated methods on the UIView and the UIViewController to handle touch:

  • TouchesBegan –第一次接触屏幕时,将调用此。TouchesBegan – This is called when the screen is first touched.
  • TouchesMoved –当触摸位置随着用户在屏幕上滑动手指时,会调用此方法。TouchesMoved – This is called when the location of the touch changes as the user is sliding their fingers around the screen.
  • TouchesEndedTouchesCancelled –当用户的手指从屏幕上提起时,将调用 TouchesEndedTouchesEnded or TouchesCancelledTouchesEnded is called when the user’s fingers are lifted off the screen. 如果 iOS 取消了触摸,则会调用 TouchesCancelled,例如,如果用户从按钮滑开按钮以取消按下。TouchesCancelled gets called if iOS cancels the touch – for example, if a user slides his or her finger away from a button to cancel a press.

触摸事件在 UIViews 堆栈中以递归方式传播,以检查触控事件是否在视图对象的边界内。Touch events travel recursively down through the stack of UIViews, to check if the touch event is within the bounds of a view object. 这通常称为_命中测试_。This is often called Hit-testing. 首先在最顶层的 UIViewUIViewController 调用它们,然后在 UIView 上调用,然后在视图层次结构中的下面 UIViewControllersThey will first be called on the topmost UIView or UIViewController and then will be called on the UIView and UIViewControllers below them in the view hierarchy.

用户每次触摸屏幕时都会创建一个 UITouch 对象。A UITouch object will be created each time the user touches the screen. "UITouch" 对象包括有关触摸数据的数据,例如触摸发生时、触摸屏发生的位置、触摸屏输入点等。触摸事件传递了触摸属性–包含一个或多个触控的 NSSetThe UITouch object includes data about the touch, such as when the touch occurred, where it occurred, if the touch was a swipe, etc. The touch events get passed a touches property – an NSSet containing one or more touches. 我们可以使用此属性来获取对触控的引用,并确定应用程序的响应。We can use this property to obtain a reference to a touch, and determine the application’s response.

重写某个触摸事件的类应首先调用基实现,然后获取与该事件关联的 UITouch 对象。Classes that override one of the touch events should first call the base implementation and then get the UITouch object associated with the event. 若要获取对第一个触控的引用,请调用 AnyObject 属性,并将其转换为 UITouch,如以下示例中所示:To obtain a reference to the first touch, call the AnyObject property and cast it as a UITouch as show in the following example:

public override void TouchesBegan (NSSet touches, UIEvent evt)
{
    base.TouchesBegan (touches, evt);
    UITouch touch = touches.AnyObject as UITouch;
    if (touch != null)
    {
        //code here to handle touch
    }
}

iOS 自动识别屏幕上的连续快速触控,并在单个 UITouch 对象中一点击即可将其全部收集。iOS automatically recognizes successive quick touches on the screen and will collect them all as one tap in a single UITouch object. 这样就可以像检查 TapCount 属性一样轻松地进行双击,如以下代码所示:This makes checking for a double tap as easy as checking the TapCount property, as illustrated in the following code:

public override void TouchesBegan (NSSet touches, UIEvent evt)
{
    base.TouchesBegan (touches, evt);
    UITouch touch = touches.AnyObject as UITouch;
    if (touch != null)
    {
        if (touch.TapCount == 2)
        {
            // do something with the double touch.
        }
    }
}

多点触控Multi-Touch

默认情况下,在控件上未启用多点触控。Multi-touch is not enabled by default on controls. 可以在 iOS 设计器中启用多点触控,如以下屏幕截图所示:Multi-touch can be enabled in the iOS Designer, as illustrated by the following screenshot:

还可以通过设置 MultipleTouchEnabled 属性以编程方式设置多点触控,如以下代码行所示:It is also possible to set multi-touch programmatically by setting the MultipleTouchEnabled property as shown in the following line of code:

imgTouchMe.MultipleTouchEnabled = true;

若要确定触摸屏幕的手指数量,请使用 UITouch 属性的 Count 属性:To determine how many fingers touched the screen, use the Count property on the UITouch property:

public override void TouchesBegan (NSSet touches, UIEvent evt)
{
    base.TouchesBegan (touches, evt);
    lblNumberOfFingers.Text = "Number of fingers: " + touches.Count.ToString();
}

确定触摸位置Determining Touch Location

方法 UITouch.LocationInView 返回一个 CGPoint 对象,该对象在给定视图中保存触摸的坐标。The method UITouch.LocationInView returns a CGPoint object that holds the coordinates of the touch within a given view. 此外,我们还可以通过调用方法 Frame.Contains来测试该位置是否在控件内。Additionally, we can test to see if that location is within a control by calling the method Frame.Contains. 下面的代码片段演示了一个示例:The following code snippet shows an example of this:

if (this.imgTouchMe.Frame.Contains (touch.LocationInView (this.View)))
{
    // the touch event happened inside the UIView imgTouchMe.
}

现在我们已经了解了 iOS 中的触摸事件,接下来让我们了解手势识别器。Now that we have an understanding of the touch events in iOS, let’s learn about gesture recognizers.

手势识别器Gesture Recognizers

手势识别器可以极大地简化和减少在应用程序中支持触控的编程工作量。Gesture recognizers can greatly simplify and reduce the programming effort to support touch in an application. iOS 手势识别器将一系列触摸事件聚合为单个触控事件。iOS gesture recognizers aggregate a series of touch events into a single touch event.

Xamarin 提供类 UIGestureRecognizer 作为以下内置笔势识别器的基类:Xamarin.iOS provides the class UIGestureRecognizer as a base class for the following built-in gesture recognizers:

  • UITapGestureRecognizer –这适用于一个或多个点击。UITapGestureRecognizer – This is for one or more taps.
  • UIPinchGestureRecognizer –收缩和散布。UIPinchGestureRecognizer – Pinching and spreading apart fingers.
  • UIPanGestureRecognizer –平移或拖动。UIPanGestureRecognizer – Panning or dragging.
  • UISwipeGestureRecognizer -沿任意方向轻扫。UISwipeGestureRecognizer – Swiping in any direction.
  • UIRotationGestureRecognizer –顺时针或逆时针运动旋转两根手指。UIRotationGestureRecognizer – Rotating two fingers in a clockwise or counter-clockwise motion.
  • UILongPressGestureRecognizer –按下并保持,有时称为长按下或长时间单击。UILongPressGestureRecognizer – Press and hold, sometimes referred to as a long-press or long-click.

使用手势识别器的基本模式如下所示:The basic pattern to using a gesture recognizer is as follows:

  1. 实例化手势识别器–首先实例化 UIGestureRecognizer 子类。Instantiate the gesture recognizer – First, instantiate a UIGestureRecognizer subclass. 实例化的对象将由视图关联,并将在处理视图时进行垃圾收集。The object that is instantiated will be associated by a view and will be garbage collected when the view is disposed of. 不需要将此视图创建为类级别的变量。It is not necessary to create this view as a class level variable.
  2. 配置任何手势设置–下一步是配置笔势识别器。Configure any gesture settings – The next step is to configure the gesture recognizer. 有关可设置为控制 UIGestureRecognizer 实例行为的属性列表,请查阅 Xamarin 的有关 UIGestureRecognizer 及其子类的文档。Consult Xamarin’s documentation on UIGestureRecognizer and its subclasses for a list of properties that can be set to control the behavior of a UIGestureRecognizer instance.
  3. 配置目标–由于其目标为 C 的遗产,因此当手势识别器匹配笔势时,Xamarin 不会引发事件。Configure the target – Because of its Objective-C heritage, Xamarin.iOS doesn’t raise events when a gesture recognizer matches a gesture. UIGestureRecognizer 具有方法– AddTarget –可以接受匿名委托,或使用在笔势识别器进行匹配时要执行的代码。UIGestureRecognizer has a method – AddTarget – that can accept an anonymous delegate or an Objective-C selector with the code to execute when the gesture recognizer makes a match.
  4. 启用手势识别器–与使用触控事件一样,仅当启用触控交互时才会识别手势。Enable gesture recognizer – Just like with touch events, gestures are only recognized if touch interactions are enabled.
  5. 视图添加手势识别器–最后一步是通过调用 View.AddGestureRecognizer 将手势添加到视图,并向其传递笔势识别器对象。Add the gesture recognizer to the view – The final step is to add the gesture to a view by calling View.AddGestureRecognizer , and passing it a gesture recognizer object.

有关如何在代码中实现它们的详细信息,请参阅手势识别器示例Refer to the gesture recognizer samples for more information on how to implement them in code.

调用该笔势的目标后,将向其传递对所发生的手势的引用。When the gesture’s target is called, it will be passed a reference to the gesture that occurred. 这允许该笔势目标获取有关所发生的手势的信息。This allows the gesture target to obtain information about the gesture that occurred. 可用的信息范围取决于所使用的笔势识别器的类型。The extent of information available depends on the type of gesture recognizer that was used. 有关可用于每个 UIGestureRecognizer 子类的数据的信息,请参阅 Xamarin 的文档。Please see Xamarin’s documentation for information about the data available for each UIGestureRecognizer subclass.

请记住,在将笔势识别器添加到视图后,视图(以及它下面的任何视图)不会收到任何触控事件。It’s important to remember that once a gesture recognizer has been added to a view, the view (and any views below it) will not receive any touch events. 若要允许同时使用触控事件和笔势,则 CancelsTouchesInView 属性必须设置为 false,如以下代码所示:To allow touch events simultaneously with gestures, the CancelsTouchesInView property must be set to false, as illustrated by the following code:

_tapGesture.Recognizer.CancelsTouchesInView = false;

每个 UIGestureRecognizer 都有一个 State 属性,该属性提供有关笔势识别器状态的重要信息。Each UIGestureRecognizer has a State property that provides important information about the status of the gesture recognizer. 此属性的值每次发生更改时,iOS 将调用订阅方法来提供更新。Every time the value of this property changes, iOS will call the subscribing method giving it an update. 如果自定义手势识别器从不更新 State 属性,则永远不会调用该订阅服务器,因此,呈现手势识别器毫无用处。If a custom gesture recognizer never updates the State property, the subscriber is never called, rendering the gesture recognizer useless.

手势可以汇总为以下两种类型之一:Gestures can be summarized as one of two types:

  1. 离散–这些手势仅在首次识别时才会激发。Discrete – These gestures only fire the first time they are recognized.
  2. 连续–只要识别这些手势,它们就会继续激发。Continuous – These gestures continue to fire as long as they are recognized.

笔势识别器存在于以下状态之一:Gesture recognizers exists in one of the following states:

  • 可能–这是所有手势识别器的初始状态。Possible – This is the initial state of all gesture recognizers. 这是 State 属性的默认值。This is the default value the State property.
  • 开始–首次识别连续手势时,状态将设置为 "已开始"。Began – When a continuous gesture is first recognized, the state is set to Began. 这使订阅可以区分手势识别开始的时间和更改时间。This allows subscribes to differentiate between when gesture recognition starts and when it is changed.
  • Changed –当连续手势开始但尚未完成后,每次触摸移动或更改时,状态将设置为 "已更改",前提是该动作仍处于笔势的预期参数内。Changed – After a continuous gesture has begun, but hasn’t finished, the state will be set to Changed every time a touch moves or changes, as long as it’s still within the expected parameters of the gesture.
  • 取消–如果识别器开始发生更改,则会设置此状态,然后,所做的更改将在中进行更改,使其不再适合手势的模式。Cancelled – This state will be set if the recognizer went from Began to Changed, and then the touches changed in such a way as to no longer fit the pattern of the gesture.
  • 识别–当手势识别器匹配一组触摸时,将设置状态,并通知订阅者该笔势已完成。Recognized – The state will be set when the gesture recognizer matches a set of touches and will inform the subscriber that the gesture has finished.
  • 结束–这是可识别状态的别名。Ended – This is an alias for the Recognized state.
  • Failed –当手势识别器不能再与它正在侦听的触控不匹配时,状态将更改为 "已失败"。Failed – When the gesture recognizer can no longer match the touches it is listening for, the state will changed to Failed.

Xamarin 在 UIGestureRecognizerState 枚举中表示这些值。Xamarin.iOS represents these values in the UIGestureRecognizerState enumeration.

使用多个手势Working with Multiple Gestures

默认情况下,iOS 不允许同时运行默认手势。By default, iOS does not allow default gestures to run simultaneously. 相反,每个手势识别器将按不确定的顺序接收触控事件。Instead, each gesture recognizer will receive touch events in a non-deterministic order. 下面的代码片段演示了如何使手势识别器同时运行:The following code snippet illustrated how to make a gesture recognizer run simultaneously:

gesture.ShouldRecognizeSimultaneously += (UIGestureRecognizer r) => { return true; };

还可以在 iOS 中禁用手势。It is also possible to disable a gesture in iOS. 可以使用两个委托属性来检查应用程序的状态和当前的触摸事件,以决定是否应识别笔势。There are two delegate properties that allow a gesture recognizer to examine the state of an application and the current touch events, to make decisions on how and if a gesture should be recognized. 这两个事件是:The two events are:

  1. ShouldReceiveTouch –在手势识别器传递触控事件之前,将调用此委托,并提供检查触控并决定手势识别器将处理哪些触摸的机会。ShouldReceiveTouch – This delegate is called right before the gesture recognizer is passed a touch event, and provides an opportunity to examine the touches and decide which touches will be handled by the gesture recognizer.
  2. ShouldBegin –在识别器尝试将状态从可能更改为其他状态时调用。ShouldBegin – This is called when a recognizer attempts to change state from Possible to some other state. 如果返回 false,则会强制将手势识别器的状态更改为 "已失败"。Returning false will force the state of the gesture recognizer to be changed to Failed.

您可以使用强类型的 UIGestureRecognizerDelegate、弱委托或通过事件处理程序语法进行绑定来重写这些方法,如以下代码片段所示:You can override these methods with a strongly typed UIGestureRecognizerDelegate, a weak delegate, or bind via the event handler syntax, as illustrated by the following code snippet:

gesture.ShouldReceiveTouch += (UIGestureRecognizer r, UITouch t) => { return true; };

最后,可以将手势识别器排队,以便仅当另一个手势识别器失败时才会成功。Finally, it is possible to queue up a gesture recognizer so that it will only succeed if another gesture recognizer fails. 例如,只有在双击手势识别器失败时,单点击手势识别器才会成功。For example, a single tap gesture recognizer should only succeed when a double tap gesture recognizer fails. 以下代码片段提供了一个示例:The following code snippet provides an example of this:

singleTapGesture.RequireGestureRecognizerToFail(doubleTapGesture);

创建自定义手势Creating a Custom Gesture

尽管 iOS 提供了一些默认的手势识别器,但在某些情况下可能需要创建自定义手势识别器。Although iOS provides some default gesture recognizers, it may be necessary to create custom gesture recognizers in certain cases. 创建自定义手势识别器涉及以下步骤:Creating a custom gesture recognizer involves the following steps:

  1. 子类 UIGestureRecognizerSubclass UIGestureRecognizer .
  2. 重写相应的触摸事件方法。Override the appropriate touch event methods.
  3. 通过基类的状态属性来向上冒泡识别状态。Bubble up recognition status via the base class’ State property.

使用 iOS演练中介绍了这种情况的一个实际示例。A practical example of this will be covered in the Using Touch in iOS walkthrough.