前言
iOS7在视觉元素上去除了拟物化,UI偏向了扁平化风格。但也从iOS7开始,iOS却在“物理”上拟真了,这儿指的是交互动画。比如,它能模拟自由落体,碰撞反弹,吸附等和现实物理现象一致的交互效果。
基础知识
- ** UIDynamicAnimator:**动力学容器。要实现动力学动画,就必须有这个容器。它的初始化方法是
- (instancetype)initWithReferenceView:(UIView *)view
。其UIView类型的view参数表示view的子类UI才可有动力学动画。 - ** UIDynamicBehavior:**动力学行为。这是一个基类,我们一般使用它的子类们来给视图添加不同的行为。包括:重力行为(UIGravityBehavior)、碰撞行为(UICollisionBehavior)、吸附行为(UIAttachmentBehavior)、瞬移行为(UISnapBehavior)、推力行为(UIPushBehavior)、动力项行为(UIDynamicItemBehavior)等。
行为的初始化方法是- (instancetype)initWithItems:(NSArray<id <UIDynamicItem>> *)items
- ** UIDynamicItem:**动力项,即要做动画的元素。最常见的项目是视图,但任何遵从
UIDynamicItem
协议的动力项都可以。该协议要求包含bounds、center、transform等属性。
** 关于碰撞行为的边界:**
translatesReferenceBoundsIntoBoundary
将整个参照view(也就是self.view)的边框作为碰撞边界(另外你还可以使用setTranslatesReferenceBoundsIntoBoundaryWithInsets:
这样的方法来设定某一个区域作为碰撞边界,更复杂的边界可以使用addBoundaryWithIdentifier:forPath:
来添加UIBezierPath,或者addBoundaryWithIdentifier:fromPoint:toPoint:
来添加一条线段为边界,详细地还请查阅文档)。
** 关于动力项行为:**
动力项行为(UIDynamicItemBehavior)和其他几个内置的行为不太一样,它用于对动力项赋物理属性值。比如,密度,速度,阻力等。
** 需要注意的是,我们知道,对某动力项移除普通动力行为后,该动力项目会立马停止动画。比如,正在下落的ImgView移除重力行为后,并没有继续随重力继续下落,而是当即停止下落。然而,若一个动力项拥有UIDynamicItemBehavior行为,即便移除了重力行为,该动力项还是会根据设置的物理属性来进行运动。在实践中,若想让我们的动力项表现得更“真实自然”,给他们附加一个空的UIDynamicItemBehavior会很有帮助。**
代码例子
下面代码创建了一个UIImageView,并为其添加了一个拖动手势,拖动手势的事件回调是为imgView添加了一个“吸附行为”;并为imgView添加了“重力行为”和“碰撞行为”,当拖动松开时,imgView便自由落体撞击“地面”;最后还给self.view添加了单击手势,其事件回调是为imgView添加了一个“瞬移行为”,点到self.view的某点,imgView就瞬移到该点。
** 注意:** 重力行为是没有“地面”的,所以我们要给imgView添加一个碰撞行为,并设置屏幕底部为碰撞边界来模拟落地撞击地面。
#import "DynamicViewController.h"
@interface DynamicViewController ()<UICollisionBehaviorDelegate>
{
UIImageView *_imgView;
UIDynamicAnimator *_dynamicAnimator;
UIGravityBehavior *_gravityBehavior;
UICollisionBehavior *_collisionBehavior;
UIAttachmentBehavior *_attachmentBehavior;
UISnapBehavior *_snapBehavior;
}
@end
@implementation DynamicViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = @"UIKit Dynamic";
_imgView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
_imgView.image = [UIImage imageNamed:@"01.jpg"];
_imgView.userInteractionEnabled = YES;
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureHandle:)];
[_imgView addGestureRecognizer:panGesture];
[self.view addSubview:_imgView];
_dynamicAnimator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
// 添加重力行为
_gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[_imgView]];
[_dynamicAnimator addBehavior:_gravityBehavior];
// 添加碰撞行为
_collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[_imgView]];
_collisionBehavior.translatesReferenceBoundsIntoBoundary = YES; // 碰撞行为的边界
[_dynamicAnimator addBehavior:_collisionBehavior];
_collisionBehavior.collisionDelegate = self;
//
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureHandle:)];
[self.view addGestureRecognizer:tapGesture];
}
- (void)panGestureHandle:(UIPanGestureRecognizer *)panGesture
{
CGPoint location = [panGesture locationInView:self.view];
CGPoint boxLocation = [panGesture locationInView:_imgView];
UIOffset centerOffet = UIOffsetMake(boxLocation.x-CGRectGetMidX(_imgView.bounds), boxLocation.y-CGRectGetMidY(_imgView.bounds));
if(panGesture.state == UIGestureRecognizerStateBegan)
{
// 添加追随行为
_attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:_imgView offsetFromCenter:centerOffet attachedToAnchor:location];
_attachmentBehavior.damping = 0.1;
_attachmentBehavior.frequency = 0.4;
[_dynamicAnimator addBehavior:_attachmentBehavior];
}
else if(panGesture.state == UIGestureRecognizerStateChanged)
{
[_attachmentBehavior setAnchorPoint:location];
}
else if(panGesture.state == UIGestureRecognizerStateEnded)
{
[_dynamicAnimator removeBehavior:_attachmentBehavior];
}
}
// 吸附在手势点击的某点
- (void)tapGestureHandle:(UITapGestureRecognizer *)tapGesture
{
CGPoint location = [tapGesture locationInView:self.view];
if(_snapBehavior!=nil){
[_dynamicAnimator removeBehavior:_snapBehavior];
}
// 添加吸附行为
_snapBehavior = [[UISnapBehavior alloc] initWithItem:_imgView snapToPoint:location];
_snapBehavior.damping = 0.1;
[_dynamicAnimator addBehavior:_snapBehavior];
}
@end
UIKit动力学自定义
我们这里说的动力学自定义是指在系统基础上打包封装,而不是完全自定义,完全自定义是很复杂的。
比如,我们要实现动力项的“自由落体”,但内置的重力行为是无“地面”的,也就是说要自己在添加个“边界”来模拟地面,用碰撞行为来模拟“落地”。要完成一个完整的“自由落体”将要用到两种动力行为,我们可以将其封装为一个自定义的行为。
** 我们封装自己的类时,尽量和原有类保持风格一致。**
- 自定义类继承自基类UIDynamicBehavior;
- 提供和原有类风格一致的初始化方法
initWithItems
; - 在定义类中创建内置的动力行为,然后添加为自己类的子动力行为。
[self addChildBehavior:gravityBehavior];
FreeFallBehavior.h
#import <UIKit/UIKit.h>
@interface FreeFallBehavior : UIDynamicBehavior
- (instancetype)initWithItems:(NSArray<id <UIDynamicItem>> *)items;
@end
FreeFallBehavior.m
#import "FreeFallBehavior.h"
@interface FreeFallBehavior ()
@end
@implementation FreeFallBehavior
- (instancetype)initWithItems:(NSArray<id <UIDynamicItem>> *)items
{
if(self = [super init])
{
UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:items];
[self addChildBehavior:gravityBehavior];
UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:items];
collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
[self addChildBehavior:collisionBehavior];
}
return self;
}
@end