UIKit Dynamics应用
2015-10-15 20:28
471 查看
什么是 UIKit Dynamics
iOS 7 中推出的UIKit Dynamics,主要带来了模拟现实的二维动画效果,Apple 的高度封装让开发者不用知道太多物理知识也可以开发出逼真的物理动画。Real word inspired interactions
Combining predefined and interactive animations
Designed for UI
Why
苹果鼓励模拟真实世界的交互而不只是简单的像素堆砌的拟物风格,所以苹果这些模拟现实的交互动画封装进了 UIKit,希望开发者能开发出更多模拟现实的交互。关键类
UIDynamicAnimator ,封装了底层 iOS 物理引擎,为动力项(UIDynamicItem)提供物理相关的功能和动画。UIDynamicBehavior ,动力行为,为动力项提供不同的物理行为
UIDynamicItem ,动力项,相当于现实世界中的一个基本物体
![](http://img0.tuicool.com/YrARRbb.png)
这三个类的结构是:UIDynamicAnimator 需要一个 refrence view 作为物理引擎的坐标系统,再根据不同需求添加各种动力行为(UIDynamicBehavior),而每个动力行为都可以指定一个或多个动力项(UIDynamicItem),常用的动力项就是一个普通的 View。
UIDynamicAnimator
UIDynamicAnimator 封装了底层 iOS 物理引擎,为动力项(UIDynamicItem)提供物理相关的功能和动画,并为这些动画提供上下文。Animator 作为底层 iOS 物理引擎和动力项(UIDynamicItem)之间的中介,通过- (void)addBehavior:(UIDynamicBehavior *)behavior;方法添加不同的动力行为,让动力项拥有物理功能和动画。
现在来看看 UIDynamicAnimator 都有哪些方法:
初始化和管理一个 Dynamic Animator
// 传入一个 Reference view 创建一个 Dynamic Animator - (instancetype)initWithReferenceView:(UIView*)view; // 获取在 CGRect 内所有的动力项,这个 CGRect 是基于 Reference view 的二维坐标系统的 - (NSArray*)itemsInRect:(CGRect)rect; // 添加动力行为 - (void)addBehavior:(UIDynamicBehavior *)behavior; // 删除指定的动力行为 - (void)removeBehavior:(UIDynamicBehavior *)behavior; // 删除所有的动力行为 - (void)removeAllBehaviors; |
// 是否正在运行 @property (nonatomic, readonly, getter = isRunning) BOOL running; // 获取所有的 Behaviors @property (nonatomic, readonly, copy) NSArray* behaviors; @property (nonatomic, readonly) UIView* referenceView; // 这个 delegate 中有两个回调方法,一个是在 animator 暂停的时候调用,一个是在将要恢复的时候调用 @property (nonatomic, assign) id <UIDynamicAnimatorDelegate> delegate; // 已经运行了多久的时间,是一个 NSTimeInterval - (NSTimeInterval)elapsedTime; // 如果动力项不是通过 animator 自动计算改变状态,比如,通过代码强制改变一个 item 的 transfrom 时,可以用这个方法通知 animator 这个 item 的改变。如果不用这个方法,animator 之后的动画会覆盖代码中对 item 做的改变,相当于代码改变 transform 变得没有意义。 - (void)updateItemUsingCurrentState:(id <UIDynamicItem>)item; |
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout*)layout // 传入一个 CollectionViewLayout 创建一个 Dynamic Animator – layoutAttributesForCellAtIndexPath: – layoutAttributesForDecorationViewOfKind:atIndexPath: – layoutAttributesForSupplementaryViewOfKind:atIndexPath: |
在 ViewController.m 文件修改成如下代码:
@interface ViewController () @property (strong, nonatomic) UIView *squareView; @property (strong, nonatomic) UIDynamicAnimator *animator; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.squareView = [[UIView alloc] initWithFrame:CGRectMake(100, 200, 200, 200)]; self.squareView.backgroundColor = [UIColor orangeColor]; [self.view addSubview:self.squareView]; self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; } @end |
UIDynamicBehavior 是具体的物理行为。
UIDynamicBehavior 赋予动态行为给一个或多个动态项(Dynamic Item)。UIDynamicBehavior 的主要方法和属性
// 在将要进行动画时的 block 回调 @property(nonatomic, copy) void (^action)(void) // 添加到该动态行为中的子动态行为 @property(nonatomic, readonly, copy) NSArray *childBehaviors // 该动态行为相关联的dynamicAnimator @property(nonatomic, readonly) UIDynamicAnimator *dynamicAnimator //添加一个子动态行为 - (void)addChildBehavior:(UIDynamicBehavior *)behavior // 移除一个子动态行为 - (void)removeChildBehavior:(UIDynamicBehavior *)behavior // 当该动态行为将要被添加到一个UIDynamicAnimator中时,这个方法会被调用。 - (void)willMoveToAnimator:(UIDynamicAnimator *)dynamicAnimator |
UIDynamicBehavior的子类有:
UIGravityBehavior
重力行为,可以指定重力的方向和大小。用gravityDirection指定一个向量,或者设置 angle 和 magnitude。打开刚才的项目,DynamicDemo,在 ViewController.m 中添加如下代码:
- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.squareView]]; // 创建一个重力行为 gravity.gravityDirection = CGVectorMake(0, 1); // 在垂直向下方向 1000 点/平方秒 的速度 [self.animator addBehavior:gravity]; } |
![](http://img0.tuicool.com/yyMr2mM.gif)
UICollisionBehavior
碰撞行为,指定一个边界,物体在到达这个边界的时候会发生碰撞行为。通过实现 UICollisionBehaviorDelegate 可以跟踪物体什么时候开始碰撞和结束碰撞。现在将下面代码添加到
[self.animator addBehavior:gravity];之后
// 创建碰撞行为 UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:balls]; // 指定 Reference view 的边界为可碰撞边界 collision.translatesReferenceBoundsIntoBoundary = YES; // UICollisionBehaviorModeItems:item 只会和别的 item 发生碰撞;UICollisionBehaviorModeBoundaries:item 只和碰撞边界进行碰撞;UICollisionBehaviorModeEverything:item 和 item 之间会发生碰撞,也会和指定的边界发生碰撞。 collision.collisionMode = UICollisionBehaviorModeEverything; [self.animator addBehavior:collision]; |
![](http://img1.tuicool.com/qyiIJnA.gif)
UICollisionBehavior通过下面两个方法来添加碰撞边界,可以根据贝塞尔曲线或者一条直线生成碰撞边界。
- (void)addBoundaryWithIdentifier:(id <NSCopying>)identifier forPath:(UIBezierPath*)bezierPath; - (void)addBoundaryWithIdentifier:(id <NSCopying>)identifier fromPoint:(CGPoint)p1 toPoint:(CGPoint)p2; |
// item 与 item 之间开始碰撞。 - (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item1 withItem:(id <UIDynamicItem>)item2 atPoint:(CGPoint)p; // item 与 item 之间结束碰撞。 - (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item1 withItem:(id <UIDynamicItem>)item2; // item 和边界开始碰撞 - (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier atPoint:(CGPoint)p; // item 和边界结束碰撞 - (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier; |
@interface ViewController () <UICollisionBehaviorDelegate> @property (strong, nonatomic) UIView *squareView; @property (strong, nonatomic) UIDynamicAnimator *animator; @end @implementation BeginnerViewController - (void)viewDidLoad { [super viewDidLoad]; self.squareView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)]; self.squareView.backgroundColor = [UIColor orangeColor]; [self.view addSubview:self.squareView]; self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.squareView]]; [self.animator addBehavior:gravity]; UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.squareView]]; collision.translatesReferenceBoundsIntoBoundary = YES; collision.collisionDelegate = self; [self.animator addBehavior:collision]; } #pragma mark - UICollisionBehaviorDelegate - (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier atPoint:(CGPoint)p { // 结束碰撞为 squareView 设置一个随机背景 self.squareView.backgroundColor = [UIColor colorWithRed:(float)rand() / RAND_MAX green:(float)rand() / RAND_MAX blue:(float)rand() / RAND_MAX alpha:1]; } - (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier { // 结束碰撞为 squareView 设置一个随机背景 self.squareView.backgroundColor = [UIColor colorWithRed:(float)rand() / RAND_MAX green:(float)rand() / RAND_MAX blue:(float)rand() / RAND_MAX alpha:1]; } @end |
![](http://img0.tuicool.com/u2YzY3V.gif)
UIAttachmentBehavior
附着行为,让物体附着在某个点或另外一个物体上。可以设置附着点的到物体的距离,阻尼系数和振动频率等。在 ViewController.m 的
- (void)viewDidAppear:(BOOL)animated末尾添加如下代码:
UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:self.squareView attachedToAnchor:self.squareView.center]; attachment.length = 50; attachment.damping = 0.5; attachment.frequency = 1; [self.animator addBehavior:attachment]; |
![](http://img0.tuicool.com/fimMZ3a.gif)
属性详细说明
// UIAttachmentBehaviorTypeAnchor类型的依赖行为的锚点,锚点与行为相关的动力动画的坐标系统有关。 @property(readwrite, nonatomic) CGPoint anchorPoint // 吸附行为的类型 @property(readonly, nonatomic) UIAttachmentBehaviorType attachedBehaviorType // 描述吸附行为减弱的阻力大小 @property(readwrite, nonatomic) CGFloat damping // 吸附行为震荡的频率 @property(readwrite, nonatomic) CGFloat frequency // 与吸附行为相连的动态项目,当吸附行为类型是UIAttachmentBehaviorTypeItems时有2个元素,当吸附行为类型是UIAttachmentBehaviorTypeAnchor时只有一个元素。 @property(nonatomic, readonly, copy) NSArray *items // 吸附行为中的两个吸附点之间的距离,通常用这个属性来调整吸附的长度,可以创建吸附行为之后调用。系统基于你创建吸附行为的方法来自动初始化这个长度 @property(readwrite, nonatomic) CGFloat length |
UIDynamicItemBehavior
物体属性,如密度、弹性系数、摩擦系数、阻力、转动阻力等。接下来我们修改物体的物理属性,为了能看到这个效果,我们先删除 UIAttachmentBehavior 相关的代码,并在
- (void)viewDidAppear:(BOOL)animated末尾添加如下代码:
UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.squareView]]; itemBehavior.elasticity = 0.8; // 改变弹性 itemBehavior.allowsRotation = YES; // 允许旋转 [itemBehavior addAngularVelocity:1 forItem:self.squareView]; // 让物体旋转 [self.animator addBehavior:itemBehavior]; |
![](http://img0.tuicool.com/QF7r6b.gif)
属性详细说明
// 弹力,通常设置 0~1 之间 @property (readwrite, nonatomic) CGFloat elasticity; // 摩擦力,0表示完全光滑无摩擦 @property (readwrite, nonatomic) CGFloat friction; // 密度,一个 100x100 points(1 point 在 retina 屏幕上等于2像素,在普通屏幕上为1像素。)大小的物体,密度1.0,在上面施加 1.0 的力,会产生 100 point/平方秒 的加速度。 @property (readwrite, nonatomic) CGFloat density; // 线性阻力,物体在移动过程中受到的阻力大小 @property (readwrite, nonatomic) CGFloat resistance; // 旋转阻力,物体旋转过程中的阻力大小 @property (readwrite, nonatomic) CGFloat angularResistance; // 是否允许旋转 @property (readwrite, nonatomic) BOOL allowsRotation; |
UIPushBehavior
对物体施加力,可以是持续性的力也可以是一次性的力。用一个向量(CGVector)来表示力的方向和大小。这次我们通过手势来动态的为物体添加推力,首先注释重力行为的相关代码,然后在
- (void)viewDidAppear:(BOOL)animated末尾添加如下代码:
UITapGestureRecognizer *viewTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapViewHandler:)]; [self.view addGestureRecognizer:viewTapGesture]; |
- (void)tapViewHandler:(UITapGestureRecognizer *)gestureRecognizer { UIPushBehavior *push = [[UIPushBehavior alloc] initWithItems:@[self.squareView] mode:UIPushBehaviorModeInstantaneous]; CGPoint location = [gestureRecognizer locationInView:self.view]; CGPoint itemCenter = self.squareView.center; push.pushDirection = CGVectorMake((location.x - itemCenter.x) / 100, (location.y - itemCenter.y) / 100); [self.animator addBehavior:push]; } |
![](http://img2.tuicool.com/reQRzeY.gif)
主要的属性和方法
// 推力模式,UIPushBehaviorModeContinuous:持续型。UIPushBehaviorModeInstantaneous:一次性推力。 @property (nonatomic, readonly) UIPushBehaviorMode mode; // 推力是否被激活,在激活状态下,物体才会受到推力效果 @property(nonatomic, readwrite) BOOL active // 推力的大小和方向 @property (readwrite, nonatomic) CGVector pushDirection; |
UISnapBehavior
将一个物体钉在某一点。它只有一个初始化方法和一个属性。// 根据 item 和 point 来确定一个 item 要被定到哪个点上。 - (instancetype)initWithItem:(id <UIDynamicItem>)item snapToPoint:(CGPoint)point; // 减震系数,范围在0.0~1.0 @property (nonatomic, assign) CGFloat damping; |
Demo
整个 Demo 的代码:@interface ViewController () <UICollisionBehaviorDelegate> |
现实中的使用场景
AlertView 弹出和隐藏![](http://img0.tuicool.com/NnABbi.gif)
图片来自
teehan+lax
类似于系统通知的弹性效果,侧边栏菜单弹性效果
![](http://img1.tuicool.com/Ar2Eni.gif)
图片来自
teehan+lax
类似于系统 Message 信息拉动时的弹簧效果
![](http://img1.tuicool.com/YJ7Fv2i.gif)
图片来自
obj.io
还有很多使用场景期待大家共同挖掘补充
总结
UIKit Dynamic 为开发者提供了模拟现实的交互动画。从例子中来看,使用 UIKit Dynamic 实际上真的很简单,只需要几行或者十几行代码就能写出很棒的模拟真实世界的交互效果。
UIKit Dynamic 是 UIKit 的一部分,这意味着使用它不需要添加其它额外的framework,所以如果应用只支持 iOS 7 以上,可以在项目中多多使用,让应用中的动画效果瞬间提升好几个档次。
相关文章推荐
- Qt Quick 事件处理之信号与槽
- Qt Quick 之 Hello World 图文详解
- SPOJ QTREE Query on a tree 树链剖分
- Integer的自动拆装箱的陷阱
- iOS 【UIKit-首尾式动画不足&动画嵌套设置方法】
- LeetCode:Distinct Subsequences
- java Integer.valueOf()方法
- UIImage - resizableImageWithCapInsets & - resizableImageWithCapInsets:resizingMode
- 音乐播放器的简单入门使用
- 采用truelicense进行Java规划license控制 扩展可以验证后,license 开始结束日期,验证绑定一个给定的mac住址
- SPOJ Query on a tree 树链剖分 水题
- requireJS对文件合并与压缩(二)
- Android 在非主线程无法操作UI意识
- requireJS(版本是2.1.15)学习教程(一)
- SPOJ QTREE2 Query on a tree II
- UIView - endEditing
- flat UI
- apns verify error:num=20:unable to get local issuer
- MMPopupView(自定义UIAlertView、UIActionSheet、UIDatePicker)
- Android UI性能优化 – Overdraw