前言
iOS7在視覺元素上去除了擬物化,UI偏向了扁平化風(fēng)格。但也從iOS7開始,iOS卻在“物理”上擬真了,這兒指的是交互動(dòng)畫。比如,它能模擬自由落體,碰撞反彈,吸附等和現(xiàn)實(shí)物理現(xiàn)象一致的交互效果。
基礎(chǔ)知識
- ** UIDynamicAnimator:**動(dòng)力學(xué)容器。要實(shí)現(xiàn)動(dòng)力學(xué)動(dòng)畫,就必須有這個(gè)容器。它的初始化方法是
- (instancetype)initWithReferenceView:(UIView *)view
。其UIView類型的view參數(shù)表示view的子類UI才可有動(dòng)力學(xué)動(dòng)畫。 - ** UIDynamicBehavior:**動(dòng)力學(xué)行為。這是一個(gè)基類,我們一般使用它的子類們來給視圖添加不同的行為。包括:重力行為(UIGravityBehavior)、碰撞行為(UICollisionBehavior)、吸附行為(UIAttachmentBehavior)、瞬移行為(UISnapBehavior)、推力行為(UIPushBehavior)、動(dòng)力項(xiàng)行為(UIDynamicItemBehavior)等。
行為的初始化方法是- (instancetype)initWithItems:(NSArray<id <UIDynamicItem>> *)items
- ** UIDynamicItem:**動(dòng)力項(xiàng),即要做動(dòng)畫的元素。最常見的項(xiàng)目是視圖,但任何遵從
UIDynamicItem
協(xié)議的動(dòng)力項(xiàng)都可以。該協(xié)議要求包含bounds、center、transform等屬性。
** 關(guān)于碰撞行為的邊界:**
translatesReferenceBoundsIntoBoundary
將整個(gè)參照view(也就是self.view)的邊框作為碰撞邊界(另外你還可以使用setTranslatesReferenceBoundsIntoBoundaryWithInsets:
這樣的方法來設(shè)定某一個(gè)區(qū)域作為碰撞邊界,更復(fù)雜的邊界可以使用addBoundaryWithIdentifier:forPath:
來添加UIBezierPath,或者addBoundaryWithIdentifier:fromPoint:toPoint:
來添加一條線段為邊界,詳細(xì)地還請查閱文檔)。
** 關(guān)于動(dòng)力項(xiàng)行為:**
動(dòng)力項(xiàng)行為(UIDynamicItemBehavior)和其他幾個(gè)內(nèi)置的行為不太一樣,它用于對動(dòng)力項(xiàng)賦物理屬性值。比如,密度,速度,阻力等。
** 需要注意的是,我們知道,對某動(dòng)力項(xiàng)移除普通動(dòng)力行為后,該動(dòng)力項(xiàng)目會立馬停止動(dòng)畫。比如,正在下落的ImgView移除重力行為后,并沒有繼續(xù)隨重力繼續(xù)下落,而是當(dāng)即停止下落。然而,若一個(gè)動(dòng)力項(xiàng)擁有UIDynamicItemBehavior行為,即便移除了重力行為,該動(dòng)力項(xiàng)還是會根據(jù)設(shè)置的物理屬性來進(jìn)行運(yùn)動(dòng)。在實(shí)踐中,若想讓我們的動(dòng)力項(xiàng)表現(xiàn)得更“真實(shí)自然”,給他們附加一個(gè)空的UIDynamicItemBehavior會很有幫助。**
代碼例子
下面代碼創(chuàng)建了一個(gè)UIImageView,并為其添加了一個(gè)拖動(dòng)手勢,拖動(dòng)手勢的事件回調(diào)是為imgView添加了一個(gè)“吸附行為”;并為imgView添加了“重力行為”和“碰撞行為”,當(dāng)拖動(dòng)松開時(shí),imgView便自由落體撞擊“地面”;最后還給self.view添加了單擊手勢,其事件回調(diào)是為imgView添加了一個(gè)“瞬移行為”,點(diǎn)到self.view的某點(diǎn),imgView就瞬移到該點(diǎn)。
** 注意:** 重力行為是沒有“地面”的,所以我們要給imgView添加一個(gè)碰撞行為,并設(shè)置屏幕底部為碰撞邊界來模擬落地撞擊地面。
#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];
}
}
// 吸附在手勢點(diǎn)擊的某點(diǎn)
- (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動(dòng)力學(xué)自定義
我們這里說的動(dòng)力學(xué)自定義是指在系統(tǒng)基礎(chǔ)上打包封裝,而不是完全自定義,完全自定義是很復(fù)雜的。
比如,我們要實(shí)現(xiàn)動(dòng)力項(xiàng)的“自由落體”,但內(nèi)置的重力行為是無“地面”的,也就是說要自己在添加個(gè)“邊界”來模擬地面,用碰撞行為來模擬“落地”。要完成一個(gè)完整的“自由落體”將要用到兩種動(dòng)力行為,我們可以將其封裝為一個(gè)自定義的行為。
** 我們封裝自己的類時(shí),盡量和原有類保持風(fēng)格一致。**
- 自定義類繼承自基類UIDynamicBehavior;
- 提供和原有類風(fēng)格一致的初始化方法
initWithItems
; - 在定義類中創(chuàng)建內(nèi)置的動(dòng)力行為,然后添加為自己類的子動(dòng)力行為。
[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