美文网首页设备屏幕
Core Motion 的那些事

Core Motion 的那些事

作者: xiao333ma | 来源:发表于2017-04-09 17:06 被阅读212次

    获取设备的移动事件

    当我们移动,晃动,或者倾斜手机的时候,这些动作都会被设备的硬件捕获。其实每一个动都都会在 X, Y, Z 三个方向上产生速度上的变化。根据不同的变化我们可以检测出来设备的朝向和移动。要检测设备的朝向和移动,我们有三种方法:

    • 利用 UIDevice,我们可以获取设备的大致方向,例如屏幕朝上还是朝下。
    • 利用 UIKit 中的 UIEvent,我们可以获取设备的晃动
    • 利用 Core Motion 来精确的获取设备在 X Y Z 三个轴上的变化。这个是最精确的。

    使用 UIDevice 来获取设备的方向

    首先我们应该告诉 UIDevice 来检测设备方向变化,然后我们就可以在 UIDevice 中的 orientation 属性得到设备方向的变化。

    typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
        UIDeviceOrientationUnknown,
        UIDeviceOrientationPortrait,            // Device oriented vertically, home button on the bottom
        UIDeviceOrientationPortraitUpsideDown,  // Device oriented vertically, home button on the top
        UIDeviceOrientationLandscapeLeft,       // Device oriented horizontally, home button on the right
        UIDeviceOrientationLandscapeRight,      // Device oriented horizontally, home button on the left
        UIDeviceOrientationFaceUp,              // Device oriented flat, face up
        UIDeviceOrientationFaceDown             // Device oriented flat, face down
    } __TVOS_PROHIBITED;
    

    如果你想检测设备的方向变化,那么你应该监听 UIDeviceOrientationDidChangeNotification 通知,系统会在设备方向发生变化的时候发出该通知。

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
        [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
        NSLog(@"%ld",[UIDevice currentDevice].orientation);
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cheange:) name:UIDeviceOrientationDidChangeNotification object:nil];
    }
    
    - (void)cheange:(NSNotification *)notification {
        UIDevice *device = notification.object;
        NSLog(@"%ld", device.orientation);
    }
    

    为了延长电池的使用寿命,我们应该在不使用的时候告诉 UIDevice 来关闭加速计。

    - (void)viewDidDisappear:(BOOL)animated {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
        [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
    }
    

    使用 UIEvent 来获取设备摇晃事件

    当用户晃动设备的时候,iOS 系统会评估加速计的数据,当这些数据特征符合摇晃的时候,系统会就会认为用户在摇晃设备,然后会创建一个 UIEvent 对象来发送个宿主 APP,让其处理。
    移动事件和点击事件很像。系统会告诉 APP 一个运动的开始和结束,但并不是每一个动作发生时都会这么做。
    检测晃动超级简单。晃动这个是可以在模拟器中试验的。

    - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
        NSLog(@"shake");
    }
    
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
        NSLog(@"shake");
    }
    
    shake

    设置并检查硬件是否支持设备移动事件

    如果你需要获取加速计数据,你应该在 Info.plist 中添加相应的键值对,如果在一个没有陀螺仪的设备上打开需要陀螺仪的应用,那么将会崩溃。App Store 也会根据你所需要的硬件能力来提醒用户来是否要下载一个打不开的 APP。

    如果你只是获取设备的大致方向,可以不用添加

    使用 Core Motion 来捕获设备的移动

    Core Motion 框架提供加速计和陀螺仪的原始数据给 APP 处理,它使用特别的算法来处理收集到的数据,精确度更高,而且是在自己的线程中处理。

    Core Motion 和 UIKit 是不同的,它和 UIEvent 无关,也不随着响应链传递,它只是直接的交付数据。
    Core Motion 所产生的数据有三类

    • CMAltimeter 高度计
    • CMPedometer 计步器
    • CMAccelerometerData 捕获每一个轴上的加速度
    • CMGyroData 捕获三个轴上的旋转率
      *CMDeviceMotion 包含了几个不同的测量,数据更加精确
      一般来说,一个 APP 只需要创建一个 CMMotionManager 对象,设置其更新间隔,请求数据,然后处理,每一个数据类都是 CMLogItem 的子类,所有的数据都会被打上时间的标记并且可以输出到日志文件中。
      CMMotionManager 有两种获取数据的方法,pull push
    • pull APP 主动去请求数据更新
    • push APP 调用加速计后,实现回调的 block,当加速计有数据更新的时候,就回去调用这个 block,然后在其中处理相应的数据即可
      一般来说使用 pull 更为方便,因为它的代码更少,用到了再去取相应的数据。
      不用的时候,记得关闭传感器,这样更加省电。

    选择一个合适的更新时间

    当你使用 Core Motion 的时候,你需要指定一个更新间隔。这个时间应该是你的 APP 所能接收的最大间隔,因为这样,可以提升电池的续航。下边这个表格提供了常用的更新频率,很少有 APP 需要每秒钟获取 100 次的加速计数据。

    事件频率(HZ) 用途
    10 - 20 确定设备的当前朝向
    30 - 60 游戏或者以设备加速度作为实时的用户输入数据
    70 - 100 适用于 APP 需要高频率的运动事件,例如,可以根据这个来判断用户快速点击和摇晃设备

    使用高度计 CMAltimeter

    在手机中,一般使用的是气压高度计,根据当前位置的大气压,获取当前位置的高度。具体公式为

    z=cT log(P0/P)
    c 为常数,T 为绝对温度,P 为在高度 z 处的气压,P0 为在海平面处的大气压,常数 c 与重力加速度和空气的摩尔质量相关

    我们知道,当高度上升12米,气压计中的汞柱下降1mm,标准大气压下是760mm的汞柱,一百帕相当于 3/4 mm的汞柱 我们可以得到简单的计算公式

    当前高度 =(760 - 当前大气压 * 3 / 4)*12

    我们使用 + isRelativeAltitudeAvailable; 来判断高度计是否可用,使用 - startRelativeAltitudeUpdatesToQueue: withHandler: 来获得当前高度,在这个回调 block 中,我们会得到一个 CMAltitudeData 对象,该对象有两个属性,一个是当前位置的大气压,单位是 kPa,一个是当前位置,相对于起始测量位置的高度。例如,当你刚开始测量的时候,该值为 0 ,当你拿着设备运动的时候,这个值会返回你当前位置,相对于起始位置的高度,具体高度,可以根据当前位置的压强计算出来,但如果遇到气压波动比较大的位置,高度误差,还是相当大的。

    使用计步器 CMPedometer

    当人携带者设备走动的时候,会产生一定的频率,不同的运动产生的频率是不一样的,或走,或坐,或跑,根据不同的频率滤波,识别,最终可以大致确定设备持有者的运动状态。
    在 iOS 8 以后,我们使用 CMPedometer ,而在 iOS 7 - iOS 8 我们使用 CMStepCounter 来获取数据
    在 CMPedometer, 我们可以获取一下数据

    1. 步数
    2. 距离
    3. 楼层变化
    4. 速度
    5. 节奏
    6. 运动的开始暂停

    当你开始调用的时候,即使应用被压到后台,系统还是可以记录步数,当你再次返回的时候,系统会把这段时间积累的步数全部返回给你。我们可以使用系统提供的 API 来查询某一段时间的运动数据,但是,系统最多只为我们保存 7 天。

    //从某个时间开始记录步数
    -startPedometerUpdatesFromDate: withHandler             
    //查询某个时间的段的数据
    -queryPedometerDataFromDate:toDate:withHandler
    //停止记录步数
    -stopPedometerUpdates
    

    使用 Core Motion 获取加速计数据

    加速计测量了三个轴的速度改变。在 Core Motion 中,每一个运动都被捕获进一个 CMAccelerometerData 对象,而这个对象包含了一个 CMAcceleration 结构体。


    acceleration_axes.png

    首先创建一个 CMMotionManager 对象

    • 使用 pull 方式获取数据,当调用 startAccelerometerUpdates 方法后,Core Motion 会用最新的测量数据更新 CMMotionManager 类的 accelerometerData 属性,之后你可以在一个循环中或者定时器中获取 accelerometerData 来判断设备的运动。
    • 使用 push 方式来获取数据,当调用 startAccelerometerUpdatesToQueue:withHandler: 当数据更新的时候,就会自动调用 handler,在这个 block 中,你可以处理 accelerometerData
        self.mManager = [[CMMotionManager alloc] init];
        if (self.mManager.accelerometerAvailable) {
            NSLog(@"available");
        }
        self.mManager.accelerometerUpdateInterval = 1;
        // pull
        [self.mManager startAccelerometerUpdates];
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"%f", self.mManager.accelerometerData.acceleration.x);
        }];
        
        // push
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        queue.name = @"accelerometer";
        [self.mManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) {
            NSLog(@"x : %f", accelerometerData.acceleration.x);
            NSLog(@"y : %f", accelerometerData.acceleration.y);
            NSLog(@"z : %f", accelerometerData.acceleration.z);
        }];
        // stop 
        - (void)stopUpdates {
    
         if ([self.mManager isAccelerometerActive] == YES) {
              [self.mManager stopAccelerometerUpdates];
         }
    }
    

    处理陀螺仪数据

    角速度传感器,测量物体的偏转,角速度。在手机上,单凭加速器是无法构建完整的 3D 动作,加上陀螺仪,可以让我们清晰感测到手机的旋转。 陀螺仪测量设备绕着 X Y Z 三个轴的自传速率

    陀螺仪

    当你请求陀螺仪数据更新的时候,Core Motion 返回的数据是有误差的。它会返回一个 CMGyroData 对象,该对象中有一个 rotationRate 属性,它是一个 CMRotationRate 结构体,里边包含了三个方向上的自传速率,单位是弧度每秒。 用 CMGyroData 是有误差的,如果想获取更为精确是自传速率,可以使用 CMDeviceMotion 。
    其更新数据方式也是有两种,分为 startGyroUpdates pull 和 startGyroUpdatesToQueue:withHandler: push,具体方式和加速计获取方式一样,取的数据在 CMMotionManager 的 gyroData 属性中,同样,你也需要设置更新的时间间隔。
    代码就不贴了,和上边大同小异。

    旋转方向
    旋转方向遵循右手定则,右手握拳,大拇指指向某一个轴,如果旋转的方向和四个手指的方向一样的话,即为正,否则就为负

    处理设备数据

    如果设备上既有加速计又有陀螺仪的话,Core Motion 提供了一个 device-motion 的服务用来处理两个传感器的原始数据。使用这个,可以得到最精确的数据。
    你可以访问这个 CMDeviceMotion 对象的 attitude 属性,每一个 CMAttitude 对象包含了下边三种数据

    1. 一个四元组
    2. 一个旋转率矩阵
    3. 欧拉角

    调用方式和上边测量加速度发方式类似,代码就不贴了。

    关于欧拉角

    描述一个物体的旋转,往往是最难的。欧拉角是表达旋转的最简单的一种方式,形式上它是一个三维向量,其值分别代表物体绕坐标系三个轴(x,y,z轴)的旋转角度。

    pitch.gif roll
    yaw.gif

    但是,这么描述的话,会有一个问题,那就是万向锁。

    万向锁.jpg

    欧拉旋转可以靠这种顺序让一个物体指到任何一个想要的方向,但如果在旋转中不幸让某些坐标轴重合了就会发生万向节锁,这时就会丢失一个方向上的旋转能力,也就是说在这种状态下我们无论怎么旋转(当然还是要原先的顺序)都不可能得到某些想要的旋转效果。具体可以点这里来了解。
    由于大多数都是物理知识,我这个农民工,了解的还不是很详细,数据我们是得到了,具体怎么应用到现实中,还要慢慢摸索。

    相关文章

      网友评论

        本文标题:Core Motion 的那些事

        本文链接:https://www.haomeiwen.com/subject/cwuiottx.html