Xamarin.iOS 中的触摸事件和手势

了解 iOS 应用程序中的触摸事件和触摸 API 非常重要,因为它们是与设备的所有物理交互的核心。 所有触摸交互都涉及 UITouch 对象。 本文介绍如何使用 UITouch 类及其 API 来支持触摸。 稍后,我们将进一步了解我们的知识,了解如何支持手势。

启用触摸

UIKit 中的控件 – 来自 UIControl 的子类中的控件如此依赖用户交互,它们内置了 UIKit 中的手势,因此不需要启用 Touch。 它已启用。

但是,默认情况下,UIKit 中的许多视图未启用触摸。 可通过两种方法在控件上启用触摸。 第一种方法是在 iOS 设计器的属性板中选中“已启用用户交互”复选框,如以下屏幕截图所示:

Check the User Interaction Enabled checkbox in the Property Pad of the iOS Designer

我们还可以使用控制器在 UIView 类上将 UserInteractionEnabled 属性设置为 true。 如果在代码中创建 UI,则需要此操作。

以下代码行是一个示例:

imgTouchMe.UserInteractionEnabled = true;

触摸事件

当用户触摸屏幕、移动手指或删除手指时,会出现三个阶段的触摸。 这些方法在 UIResponder 中定义,这是 UIView 的基类。 iOS 将覆盖 UIView 和处理触摸的 UIViewController 上的关联方法:

  • TouchesBegan – 首次触摸屏幕时会调用此项。
  • TouchesMoved – 当用户在屏幕中滑动手指时,触摸的位置发生变化时,将调用此项。
  • TouchesEndedTouchesCancelled – 当用户的手指从屏幕上抬起时调用 TouchesEnded。 例如,如果 iOS 取消触摸,则 TouchesCancelled 会被调用。示例为用户从按钮上滑开手指来取消按下。

触摸事件在 UIView 的堆栈中以递归方式向下传输,以检查触摸事件是否位于视图对象的边界内。 这通常称为 Hit-testing。 他们将首先在最顶层的 UIViewUIViewController 上调用它们,然后在视图层次结构中下方的 UIViewUIViewControllers 上调用它们。

每次用户触摸屏幕时,都会创建一个 UITouch 对象。 UITouch 对象包括有关触摸的数据,例如触摸发生时间、发生位置、触摸是否是轻扫等。触摸事件会传递一个触摸属性 – 包含一个或多个触摸的 NSSet。 可以使用此属性获取对触摸的引用,并确定应用程序的响应。

重写其中一个触摸事件的类应首先调用基本实现,然后获取与事件关联的 UITouch 对象。 若要获取对第一个触摸的引用,请调用 AnyObject 属性并将其强制转换为 UITouch,如以下示例所示:

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 对象中的一次点击。 这使得检查双击变得像检查 TapCount 属性一样简单,如以下代码所示:

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.
        }
    }
}

多点触控

默认情况下,控件上未启用多点触控。 可以在 iOS 设计器中启用多点触控,如以下屏幕截图所示:

Multi-touch enabled in the iOS Designer

还可以通过设置 MultipleTouchEnabled 属性以编程方式设置多点触控,如以下代码行所示:

imgTouchMe.MultipleTouchEnabled = true;

若要确定触摸屏幕的手指数,请使用 UITouch 属性上的 Count 属性:

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

确定触摸位置

方法 UITouch.LocationInView 返回一个 CGPoint 对象,该对象保存给定视图中触摸的坐标。 此外,我们可以通过调用方法 Frame.Contains 来测试该位置是否在控件中。 以下代码片段演示了一个示例:

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

了解 iOS 中的触摸事件后,让我们了解手势识别器。

手势识别器

手势识别器可以大大简化和减少在应用程序中支持触摸的编程工作。 iOS 手势识别器将一系列触摸事件聚合为单个触摸事件。

Xamarin.iOS 提供类 UIGestureRecognizer 作为以下内置手势识别器的基类:

  • UITapGestureRecognizer – 用于一个或多个点击。
  • UIPinchGestureRecognizer – 捏合和分散手指。
  • UIPanGestureRecognizer – 平移或拖动。
  • UISwipeGestureRecognizer – 向任何方向轻扫。
  • UIRotationGestureRecognizer – 以顺时针或逆时针方向旋转两根手指。
  • UILongPressGestureRecognizer – 按住,有时称为长按或长点。

使用手势识别器的基本模式如下所示:

  1. 实例化手势识别器 – 首先实例化 UIGestureRecognizer 子类。 实例化的对象将由视图关联,并在释放视图时进行垃圾回收。 不必将此视图创建为类级变量。
  2. 配置任何手势设置 – 下一步是配置手势识别器。 有关可以设置为控制实例行为的 UIGestureRecognizer 属性列表,请参阅 UIGestureRecognizer 上的 Xamarin 文档及其子类。
  3. 配置目标 – 由于其 Objective-C 遗产,Xamarin.iOS 在手势识别器与手势匹配时不会引发事件。 UIGestureRecognizer 具有一种方法:AddTarget – 当手势识别器进行匹配时,它可以接受具有要执行的代码的匿名委托或 Objective-C 选择器。
  4. 启用手势识别器 – 与触摸事件一样,仅当启用触摸交互时,才会识别手势。
  5. 将手势识别器添加到视图 – 最后一步是通过调用 View.AddGestureRecognizer 向视图添加手势,并向其传递手势识别器对象。

有关如何在代码中实现手势识别器示例的详细信息,请参阅手势识别器示例

调用手势的目标时,它将传递对所发生的手势的引用。 这样,手势目标就可以获取有关所发生的手势的信息。 可用信息的范围取决于所使用的手势识别器的类型。 有关每个 UIGestureRecognizer 子类可用的数据的信息,请参阅 Xamarin 文档。

请务必记住,将手势识别器添加到视图后,视图(及其下方的任何视图)将不会收到任何触摸事件。 若要使用手势同时允许触摸事件,必须将 CancelsTouchesInView 属性设置为 false,如以下代码所示:

_tapGesture.Recognizer.CancelsTouchesInView = false;

每个 UIGestureRecognizer 都有一个 State 属性,该属性提供有关手势识别器状态的重要信息。 每次此属性的值更改时,iOS 都会调用订阅方法,为其提供更新。 如果自定义手势识别器从不更新 State 属性,则永远不会调用订阅服务器,呈现手势识别器无用。

手势可以汇总为以下两种类型之一:

  1. 离散 – 这些手势仅在首次识别它们时触发。
  2. 连续 – 只要识别出这些手势,这些手势就会继续触发。

手势识别器存在于以下状态之一:

  • 可能 – 这是所有手势识别器的初始状态。 这是 State 属性的默认值。
  • 已开始 – 当首次识别连续手势时,状态会设置为“已开始”。 这允许订阅区分手势识别的开始时间以及何时更改。
  • 已更改 – 在连续手势开始但尚未完成之后,每次触摸移动或更改时,状态都将设置为“已更改”,只要它仍在手势的预期参数内。
  • 已取消 – 如果识别器从“已开始”更改为“已更改”,然后触摸以不再适合手势模式的方式更改,则会设置此状态。
  • 已识别 - 当手势识别器与一组触摸匹配时,将设置状态,并将通知订阅者手势已完成。
  • 已结束 – 这是“已识别”状态的别名。
  • 已失败 – 当手势识别器无法再匹配它正在侦听的触摸时,状态将更改为“已失败”。

Xamarin.iOS 在 UIGestureRecognizerState 枚举中表示这些值。

使用多个手势

默认情况下,iOS 不允许默认手势同时运行。 相反,每个手势识别器都将按非确定性顺序接收触摸事件。 以下代码片段演示了如何同时运行手势识别器:

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

也可以在 iOS 中禁用手势。 有两个委托属性允许手势识别器检查应用程序的状态和当前触摸事件,以决定如何以及是否应识别手势。 这两个事件包括:

  1. ShouldReceiveTouch – 在传递触摸事件之前,将立即调用此委托,并提供检查触摸并确定手势识别器将处理哪些触摸的机会。
  2. ShouldBegin – 当识别器尝试将状态从“可能”更改为其他状态时,将调用此方法。 返回 false 将强制将手势识别器的状态更改为“已失败”。

可以使用强类型 UIGestureRecognizerDelegate、弱委托或通过事件处理程序语法绑定来替代这些方法,如以下代码片段所示:

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

最后,可以将手势识别器排入队列,以便在另一个手势识别器失败时,它才会成功。 例如,仅当双击手势识别器失败时,单个点击手势识别器才应成功。 以下代码片段提供了一个示例:

singleTapGesture.RequireGestureRecognizerToFail(doubleTapGesture);

创建自定义手势

尽管 iOS 提供了一些默认手势识别器,但在某些情况下,可能需要创建自定义手势识别器。 创建自定义手势识别器涉及以下步骤:

  1. 子类 UIGestureRecognizer
  2. 重写适当的触摸事件方法。
  3. 通过基类的 State 属性弹出识别状态。

在 iOS 中使用触控演练中将介绍此操作的实际示例。