转载自:http://www.cocoachina.com/ios/20131226/7614.html

这篇文章还可以在这里找到 英语,
 
Ray:这篇教程节选自 iOS 7 教程集,它是 iOS 7 盛宴的一部分,希望你能喜欢。
 
你可能已经注意到 iOS 7 中似乎有一些自相矛盾的地方,苹果在建议放弃真实世界的隐喻和拟物化同时,又鼓励创造体验真实的用户界面。
 
在实践中这意味着什么呢?iOS 7 的设计目标是鼓励创造能像真实的物理对象一样响应触摸、手势和方向变化的数字界面,而不是像素的简单堆砌。最终,区别于形式上的拟物化,让用户与界面产生更为深刻的联系。
这个任务听起来很艰巨,因为做一个看起来很真实的数字界面,要比做一个体验真实的界面简单得多。值得庆幸的是,你有一些很赞的新工具可以帮助你完成这个任务:UIKit 力学(Dynamics)和动态效果(Motion Effects)。
 
译者注:关于 UIKit Dynamics 的中译名,我与许多开发者有过讨论,有动力、动力模型、动态等译法。但我认为译为力学更为贴切,希望文中出现的力学知识能让你认同我的看法。
 
1.UIKit 力学是一个集成到 UIKit 的完整的物理引擎。它使你可以通过添加诸如重力、吸附(弹簧)和作用力等行为(behavior),创造体验真实的界面。你只需定义你的界面元素需要遵从的物理特性,剩下的事交给力学引擎处理即可。
 
2.动态效果让你能够创造相当酷的视差效果,例如 iOS 7 主屏的动态背景。简单来说,你可以利用手机的加速计提供的数据,开发能够响应手机方向变化的界面。
 
同时使用动态效果和力学效果,是让数字界面和体验变得栩栩如生的利器。当你的用户看到你的应用以一种自然的、动态的形式响应他们的操作时,就和你的应用产生了更深层次的联系。
 
开始吧
UIKit 力学非常有意思,最好的学习方法就是从一些小的例子开始。
 
打开 Xcode,选择 File / New / Project … 然后选择 iOSApplicationSingle View Application 并且命名新的工程为 DynamicsPlayground。创建完工程后,打开 ViewController.m 并且添加下面的代码到 viewDidLoad 的末尾:
 
  1. UIView* square = [[UIView alloc] initWithFrame:
  2. CGRectMake(100, 100, 100, 100)];
  3. square.backgroundColor = [UIColor grayColor];
  4. [self.view addSubview:square];
 
以上代码简单地添加了一个方块 UIView 到界面上。
编译运行,你可以看到如下图所示方块:
 
 
如果你在真机上运行 App,尝试转动手机,上下颠倒,或者摇动它。什么都没发生?那就对了,理应如此。因为当你向界面中添加一个视图的时候,你希望他保持稳定的 frame,直到你添加一些力学行为到界面中。
 
添加重力
继续编辑 ViewController.m,添加以下实例变量:
 
  1. UIDynamicAnimator* _animator;
  2. UIGravityBehavior* _gravity;
 
在 viewDidLoad 末尾添加以下代码:
 
  1. _animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
  2. _gravity = [[UIGravityBehavior alloc] initWithItems:@[square]];
  3. [_animator addBehavior:_gravity];
 
我稍后再解释这些代码,现在,你只需编译运行你的程序。你应该会看到方块渐渐地加速下坠,直到落到屏幕之外,如下图所示:
 
在刚添加的代码中,出现了一些力学相关的类:
1.UIDynamicAnimator 是 UIKit 物理引擎。这个类会记录你添加到引擎中的各种行为(比如重力),并且提供全局上下文。当你创建动画实例时,需要传入一个参考视图用于定义坐标系。
 
2.UIGravityBehavior 把重力的行为抽象成模型,并且对一个或多个元素施加作用力,让你可以建立物理交互模型。当你创建一个行为实例的时候,你需要把它关联到一组元素上,一般是一组视图。这样你就能选择受该行为影响的元素,在这个例子中就是指受重力影响的元素。
 
大部分行为有一些可配置属性。比如重力行为允许你改变它的角度和量级。尝试修改这些属性使你的物体向上、侧向或斜向以不同的加速度移动。
 
注意:关于单位:在物理学中,重力(g)单位是米每平方秒,约为 9.8 m/s2。根据牛顿第二定律,你可以用下面公式计算在重力作用下,物体移动的距离:
距离 = 0.5 × g × 时间^2
 
在 UIKit 力学中,公式依然适用,但单位有所不同。单位中的米每平方秒要用像素每平方秒代替。基于重力参数,应用牛顿第二定律你任然可以计算出视图在任意时间的位置。
 
你真的需要了解这些么?未必,你只需要知道更大的 g 意味着掉落得更快,但是了解背后的数学原理有利无弊。
 
设置边界
虽然你看不到,但是当方块消失在屏幕边缘的时候,它其实还在继续下落。为了使它保持在屏幕范围之内,你需要定义一个边界。
 
在 ViewController.m 里添加另一个实例变量:
 
  1. UICollisionBehavior* _collision;
  2. 在 viewDidLoad 末尾加入以下代码:
  3. _collision = [[UICollisionBehavior alloc]
  4. initWithItems:@[square]];
  5. _collision.translatesReferenceBoundsIntoBoundary = YES;
  6. [_animator addBehavior:_collision];
 
上面的代码创建了一个碰撞行为,定义了一个或多个边界,以决定相关元素之间如何互相影响。
 
上面的代码没有显式地添加边界坐标,而是设置 translatesReferenceBoundsIntoBoundary 属性为 YES。这意味着用提供给 UIDynamicAnimator 的视图的 bounds 作为边界。
 
编译并运行,你会看到方块在碰到屏幕底部之后,轻轻弹起,并最终静止,如下图所示:
 
这是一个很赞的行为,特别是看到如此少的代码量。
 
处理碰撞
接下来你要添加一个固定的障碍物,他会跟下落的方块碰撞并相互影响。在 viewDidLoad 中添加方块的代码之后加入以下代码:
 
  1. UIView* barrier = [[UIView alloc] initWithFrame:CGRectMake(0, 300, 130, 20)];
  2. barrier.backgroundColor = [UIColor redColor];
  3. [self.view addSubview:barrier];
 
编译并运行应用,你可以看到一个红色的“障碍物”横跨在屏幕中间。但是你会发现他没有起到任何作用,方块直接穿过了障碍物:
 
这显然不是你想要的,这也说明了很重要的一点:力学只影响关联到行为上的视图。
 
下面是一个简单的示意图:
 
关联 UIDynamicAnimator 到提供坐标系的参考视图,然后添加一个或多个行为来对关联的物体施加作用力。大部分行为可以与多个物体关联,每个物体可以与多个行为关联。上图展示了当前应用中的行为以及他们的关系。
当前代码里的行为都没有涉及到障碍物,因此在力学引擎中,这个障碍物并不存在。
 
使物体响应碰撞
为了让方块与障碍物碰撞,找到初始化碰撞行为的代码并用下面的代码替代:
 
  1. _collision = [[UICollisionBehavior alloc] initWithItems:@[square, barrier]];
碰撞实例需要知道它所影响的每一个视图,因此添加障碍物到列表中使得碰撞对障碍物也有作用。
 
编译并运行应用,两个物体碰撞并相互影响,如下图所示:
 
碰撞行为在每个关联的物体周围形成一个边界,使得这些物体从可以互相穿越的物体变成实体无法穿越。
 
更新一下前面的示意图,现在碰撞行为与两个视图都关联起来了:
 
但是现在还有一些有出入的地方。我们希望障碍物是不可移动的,但是当前设置下,当两个物体碰撞的时候,障碍物被撞开并且旋转着落向屏幕底部。
 
更奇怪的是,障碍物从底部弹起后似乎没有趋于静止的意思。这是因为重力没有对障碍物产生影响,这也解释了为什么在方块撞到障碍物之前它没有移动。
 
你需要另一种解决问题的思路。既然障碍物是不可移动的,那么力学引擎就没有必要知道它的存在。但是如何检测碰撞呢?
 
不可见的边界和碰撞
把碰撞行为的初始化代码改回原先的样子,使他只知道方块的存在:
 
  1. _collision = [[UICollisionBehavior alloc] initWithItems:@[square]];
然后,添加如下边界:
 
  1. // add a boundary that coincides with the top edge
  2. CGPoint rightEdge = CGPointMake(barrier.frame.origin.x +
  3. barrier.frame.size.width, barrier.frame.origin.y);
  4. [_collision addBoundaryWithIdentifier:@"barrier"
  5. fromPoint:barrier.frame.origin
  6. toPoint:rightEdge];
 
上述代码添加了一个不可见的边界,它正是障碍物的上边界。红色障碍物对用户依然是可见的,但是力学引擎不知道它的存在;相反,添加的边界对于力学引擎是可见的,对于用户是不可见的。当方块下落的时候,看起来与障碍物发生了碰撞,其实它碰到了不可移动的边界。
 
编译并运行应用,你看到的效果如下图所示:
 
方块现在从障碍物的边界弹起,旋转,然后继续落到屏幕底部直到静止。
 
到现在为止,UIKit 力学的强大之处可见一斑:你只需要几行简单的代码就可以实现相当复杂的效果。在这背后有许多复杂的逻辑,在下个章节会涉及力学引擎与应用中物体交互的具体方式。
 
碰撞的背后
每一个力学行为都有一个 action 属性,你可以定义一个 block 使其在动画的每一步被执行。添加下面的代码到 viewDidLoad:
 
  1. _collision.action =  ^{
  2. NSLog(@"%@, %@",
  3. NSStringFromCGAffineTransform(square.transform),
  4. NSStringFromCGPoint(square.center));
  5. };
 
上面的代码记录了下落的方块的中点位置核 transform 属性。编译并运行应用,你可以在 Xcode 的控制台中看到调试信息。
 
在前 400 毫秒左右你会看到类似这样的信息:
 
  1. 2013-07-26 08:21:58.698 DynamicsPlayground[17719:a0b] [1, 0, 0, 1, 0, 0], {150, 236}
  2. 2013-07-26 08:21:58.715 DynamicsPlayground[17719:a0b] [1, 0, 0, 1, 0, 0], {150, 243}
  3. 2013-07-26 08:21:58.732 DynamicsPlayground[17719:a0b] [1, 0, 0, 1, 0, 0], {150, 250}
 
可以看到力学引擎在动画过程中不断改变方块的中点位置,或者说它的 frame。
 
当方块撞到障碍物的时候,它开始旋转,这时候的调试信息类似这样:
 
  1. 2013-07-26 08:21:59.182 DynamicsPlayground[17719:a0b] [0.10679234, 0.99428135, -0.99428135, 0.10679234, 0, 0], {198, 325}
  2. 2013-07-26 08:21:59.198 DynamicsPlayground[17719:a0b] [0.051373702, 0.99867952, -0.99867952, 0.051373702, 0, 0], {199, 331}
  3. 2013-07-26 08:21:59.215 DynamicsPlayground[17719:a0b] [-0.0040036771, 0.99999201, -0.99999201, -0.0040036771, 0, 0], {201, 338}
 
你可以看到力学引擎根据物理模型计算并同时改变 transform 属性和 frame 属性来定位视图。
 
虽然这些属性的具体取值没什么意思,但是很重要的一点是他们每时每刻都在改变。因此如果你尝试用代码改变物体的 frame 或者 transform 属性,这些值会被覆盖。这意味着当你的物体受力学引擎控制的时候,你不能通过 transform 来缩放你的物体。
 
力学行为的方法名里用的是 items 而不是 views,这是因为想要使用力学行为的对象只需实现 UIDynamicItem 协议即可,定义如下:
 
  1. @protocol UIDynamicItem
  2. @property (nonatomic, readwrite) CGPoint center;
  3. @property (nonatomic, readonly) CGRect bounds;
  4. @property (nonatomic, readwrite) CGAffineTransform transform;
  5. @end
 
UIDynamicItem 协议为力学引擎提供了读写 center 和 transform 属性的权限,使它可以根据内部的计算结果移动物体。同时提供了 bounds 的读权限,用以确定物体的大小,这不但在计算物体边界的时候被用到,同时在物体受力时用于计算物体的质量。
 
这个协议说明力学引擎与 UIView并不耦合,其实 UIKit 中还有一个类也实现了这个协议 – UICollectionViewLayoutAttributes。所以可以通过力学引擎对 collection views 实现动画效果。
 
碰撞通知
到现在,你添加了一些视图和行为,然后让力学引擎接手剩下的任务。在接下来的内容中你会看到如何接收物体碰撞时的通知。
 
打开 ViewController.m 并且实现 UICollisionBehaviorDelegate 协议:
 
  1. @interface ViewController ()
  2. @end
 
还是在 viewDidLoad 中,在初始化碰撞行为后,设置当前 view controller 为其代理(delegate),代码如下:
 
  1. _collision.collisionDelegate = self;
然后,添加一个碰撞行为的代理方法:
 
  1. - (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id)item
  2. withBoundaryIdentifier:(id)identifier atPoint:(CGPoint)p {
  3. NSLog(@"Boundary contact occurred - %@", identifier);
  4. }
 
当碰撞发生的时候,这个代理方法会被调用并且在控制台打印出调试信息。为了避免控制台的信息太乱,你可以删除之前在 _collision.action 里添加的调试信息。
 
编译运行,物体相互碰撞,你会在控制台看到如下信息:
 
  1. 2013-07-26 08:44:37.473 DynamicsPlayground[18104:a0b] Boundary contact occurred - barrier
  2. 2013-07-26 08:44:37.689 DynamicsPlayground[18104:a0b] Boundary contact occurred - barrier
  3. 2013-07-26 08:44:38.256 DynamicsPlayground[18104:a0b] Boundary contact occurred - (null)
  4. 2013-07-26 08:44:38.372 DynamicsPlayground[18104:a0b] Boundary contact occurred - (null)
  5. 2013-07-26 08:44:38.455 DynamicsPlayground[18104:a0b] Boundary contact occurred - (null)
  6. 2013-07-26 08:44:38.489 DynamicsPlayground[18104:a0b] Boundary contact occurred - (null)
  7. 2013-07-26 08:44:38.540 DynamicsPlayground[18104:a0b] Boundary contact occurred - (null)
 
从调试信息中可以看到方块碰了两次障碍物,也就是之前添加的不可见的边界。(null) 则是指参考视图的边界。
 
这些调试信息读起来很有意思(认真的),但是如果能在碰撞时触发一些视觉反馈,那就更有意思了。
 
在输出调试信息的代码之后添加如下代码:
 
  1. UIView* view = (UIView*)item;
  2. view.backgroundColor = [UIColor yellowColor];
  3. [UIView animateWithDuration:0.3 animations:^{
  4. view.backgroundColor = [UIColor grayColor];
  5. }];
 
上述代码会改变碰撞的物体的背景色为黄色,然后渐变回灰色。
 
编译运行,看一下实际效果:
 
每次方块与边界发生碰撞的时候,它都会闪现黄色。
 
UIKit 力学会根据物体的 bounds 计算并自动设置它们的物理属性(如质量或弹性系数)。接下来你会看到如何使用 UIDynamicItemBehavior 类控制这些物理属性。
 
设置物体属性
在 viewDidLoad 的末尾,添加下面的代码:
 
  1. UIDynamicItemBehavior* itemBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:@[square]];
  2. itemBehaviour.elasticity = 0.6;
  3. [_animator addBehavior:itemBehaviour];
 
上面的代码创建了一个物体行为(item behavior),把它关联到方块,然后添加该行为到动画实例(animator)。弹性系数属性(elasticity)控制物体的弹性,取 1.0 表示完全弹性碰撞,也就是说碰撞中没有能量或速度的损失。你刚刚设置了方块的弹性系数为 0.6,意味着方块在每次弹起的时候速度都会减慢。
 
编译运行应用,你会发现现在的方块比之前更有弹性,如下:
 
注: 如果你想知道我是如何生成如上图片来展现方块的历史位置,其实相当简单!我给行为的 action 属性添加了一个简单的 block,每执行五次,用当前方块的中点位置和 transform 属性,添加一个新的方块到当前视图。
 
在上面的代码中,你只改变了物体的弹性系数,然后物体的行为类还有很多其他可以调整的属性。有下列属性:
 
elasticity(弹性系数) – 决定了碰撞的弹性程度,比如碰撞时物体的弹性。
friction(摩擦系数) – 决定了沿接触面滑动时的摩擦力大小。
density(密度) – 跟 size 结合使用,来计算物体的总质量。质量越大,物体加速或减速就越困难。
resistance(阻力) – 决定线性移动的阻力大小,这根摩擦系数不同,摩擦系数只作用于滑动运动。
angularResistance(转动阻力) – 决定转动运动的阻力大小。
allowsRotation(允许旋转) – 这个属性很有意思,它在真实的物理世界没有对应的模型。设置这个属性为 NO 物体就完全不会转动,无力受到多大的转动力。
 
动态添加行为
现在的情况下,你的应用设置系统的所有行为,然后由力学引擎处理系统的物理行为,直至所有物体静止。在下一步中,你会看到如何动态添加或删除行为。
 
打开 ViewController.m 并添加如下实例变量:
 
  1. BOOL _firstContact;
添加下面的代码到碰撞代理方法 collisionBehavior:beganContactForItem:withBoundaryIdentifier:atPoint: 的末尾:
 
  1. if (!_firstContact)
  2. {
  3. _firstContact = YES;
  4. UIView* square = [[UIView alloc] initWithFrame:CGRectMake(30, 0, 100, 100)];
  5. square.backgroundColor = [UIColor grayColor];
  6. [self.view addSubview:square];
  7. [_collision addItem:square];
  8. [_gravity addItem:square];
  9. UIAttachmentBehavior* attach = [[UIAttachmentBehavior alloc] initWithItem:view
  10. attachedToItem:square];
  11. [_animator addBehavior:attach];
  12. }
 
上面的代码检测到方块和障碍物的第一次接触时,创建第二个方块并添加到碰撞和重力行为中。此外,设置了一个吸附行为,实现两个物体之间加入虚拟的弹簧的效果。
 
编译运行应用,当原有的方块撞到障碍物时,你应该会看到一个新的方块出现,如下:
 
虽然两个方块看起来被连接到一起,但是因为没有在屏幕上画线条或是弹簧,你并不会看到视觉上的联系。
 
接下来做什么?
 
现在你应该比较了解 UIKit 力学的核心概念了。
 
如果你有兴趣学习更多关于 UIKit 力学的内容,可以关注我们的新书 iOS 7 教程集。书中结合你在本文学到的内容,有更深入的内容,展示了如何在现实场景中利用 UIKit 力学:
 
用户可以上拉一个食谱来预览它,当用户松开食谱的时候,它会落回菜单中,或是停靠在屏幕顶部。最终的成品是一个有真实物理体验的应用。
 
我希望你喜欢这个 UIKit 力学教程 – 我们觉得这很酷,并且期待看到你在应用中付诸有创意的交互。如果你有任何问题或评论,请加入下面的论坛讨论!
 
你在这个教程中创建的 Dynamics Playground 工程的完整代码可以在github上找到,文中每一步编译运行都对应一次提交。

(转载)UIKIt力学教程的更多相关文章

  1. UIKit 力学教程

    import UIKit class ViewController: UIViewController { var animator: UIDynamicAnimator? override func ...

  2. 转载 CSS3 经典教程系列:CSS3 盒阴影(box-shadow)详解

    目标大纲 文章转载 CSS3 经典教程系列:CSS3 盒阴影(box-shadow)详解 IE中CSS-filter滤镜小知识大全 CSS实现跨浏览器兼容性的盒阴影效果

  3. 【转载】NSURLSession教程

    原文:http://www.raywenderlich.com/51127/nsurlsession-tutorial 查理·富尔顿 2013年10月9日, 推特 注意从雷 :这是一个缩写版的一章 i ...

  4. [转载]Ocelot简易教程(六)之重写配置文件存储方式并优化响应数据

    作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9807125.html 很多人都说配置文件的配置很繁琐,如果存储在数据库就方便很多,可以通过自定义UI界面 ...

  5. [转载]Ocelot简易教程(五)之集成IdentityServer认证以及授权

    作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9807125.html 最近比较懒,所以隔了N天才来继续更新第五篇Ocelot简易教程,本篇教程会先简单介 ...

  6. [转载]Ocelot简易教程(三)之主要特性及路由详解

    上篇<Ocelot简易教程(二)之快速开始2>教大家如何快速跑起来一个ocelot实例项目,也只是简单的对Ocelot进行了配置,这篇文章会给大家详细的介绍一下Ocelot的配置信息.希望 ...

  7. [转载]Ocelot简易教程(一)Ocelot是什么

    Ocelot简易教程(一)Ocelot是什么 简单的说Ocelot是一个用.NET Core实现并且开源的API网关技术. 可能你又要问了,什么是API网关技术呢?Ocelot又有什么特别呢?我们又该 ...

  8. iOS之UIKit系列教程<一>

    前言:博主接触iOS的编程也有一段时间,今天把有关UI控件的一些知识在这里做一些总结. 申明:此系列文章都是使用目前最新版本swift3.0.1进行讲解的,与其他版本可能略有差异. 一,UIKit之设 ...

  9. [转载]SVN使用教程

    SVN简介: 为什么要使用SVN? 程序员在编写程序的过程中,每个程序员都会生成很多不同的版本,这就需要程序员有效的管理代码,在需要的时候可以迅速,准确取出相应的版本. Subversion是什么? ...

随机推荐

  1. BZOJ3533 [Sdoi2014]向量集 【线段树 + 凸包 + 三分】

    题目链接 BZOJ3533 题解 我们设询问的向量为\((x_0,y_0)\),参与乘积的向量为\((x,y)\) 则有 \[ \begin{aligned} ans &= x_0x + y_ ...

  2. 洛谷P3763 [Tjoi2017]DNA 【后缀数组】

    题目链接 洛谷P3763 题解 后缀数组裸题 在BZOJ被卡常到哭QAQ #include<algorithm> #include<iostream> #include< ...

  3. 洛谷P3806 【模板】点分治1 【点分治】

    题目背景 感谢hzwer的点分治互测. 题目描述 给定一棵有n个点的树 询问树上距离为k的点对是否存在. 输入输出格式 输入格式: n,m 接下来n-1条边a,b,c描述a到b有一条长度为c的路径 接 ...

  4. Website Collection

    前一百个卡特兰数 Candy?的博弈论总结 杜教筛资料 线性基资料 (ex)BSGS资料 斐波那契数列前300项 斯特林数 STL标准库-容器-unordered_set C++ unordered_ ...

  5. PHP 5.4语法改进与弃用特性

    PHP 5.4于本月尘埃落定,它是 PHP 自 2009 年以来的首次重大更新.该版本对语言部分进行了增强,包括支持 Traits 和移除部分争议特性. Traits 同 Java 和 .NET 一样 ...

  6. 使用google api material icons在网页中插入图标

    在<head></head>中加入这一句: <link rel='stylesheet' href='http://fonts.googleapis.com/icon?f ...

  7. Codeforces Round #525 (Div. 2)D. Ehab and another another xor problem

    D. Ehab and another another xor problem 题目链接:https://codeforces.com/contest/1088/problem/D Descripti ...

  8. rest与restful

      知乎上面摘抄的,感觉不错,分享下:  https://www.zhihu.com/question/28557115 1. REST描述的是在网络中client和server的一种交互形式:RES ...

  9. 解析json方式之net.sf.json

    前面转载了json解析的技术:fastjson,今天说下另外一种技术. 下载地址 本次使用版本:http://sourceforge.net/projects/json-lib/files/json- ...

  10. JS学习笔记之页面信息滚动效果

    效果截图: 1.无缝滚动效果 JS代码: <script> window.onload=function(){ var oInfobox=document.getElementById(' ...