UIKit Dynamics:集成到UIKit中的物理引擎,用于创建带有重力效果、弹跳效果的UI效果,让用户感受更加真实。
Motion Effects:用于创建“视觉差”效果,类似在iOS主界面上,左右倾斜手机的效果。
UIKit dynamics
学习UIKit dynamics最好的方法还是做一些实例。
创建名为DynamicsPlayground的Single View应用程序,并在ViewController.m中添加如下代码以创建一个灰色的正方形:
- (void)viewDidLoad {
[super viewDidLoad];
UIView *square = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
square.backgroundColor = [UIColor grayColor];
[self.view addSubview:square];
}
添加重力效果
定义实例变量:
UIDynamicAnimator* _animator;
UIGravityBehavior* _gravity;
在viewDidLoad的最后添加重力效果:
_animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
_gravity = [[UIGravityBehavior alloc] initWithItems:@[square]];
[_animator addBehavior:_gravity];
编译执行,你将会看到灰色的方块向下加速移动直到掉出屏幕:
这里用到了动态效果相关的两个类:
UIDynamicAnimator:作为UIKit物理效果的引擎,这个类保存着我们加到引擎中的所有动态效果,比如重力效果,并提供全局上下文。当我们创建animator实例时,需要传递一个reference view给引擎,animator使用这个view来设置坐标系。
UIGravityBehavior:为一个或多个item模拟重力效果。这里我们为square view模拟重力效果。
设置边界
通过设置边界,可以让灰色方块保持在屏幕上
定义变量:
UICollisionBehavior* _collision;
添加如下代码到viewDidLoad的最后:
_collision = [[UICollisionBehavior alloc] initWithItems:@[square]];
_collision.translatesReferenceBoundsIntoBoundary = YES;
[_animator addBehavior:_collision];
处理碰撞
接下来,我们添加一个无法移动的障碍物,下落的方块撞到障碍物后,会改变其下落路线。
UIView *barrier = [[UIView alloc] initWithFrame:CGRectMake(0, 300, 130, 20)];
barrier.backgroundColor = [UIColor redColor];
[self.view addSubview:barrier];
_collision = [[UICollisionBehavior alloc] initWithItems:@[square, barrier]];
_collision.translatesReferenceBoundsIntoBoundary = YES;
[_animator addBehavior:_collision];
此时,关系图如下:
隐形的边界
要到达障碍物不被撞飞,需要创建一个隐形的边界,宽度和位置和障碍物上沿一致。
将barrier从UICollisionBehavior中移除,这样障碍物就和dynamic引擎没有关联了。
_collision = [[UICollisionBehavior alloc] initWithItems:@[square]];
添加一个与红色障碍物上沿一致的边界,当灰色方框装上这条边界时,就会弹开。
// add a boundary that coincides with the top edge
CGPoint rightEdge = CGPointMake(barrier.frame.origin.x + barrier.frame.size.width, barrier.frame.origin.y);
[_collision addBoundaryWithIdentifier:@"barrier" fromPoint:barrier.frame.origin toPoint:rightEdge];
碰撞action
通过动态行为的action属性,我们可以为动画的每一步指定一段可执行代码,如下代码可以让引擎在动画的每一步输出灰色方块的center和transform属性。
_collision.action = ^{
NSLog(@"%@, %@", NSStringFromCGAffineTransform(square.transform),
NSStringFromCGPoint(square.center));
};
编译执行程序,在方块下落过程中但是碰撞到障碍物之前,log如下,只有位移,没有旋转
2015-02-26 15:38:06.135 DynamicsPlayground[16183:168172] [1, 0, 0, 1, 0, 0], {150, 151}
2015-02-26 15:38:06.150 DynamicsPlayground[16183:168172] [1, 0, 0, 1, 0, 0], {150, 151}
2015-02-26 15:38:06.165 DynamicsPlayground[16183:168172] [1, 0, 0, 1, 0, 0], {150, 152}
2015-02-26 15:38:06.182 DynamicsPlayground[16183:168172] [1, 0, 0, 1, 0, 0], {150, 154}
碰撞到障碍物之后,方块开始旋转,log如下:
2015-02-26 15:38:06.550 DynamicsPlayground[16183:168172] [0.99997687, 0.0067999475, -0.0067999475, 0.99997687, 0, 0], {150, 250}
2015-02-26 15:38:06.566 DynamicsPlayground[16183:168172] [0.99839866, 0.056569785, -0.056569785, 0.99839866, 0, 0], {152, 250}
2015-02-26 15:38:06.583 DynamicsPlayground[16183:168172] [0.99434483, 0.10619935, -0.10619935, 0.99434483, 0, 0], {153, 250}
2015-02-26 15:38:06.600 DynamicsPlayground[16183:168172] [0.98785663, 0.15536803, -0.15536803, 0.98785663, 0, 0], {155, 251}
UIDyamicItem protocol定义如下,它使得引擎可以读取或修改对象的center和transform属性,使得引擎可以根据自己的计算修改这些属性。同时,引擎还可以访问bounds属性,以确定对象的大小。引擎使用这个数据来为对象创建边界。UIKit中另外一个遵循此协议的类是UICollectionViewLayoutAttributes,使得View Collection中的view之间可以以动画的方式切换。
@protocol UIDynamicItem <NSObject>
@property (nonatomic, readwrite) CGPoint center;
@property (nonatomic, readonly) CGRect bounds;
@property (nonatomic, readwrite) CGAffineTransform transform;
@end
碰撞通知
通过碰撞通知功能,我们的程序可以在对象发生碰撞时,收到来自引擎的通知。修改ViewController.m
@interface ViewController () <UICollisionBehaviorDelegate>
@end
实现代理方法:
- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p
{
NSLog(@"Boundary contact occurred - %@", identifier);
}
执行程序可以看到以下log:
2015-02-26 16:11:22.289 DynamicsPlayground[17811:185503] Boundary contact occurred - barrier
2015-02-26 16:11:22.489 DynamicsPlayground[17811:185503] Boundary contact occurred - barrier
2015-02-26 16:11:22.939 DynamicsPlayground[17811:185503] Boundary contact occurred - (null)
2015-02-26 16:11:23.172 DynamicsPlayground[17811:185503] Boundary contact occurred - (null)
修改代码,当碰撞发生时,改变方块的颜色:
- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p
{
NSLog(@"Boundary contact occurred - %@", identifier);
UIView *view = (UIView *)item;
view.backgroundColor = [UIColor yellowColor];
[UIView animateWithDuration:0.3 animations:^{
view.backgroundColor = [UIColor grayColor];
}];
}
程序执行效果如下:
配置item属性
修改viewDidLoad,修改灰色方块的弹性属性为0.6,这个值,越大,弹性越好,1.0代表不会损失动力。
UIDynamicItemBehavior *itemBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:@[square]];
itemBehaviour.elasticity = 0.6;
[_animator addBehavior:itemBehaviour];
运行时添加动态行为
修改ViewController.m,定义实例变量:
BOOL _firstContact;
修改collisionBehavior:beganContactForItem:withBoundaryIdentifier:atPoint方法,当灰色方块发生第一次碰撞时,创建第二个方块,将第二个方块加入到collision和gravity行为中。
- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p
{
NSLog(@"Boundary contact occurred - %@", identifier);
UIView *view = (UIView *)item;
view.backgroundColor = [UIColor yellowColor];
[UIView animateWithDuration:0.3 animations:^{
view.backgroundColor = [UIColor grayColor];
}];
if (!_firstContact)
{
_firstContact = YES;
UIView *square = [[UIView alloc] initWithFrame:CGRectMake(30, 0, 100, 100)];
square.backgroundColor = [UIColor grayColor];
[self.view addSubview:square];
[_collision addItem:square];
[_gravity addItem:square];
UIAttachmentBehavior *attch = [[UIAttachmentBehavior alloc] initWithItem:view attachedToItem:square];
[_animator addBehavior:attch];
}
}
程序执行效果如下: