基本概念:
- 加速计: 又称加速度计,测量设备运动的加速度。
- 加速度: 矢量,描绘速度的方向和大小变化的快慢。
- 陀螺仪: 感测与维持方向的装置。
原文: Motion Event
声明: 由于本人水平有限,翻译定有不当甚至错误之处,如有发现还望指出。
当用户移动,摇晃,或倾斜设备时,产生motion事件。这些motion事件被设备硬件检测到,就是加速计和陀螺仪。
acclerometer事实上是由3个加速计组成,X,Y,Z坐标轴各有一个。每一个测量随着时间的推移以线性的速度变化。结合这三个加速计可以让你检测到设备的任何方向上的运动,以及获得设备的当前方向。虽然有3个加速计,在本文档的其余部分是指它们作为一个单一的实体。gyroscope 测量围绕3个轴的旋转速度。
所有的motion事件产生于同样的硬件。访问该硬件数据有几种不同的方式,这取决于您的应用程序的需求:
- 如果你需要检测设备的大体方向,但是不必知道方向向量,用UIDevice类。看 Getting the Current Device Orientation with UIDevice 来获得更多的信息。
- 如果你想你的应用响应用户摇晃设备,你可以用UIKit框架中的motion-event handling methods来从传递的UIEvent对象中获得信息。看 Detecting Shake-Motion Events with UIEvent 来获得更多信息。
- 如果UIDevice和UIEvent类都不能满足需求的话,可能你希望用Core Motion框架来访问加速计,陀螺仪,以及设备运动类。看 Capturing Device Movement with Core Motion 来获得更多信息。
用UIDevice获得设备的当前方向
当你需要知道设备的大体方向,而不是精确的方向向量,用 UIDevice 类的方法。用UIDevice很简单,不需要你自己计算方法向量。
在你获得当前方向前,调用 beginGeneratingDeviceOrientationNotifications 方法,来告诉UIDevice类开启设备方向的通知,。这样会启动加速计硬件,为了节省电量加速计可能会被关闭。代码4-1在viewDidLoad方法中做了演示。
启用方向通知后,从UIDevice对象的 orientation property 获得当前方向。如果你想得到设备方向改变的通知,注册接收 UIDeviceOrientationDidChangeNotification 通知。设备方向用UIDeviceOrientation常量表示,指示设备是横屏模式,竖屏模式,屏幕朝上,还是屏幕朝下等等。这些常量指示设备的物理方向,并不一定符合你应用的用户界面方向。
当你不需要知道设备方向时,总是要调用UIDevice的 endGeneratingDeviceOrientationNotifications 来禁用方向通知。这会给系统一个禁用加速计的机会,如果加速计不在别处使用的话,这样可以保留电池电量。
Listing 4-1 Responding to changes in device orientation
1 | - (void)viewDidLoad { |
响应设备方向变换的另一个例子,看 Alternate Views 例子代码。
用UIEvent检测Shake-Motion事件
当用户摇晃设备时,iOS评估加速计数据。如果数据满足一定的标准,iOS解释摇晃手势,并创建 UIEvent 对象来表示它。然后它发送event对象给当前活跃的应用来处理。注意,你的应用可以同时响应shake-motion事件和设备方向的改变。
运动事件要比触摸事件简单。当运动开始和停止时,系统会通知应用,而不是每一次单独的运动都会通知。而且,motion事件只包括一个事件类型( UIEventTypeMotion ),事件子类型( UIEventSubtypeMotionShake ),以及一个时间戳。
为运动事件指派第一响应者
为了接收motion事件,指派一个响应者对象作为第一响应者。这是你想要处理运动事件的响应者对象。代码4-2展示了一个responder如何让自己成为第一响应者。
Listing 4-2 Becoming first responder
1 | - (BOOL)canBecomeFirstResponder { |
Motion事件使用响应者链来找到处理事件的对象。当用户开始摇晃设备时,iOS给第一响应者发送第一个运动事件。如果第一响应者不能处理事件,它沿着响应者链向上传递。看 The Responder Chain Follows a Specific Delivery Path 获得更多信息。如果shaking-motion事件沿着响应者链被传递到了window而没有被处理,并且UIApplication的 applicationSupportsShakeToEdit 属性为YES(默认),iOS显示一个带撤销和重做命令的sheet。
实现Motion-Handling方法
有三个Motion-Handling方法, motionBegan:withEvent: , motionEnded:withEvent: , and motionCancelled:withEvent: 。处理运动事件,你必须实现motionBegan:withEvent:或motionEnded:withEvent:方法,有时两者都会实现。一个响应者也应该实现motionCancelled:withEvent:方法来响应iOS取消一个运动事件。如果摇晃运动被打断或者iOS测定了运动是无效的-例如摇晃持续了太长时间,事件就会被取消。
代码4-3是从示例工程 GLPaint 中提取的。在那个应用中,用户在屏幕上绘制,并且晃动设备来擦除绘画。代码在motionEnded:withEvent:中检测是否是摇晃,如果是的话,发送通知来执行shake-to-erase功能。
Listing 4-3 Handling a motion event
1 | - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event { |
注意: 除了简单,另一个使用shake-motion事件,而不是Core Motion的原因是,当你测试和调试你的应用时可以在iOS模拟器上模拟shake-motion事件。更多关于iOS Simulator的信息,请看 Simulator User Guide 。
设置和检查运动事件所需的硬件功能
在你的应用可以访问设备相关的功能之前,如加速计数据,你必须为你的应用添加必须的功能列表。具体来说,给应用的Info.plist文件加入keys。在运行的时候,只有设备有这些必须的功能,iOS才会启动你的应用。例如,如果你的应用依赖陀螺仪数据,列出陀螺仪,那么如果设备没有一个陀螺仪则应用不会启动。App Store也会使用这个list来通知用户,避免用户下载他们不能运行的apps。
声明你的应用需要的功能,使用 adding keys to your app’s propery list 。有两个UIRequiredDeviceCapabilities keys,基于硬件的源:
- accelerometer
- gyroscope
注意: 如果你的应用只检测设备方向的改变,不必包括accelerometer key。
你可以用数组或者字典来指定key-values。如果你用数组的话,在数组中列出每一个需要的功能key。如果你用字典的话,为字典中的每一个需要的key指定一个Boolean值。这两种方法,都不需要列出不需要的功能的key。更多信息,看 UIRequiredDeviceCapabilities 。
如果你的应用使用陀螺仪数据的功能对用户体验来说不是不可或缺的,你可能也想让没有陀螺仪设备的用户下载应用。如果你没有指定陀螺仪为必须的硬件功能,但是仍然有请求陀螺仪数据的代码,你需要在运行的时候检查陀螺仪是否可用。通过 CMMotionManager 类的 gyroAvailiable 属性来检查。
用Core Motion捕获设备运动
Core Motion框架主要负责访问原始的加速计和陀螺仪数据,并将数据传递给一个应用来处理。Core Motion用独特的算法来处理收集的原始数据,以便它可以呈现更紧致的信息。这个处理发生在框架自己的线程上。
Core Motion与UIKit是截然不同的。它不与 UIEvent 模型连接,并且也不使用响应者链。代替的,Core Motion简单的将运动事件直接传递给请求它们的apps。
Core Motion事件由三个数据对象表示,每一个数据对象包含一个或多个测量:
- CMAccelerometerData 对象捕获沿空间轴的加速度。
- CMGyroData 对象捕获围绕三个空间轴的旋转速度。
- CMDeviceMotion 对象封装了几个不同的测量,包括高度,旋转速度和加速度的更加有用的测量。
CMMotionManager 类是Core Motion的中心接入点。创建一个该类的实例,指定一个更新间隔,更新开始请求,处理运动事件,并将他们传递。一个应用应该只创建一个CMMotionManager 类的实例。多个该类的实例会影响一个应用从加速计和陀螺仪接收数据的速度。
所有的Core Motion数据封装类都是 CMLogItem 的子类,CMLogItem定义了一个时间戳,以便运动数据可以被time标记并且纪录到一个文件中。一个应用的运动事件可以与之前的运动事件比较时间戳,确定两个事件真实的更新间隔。
对于每一个描述的运动数据类型, CMMotionManager 类提供了两个获得运动数据的途径:
- Pull. 一个应用请求更新开始,并定期的取样最新的运动数据测量。
- Push. 一个应用指定更新周期,并实现一个处理数据的block。然后,请求更新启动,并通过Core Motion的操作队列和block。Core Motion向block传递每一个更新,block在那个操作队列中作为一个任务执行。
Pull是大多数apps,尤其是游戏的推荐途径。它通常更有效而且需要较少的代码。Push适合数据收集的应用以及不能丢失任何一个样品测量类似应用。这两种方式都是线程安全的;用push,你的block在操作队列的线程中执行,而用pull,Core Motion从不会打断你的线程。
重要: 用Core Motion,你必须在设备上测试和调试应用。iOS模拟器不支持加速计和陀螺仪数据。
当应用处理完必要的数据时,尽快停掉运动更新。这样的话,Core Motion可以关闭运动传感器,节省电量。
选择运动事件更新周期
当你用Core Motion请求运动数据时,你指定一个更新间隔。你应该选择满足你应用的最大间隔。间隔越大,就有越少的事件传递给你的应用,这会提高电池寿命。表4-1里列出了一些常用更新频率,并解释了你可以用那个频率生成的数据做什么。很少有应用需要acceleration事件每秒100次的传递。
你可以将间隔设置为10毫秒那么小,这会以100Hz的更新速率响应,但是大多是应用有一个较大的间隔就足够用了。
使用Core Motion处理加速计事件
加速计测量随着时间推移3个轴的速度,如图4-1所示。用Core Motion,每一个运动被捕获在 CMAccelerometerData 对象中,CMAccelerometerData封装了一个CMAcceleration类型的结构体。
为了开始接收并处理加速计数据,创建一个CMMotionManager类的实例,并调用以下方法之一:
startAccelerometerUpdates -pull方式
在调用该方法后,Core Motion不断的使用加速计活动的最新测量更新CMMotionManager的 accelerometerdata 属性。然后,你定期的取样该属性,在游戏中通常是一个呈现循环。如果你采用这种轮询方式,设置更新间隔属性( accelerometerUpdate )为最大时间间隔m,Core Motion以此执行更新。startAccelerometerUpdatesToQueue:withHandler: -push方式
在调用该方法前,给 accelerometerUpdateInterval 属性赋一个更新间隔,创建一个 NSOperationQueue实例 实例,实现 CMAccelerometerHandler 类型的block来处理加速计的更新。然后,在motion manager对象上调用 startAccelerometerUpdatesToQueue:withHandler: 方法,传递操作队列和block。以指定的更新间隔,Core Motion传递最新的加速计活动样本给block,该block作为任务在队列中执行。代码4-4 阐明了这种方式。
代码4-4是从 MotionGraphs 示例项目中提取的,在那你可以细查更多的环境上下文。在该应用中,用户移动滑块来指定一个更新间隔。startUpdatesSWithSliderValue:方法用滑块值来计算心的更新间隔。然后,创建一个CMMotionManager类的实例,检查确保设备有一个加速计,并给motion manager赋一个新的更新间隔。该应用使用push方式接收加速计数据并在图上绘制。注意,它在stopUpdates方法中停止加速计更新。
代码4-4 在MotionGraphs中访问加速计数据
大专栏 Event Handling Guide for iOS(五)ne">1 | static const NSTimeInterval accelerometerMin = 0.01; |
处理旋转速度数据
一个陀螺仪测量设备沿着三个坐标的旋转速度,如图4-2所示。
每次你请求陀螺仪更新时,Core Motion有一个旋转速度的偏差估计并在 CMGyroData 对象中返回信息。CMGyroData有一个 rotationRate 属性,该属性存储一个 CMRotationRate 结构体,其捕获三个轴的旋转速度,以弧度每秒的单位。注意,由CMGyroData对象测量的旋转速度是有偏差的。用 CMDeviceMotion 类你能获得更精确,无偏差的测量。看 Handling Processed Device Motin Data 了解更多信息。
当分析旋转速度数据时 –具体来说,当分析 CMRotationMatrix 结构体的字段 –跟随右手定则来决定旋转方向,如图4-2。用右手环绕着x轴,拇指朝向正x方向,正向旋转是朝向另外四个手指的提示方向。负向旋转是手指提示的相反方向。
为了开始接收并处理rotation-rate数据,创建一个 CMMotionManager 类实例,并调用下列方法之一:
startGyroUpdates –pull方式
在调用该方法后,Core Motion不断的用最新的陀螺仪运动测量来更新CMMotionManager属性。然后,你定期的取样这些属性。如果你采用轮询方式,设置更新间隔属性( gyroUpdateInterval 为最大间隔,Core Motion以此来执行更新。startGyroUpdatesToQueue:withHandler: –push方式
调用该方法之前,给 gyroUpdateInterval 赋一个更新间隔,创建一个 NSOperationQueue实例,并实现 CMGyroHandler类型block来处理陀螺仪更新。然后,在motion-manager对象上调用 startGyroUpdatesToQueue:withHandler:方法,传递操作队列和block。以指定的更新间隔,Core Motion传递陀螺仪活动的最近样本给block,block作为一个任务在队列中执行。代码4-5阐明了这种方式。
代码4-5也是从 MotionGraphs 示例代码中提取的,并几乎和代码4-4一样。该应用使用push方式接收陀螺仪数据以便在屏幕上绘制数据。
代码4-5 在MotionGraphs中访问陀螺仪数据
1 | static const NSTimeInterval gyroMin = 0.01; |
处理经过Device Motion处理过的数据
如果设备既有加速计又有陀螺仪,Core Motion提供一个device-motion服务,它处理来自这两个传感器的原始运动数据。device motion使用传感器融合算法来提炼原始数据并产生用于设备方向,无偏差旋转率,设备的重力方向,以及用户产生的加速度信息。一个 CMDeviceMotion 类的实例封装了这些数据。另外,你不必过虑加速度数据,因为device motion分离重力和用户加速度。
你可以通过CMDeviceMotion对象的 attitude 属性访问姿态数据,它封装了一个CMAttitude对象。每一个 CMAttitude 类的实例封装了attitude的三个数学表示:
- 一个四元组
- 一个旋转矩阵
- 三个欧拉角(roll,pitch,and yaw)
为了开始接收并处理device-motion更新,创建一个 CMMotionManager 类实例并调用以下两个方法之一:
- startDeviceMotionUpdates –pull方式
调用该方法后,Core Motion不断的用加速计和陀螺仪活动的最新提炼的测量更新CMMotionManager的 deviceMotion 属性,封装在 CMDeviceMotion对象中。然后,你定期的取样该属性。如果你采用轮询方式,设置更新间隔属性( deviceMotionUpdateInterval )为最大的间隔,Core Motion以此来执行更新。
代码4-6说明了该方式
- startDeviceMotionUpdatesToQueue:withHandler: –push方式
调用该方法前,为 deviceMotionUpdateInterval 属性赋值一个更新间隔,创建一个 NSOperationQueue 实例,并实现 CMDeviceMotionHandler 类型的block来处理加速计的更新。然后,调用 startDeviceMotionUpdatesToQueue:withHandler: 方法,传递操作队列和block。以指定的更新间隔,Core Motion向block传递结合了加速计和陀螺仪运动的最新样本,由 CMDeviceMotion 对象表示,block在该队列中作为任务执行。
代码4-6使用 pARk 示例项目中的代码来解释说明如何开启和停止device motion更新。startDeviceMotion方法使用pull方式用一个reference frame开始设备更新。看 Device Attitude and the Reference Frame 来获得更多关于device motion reference frames的信息。
代码4-6 开启和停止device motion更新
1 | - (void)startDeviceMotion { |
设备姿态和参考系
一个CMDeviceMotion对象包含设备的attitude信息,或空间上的方向。设备attitude总是被测量在相关的reference frame。当你的应用开始device-motion更新时,Core Motion 建立reference frame。然后, CMAttitude 给设备当前的reference frame一个来自于最初的reference frame的旋转。
在Core Motion reference frame中,z轴总是垂直的,x和y轴与重力总是直角,这使得重力向量为[0,0,-1]。该向量也被称为gravity reference。如果你将来自于CMAttitude对象的旋转矩阵与gravity reference相乘,就可以得到在设备frame上的重力。或者,算术地:
你可以改变CMAttitude用的参考系。通过缓存包含reference frame的attitude对象并将它作为参数传递给 multiplyByInverseOfAttitude: 。该attitude参数接收消息改变以便它可以呈现从传入的reference frame的attitude的转变。
大多数应用对设备姿态感兴趣。为了了解这可能是多么有用,考虑棒球游戏,用户旋转设备来挥舞。通常情况下,在球场的起点,棒球棒处在某个静止方向。之后,棒球棒基于设备姿态相对于球场开始时的改变呈现。代码4-7说明了你能怎样做这些。
代码4-7 在呈现之前获得姿态的改变
1 | -(void) startPitch { |
这该例子中, multiplyByInverseOfAttitude: 方法返回之后,currentAttitude表示从referenceAttitude到最近采样的 CMAttitude 实例的变化。