一篇文章學(xué)會使用UIKit Dynamics

iOS 7增加了UIKit Dynamics庫,其集成于UIKit框架中而克,將2D物理引擎引入了UIKit,提供了以最簡單方式實現(xiàn)真實物理動畫功能怔毛。UIKit動力學(xué)的引入员萍,并不是為了替代CoreAnimationUIView動畫,絕大多數(shù)情況下拣度,CAUIView動畫仍然是最優(yōu)方案碎绎,只有在需要引入逼真交互設(shè)計的時候,才需要使用UIKit動力學(xué)抗果。如果你在開發(fā)電子游戲筋帖,那么應(yīng)該使用SpritKit

動力項(UIDynamicItem)是任何遵守UIDynamicItem協(xié)議的對象冤馏,相當(dāng)于現(xiàn)實世界中的一個基本物體日麸。自iOS 7開始,UIViewUICollectionViewLayoutAttributes默認(rèn)實現(xiàn)了上述協(xié)議逮光,你也可以自行實現(xiàn)該協(xié)議以便在自定義的類中使用動力效果動畫(UIDynamicAnimator)代箭,但很少需要這樣做。

動力行為(UIDynamicBehavior)為動力項(UIDynamicItem)提供不同的2D物理動畫涕刚,即指定UIDynamicItem應(yīng)該如何運(yùn)動嗡综、適用哪些物理規(guī)則。在這里UIDynamicBehavior類似一個抽象類杜漠,沒有實現(xiàn)具體行為极景,因此一般使用這個類的子類來對一組UIDynamicItem應(yīng)遵守的行為規(guī)則進(jìn)行描述。UIDynamicBehavior可以獨立作用驾茴,多個動力行為同時作用時遵守力的合成戴陡。

UIKit Dynamics庫的核心在于UIDynamicAnimator,其封裝了底層iOS物理引擎沟涨,是動力行為(UIDynamicBehavior)的容器恤批,動力行為添加到容器內(nèi)才會發(fā)揮作用,為動力項(UIDynamicItem)提供物理相關(guān)的功能和動畫裹赴。

當(dāng)所有item處于暫停時喜庞,animator自動暫停诀浪,并且當(dāng)動力行為參數(shù)改變、添加或移除動力行為或動力項時自動恢復(fù)延都。

使用動力學(xué)(dynamics)的步驟是:配置一個或多個UIDynamicBehavior雷猪,其中為每個UIDynamicBehavior指定一個或多個UIDynamicItem,最后添加這些UIDynamicBehaviorUIDynamicAnimator晰房。

UIDynamciBehavior有以下六個子類:

  1. UIGravityBehavior:重力行為求摇,重力的大小和方向可以配置,并會同等作用于所有關(guān)聯(lián)對象殊者。
  2. UICollisionBehavior:碰撞行為与境,提供兩個或多個視圖對象碰撞的能力,或視圖對象和邊界碰撞的能力猖吴。
  3. UIPushBehavior:對一個或多個動力項施加連續(xù)(continuous)或瞬時(instantaneous)力的行為摔刁,導(dǎo)致這些動力項改變位置。
  4. UIAttachmentBehavior:描述一個view和一個錨點相連接的情況海蔽,也可以描述view和view之間的連接情況共屈。
  5. UISnapBehavior:將view通過動畫吸附到某個點上。吸附的過程類似彈簧作用党窜,使動力項朝向目標(biāo)點甩出拗引,到達(dá)目標(biāo)點后其初始運(yùn)動隨著時間的推移而減弱,最終物體停止在目標(biāo)點幌衣。
  6. UIFieldBehavior:將基于場的物理學(xué)應(yīng)用于動力項對象寺擂。場的行為定義了可以應(yīng)用諸如磁力(magnetism)、拖拽(dragging)泼掠、電場(electric)怔软、漩渦(vortex)、輻射(radial)择镇、線性重力(linear gravity)挡逼、噪聲(noise)、渦流(turbulence)腻豌、彈簧場(SpringField)等力的區(qū)域家坎。

還有另外一個重要的類是UIDynamicItemBehavior,用于修改指定動力項的以下屬性:

  • allowsRotation:BOOL型值吝梅,用于指定UIDynamicItem是否旋轉(zhuǎn)虱疏。默認(rèn)值是YES
  • angularResistance:旋轉(zhuǎn)阻力苏携,物體旋轉(zhuǎn)過程的阻力大小做瞪。值的范圍是0CGFLOAT_MAX,值越大,阻力越大装蓬。
  • density:動力項的密度(density)和動力項大兄谩(size),決定了其參與UIKit動力行為(包括摩擦牍帚、碰撞儡遮、推動等)的表現(xiàn)。例如暗赶,假設(shè)有兩個相同密度但不同大小的動態(tài)項目鄙币,item1是100*100points,item2是100*200points蹂随,即item2的有效質(zhì)量是item1的二倍十嘿,在彈性碰撞時,item1和item2會展現(xiàn)出與自然界相同的動量守恒糙及。一個100p * 100p的動力項详幽,density1.0筛欢,施加1.0的力浸锨,會產(chǎn)生100points每平方秒的加速度。
  • elasticity:彈力版姑。范圍是01.0铡买,0表示沒有彈性兔毒,1.0表示完全彈性碰撞。默認(rèn)值為0
  • friction:摩擦力菇晃。默認(rèn)值0表示沒有摩擦力,1.0表示很強(qiáng)摩擦崎场。為了取得更大摩擦力埋泵,可以使用更大值。
  • resistance:線性阻力么介。物體移動過程中受到的阻力娜遵。默認(rèn)值0表示沒有線性阻力,上限CGFLOAT_MAX為完全阻力壤短。如果此屬性值為1.0设拟,則動態(tài)項一旦沒有作用力會立即停止。
  • anchored:BOOL型值久脯,指定動力項是否固定在當(dāng)前位置纳胧。被固定的動力項參與碰撞時不會被移動,而是像邊界一樣參與碰撞帘撰。默認(rèn)值是NO跑慕。

目前為止,對UIKit Dynamics的整體介紹已經(jīng)結(jié)束摧找,下一步將通過demo學(xué)習(xí)具體使用相赁。

1. 創(chuàng)建demo

和往常一樣相寇,將在創(chuàng)建的demo上添加代碼來學(xué)習(xí)UIKit Dynamics。

打開Xcode钮科,點擊File > New > File…唤衫,選擇iOS > Application > Single View Application模板,點擊Next绵脯;在Product Name中填寫UIKitDynamics佳励,LanguageObjective-C,點擊Next蛆挫;選擇文件位置赃承,點擊Create創(chuàng)建工程。

該demo將分為五個部分悴侵,為此把UITabBarController設(shè)置為根控制器瞧剖。每一部分作為單獨視圖控制器添加到UITabBarController

選中ViewController.hViewController.m并刪除可免。使用快捷鍵command+N添加文件抓于,選擇iOS > Source > Cocoa Touch Class模板,點擊Next浇借;Class名稱為FirstViewController捉撮,Subclass ofUIViewController,點擊Next妇垢;選擇文件位置巾遭,點擊Create創(chuàng)建文件。

重復(fù)上面添加文件步驟闯估,依次添加下面名稱文件:

  • SecondViewController
  • ThirdViewController
  • FourthViewController
  • FifthViewController

最后添加一個ClassTabBarController灼舍,Subclass ofUITabBarController的文件。完成后Project Navigator 如下圖:

DynamicsProjectNavigator.png

進(jìn)入AppDelegate.m涨薪,導(dǎo)入TabBarController.h骑素,在application: didFinishLaunchingWithOptions:方法中設(shè)定根控制器為TabBarController

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    //  設(shè)定根控制器
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.backgroundColor = [UIColor whiteColor];
    self.window.rootViewController = [[TabBarController alloc] init];
    [self.window makeKeyAndVisible];
    
    return YES;
}

進(jìn)入TabBarController.m尤辱,導(dǎo)入之前添加的五個視圖控制器砂豌,并將其添加到TabBarController,同時設(shè)定各視圖控制器標(biāo)題光督。

#import "TabBarController.h"

// 導(dǎo)入視圖控制器
#import "FirstViewController.h"
#import "SecondViewController.h"
#import "ThirdViewController.h"
#import "FourthViewController.h"
#import "FifthViewController.h"
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化視圖控制器 設(shè)定標(biāo)題 tabBarItem圖片
    UIImage *image = [UIImage imageNamed:@"circle"];
    FirstViewController *firstVC = [[FirstViewController alloc] init];
    firstVC.title = @"First";
    firstVC.tabBarItem.image = image;
    
    SecondViewController *secVC = [[SecondViewController alloc] init];
    secVC.title = @"Second";
    secVC.tabBarItem.image = image;
    
    ThirdViewController *thirdVC = [[ThirdViewController alloc] init];
    thirdVC.title = @"Third";
    thirdVC.tabBarItem.image = image;
    
    FourthViewController *fourthVC = [[FourthViewController alloc] init];
    fourthVC.title = @"Fourth";
    fourthVC.tabBarItem.image = image;
    
    FifthViewController *fifthVC = [[FifthViewController alloc] init];
    fifthVC.title = @"Fifth";
    fifthVC.tabBarItem.image = image;
    
    // 設(shè)置標(biāo)簽欄控制器的根視圖
    [self setViewControllers:@[firstVC, secVC, thirdVC, fourthVC, fifthVC] animated:YES];
}

上面的代碼非常簡單阳距,記得添加圖片到Assets.xcassets,可以通過文章底部鏈接下載源碼獲取结借。

2. 重力行為筐摘、碰撞行為之初體驗

2.1 重力行為 UIGravityBehavior

為了體現(xiàn)重力行為作用,我們在FirstViewController添加一個圓,填充顏色以使其看起來像一個球咖熟。最后為其添加UIGravityBehavior圃酵。

進(jìn)入FirstViewController.m文件,在私有接口部分聲明以下屬性:

@interface FirstViewController ()

@property (strong, nonatomic) UIDynamicAnimator *animator;
@property (strong, nonatomic) UIView *orangeBall;

@end

第一個聲明的為UIDynamicAnimator對象馍管。之前已經(jīng)說過郭赐,UIDynamicAnimator自身不能工作,添加其他行為后才可以确沸,稍后添加重力行為捌锭。聲明的UIView對象用于演示重力行為。

打開FirstViewController.m罗捎,進(jìn)入viewDidLoad方法中观谦,配置剛聲明的兩個屬性。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 1.初始化桨菜、配置orangeBall
    self.orangeBall = [[UIView alloc] initWithFrame:CGRectMake(self.view.bounds.size.width/2-25, 100, 50, 50)];
    self.orangeBall.backgroundColor = [UIColor orangeColor];
    self.orangeBall.layer.cornerRadius = 25;
    [self.view addSubview:self.orangeBall];
    
    // 2.初始化animator
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
}

上述代碼分步說明:

  1. 初始化豁状、配置orangeBall。圓形由self.orangeBall.layer.cornerRadius = 25;代碼生成倒得。
  2. 初始化animator泻红。初始化時需要Reference View,相當(dāng)于力學(xué)參考系屎暇,只有當(dāng)想要添加力學(xué)的UIView是Reference View的子視圖時承桥,UIDynamicAnimator才發(fā)生作用驻粟。

FirstViewController.m底部添加以下方法根悼,并在其中初始化UIGravityBehavior

- (void)testGravity {
    // 1.初始化重力行為
    UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.orangeBall]];
    
    // 2.添加重力行為到UIDynamicAnimator
    [self.animator addBehavior:gravity];
}

上述代碼分步說明:

  1. 初始化UIGravityBehavior蜀撑,它的參數(shù)為NSArray類型挤巡,可以把所有需要添加重力作用的視圖添加到該數(shù)組。在這個示例中酷麦,只需要添加orangeBall矿卑。
  2. 如果想要讓行為有效,必需添加行為到UIDynamicAnimator容器沃饶。使用animator對象的addBehavior:方法添加重力行為母廷。

進(jìn)入到viewDidLoad方法,調(diào)用上述方法糊肤。

- (void)viewDidLoad {
    ...
    // 調(diào)用testGravity方法
    [self testGravity];
}

在模擬器運(yùn)行app琴昆,如下所示:

Dynamics1Gravity.gif

現(xiàn)在重力效果帶動orangeBall向下跌落。事實上馆揉,orangeBall在跌出可見區(qū)域后业舍,仍然在繼續(xù)下滑,下面進(jìn)行驗證。

UIDynamicBehavior類有void(^action)(void)塊屬性舷暮,其子類也會繼承該屬性态罪。animator會在每步(step)動畫調(diào)用該塊,也就是action中添加的代碼會在動力動畫運(yùn)行過程中不斷執(zhí)行下面。在testGravity方法初始化graviy后添加以下代碼:

- (void)testGravity {
    ...
    gravity.action = ^{
        NSLog(@"%f",self.orangeBall.center.y);
    };
    ...
}

在上面的代碼中复颈,把塊賦值給重力行為gravityaction屬性,在塊中用NSLog輸出orangeBall中心的y坐標(biāo)沥割,用以表示其位置券膀。運(yùn)行app,可以在控制臺看到不斷輸出的數(shù)字驯遇,并且相鄰值的差不斷增大芹彬,在orangeBall離開可見區(qū)域后,控制臺繼續(xù)輸出叉庐。事實上舒帮,此時orangeBall正在做自由落體運(yùn)動。

如果對塊不熟悉陡叠,可以查看我的另一篇文章:Block的用法

2.2 碰撞行為 UICollisionBehavior

通過上面代碼玩郊,我們?yōu)?code>orangeBall添加了UIGravityBehavior,但讓視圖在重力行為作用下無限下滑沒有意義枉阵。如果orangeView在撞到tar bar頂部后開始彈跳译红,最終停止會更有用途,就像生活中的球跌落到地上兴溜。

UICollisionBehavior可以實現(xiàn)碰撞功能侦厚。使用UICollisionBehavior的步驟是初始化碰撞行為并制定碰撞邊界,添加碰撞行為到animator拙徽。更新后代碼如下:

- (void)testGravity {
    ...
    // 3.初始化碰撞行為刨沦、制定邊界
    UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.orangeBall]];
    [collision addBoundaryWithIdentifier:@"tabbar" fromPoint:self.tabBarController.tabBar.frame.origin toPoint:CGPointMake(self.tabBarController.tabBar.frame.origin.x + self.tabBarController.tabBar.frame.size.width, self.tabBarController.tabBar.frame.origin.y)];
    [self.animator addBehavior:collision];
}

在初始化時指定orangeBallUICollisionBehavior的作用對象,使用addBoundaryWithIdentifier: fromPoint: toPoint:方法添加tab bar上部為邊界膘怕。最后添加collisionanimator想诅。

除上面的添加邊界方法外,還有以下三種添加邊界的方法:

  • translatesReferenceBoundsIntoBoundary:BOOL類型值岛心,指定是否把reference view作為碰撞邊界来破。默認(rèn)為NO
  • addBoundaryWithIdentifier: forPath::添加指定Bezier Path作為碰撞邊界忘古。
  • setTranslatesReferenceBoundsIntoBoundaryWithInsets::設(shè)定某一區(qū)域作為碰撞邊界徘禁。

運(yùn)行demo,如下所示:

Dynamics1Collision.gif

現(xiàn)在orangeBall遇到邊界會彈跳存皂,不會直接穿過晌坤。我們在開始部分介紹到UIDynamicItemBehavior用于修改動力項屬性逢艘,現(xiàn)在初始化一個UIDynamicItemBehavior,修改其elasticity屬性骤菠。更新后testGravity方法如下:

- (void)testGravity {
    ...
    // 4.初始化UIDynamicItemBehavior 修改elasticity
    UIDynamicItemBehavior *ballBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.orangeBall]];
    ballBehavior.elasticity = 0.7;
    [self.animator addBehavior:ballBehavior];
}

elasticity默認(rèn)值為0它改,范圍是01.00表示沒有彈性商乎,1.0表示完全彈性碰撞央拖。這里把值設(shè)為0.7

運(yùn)行app鹉戚,顯示如下:

Dynamics1Elasticity.gif

現(xiàn)在已經(jīng)了解了UIDynamicItemBehavior的使用方法鲜戒,你可以嘗試一下其他屬性。

UICollisionBehavior類遵守UICollisionBehaviorDelegate協(xié)議抹凳,該協(xié)議內(nèi)有以下方法:

  • collisionBehavior: beganContactForItem: withBoundaryIdentifier: atPoint:動力項與邊界碰撞開始時被調(diào)用遏餐。
  • collisionBehavior: beganContactForItem: withItem: atPoint:動力項與動力項之間碰撞開始時被調(diào)用。
  • collisionBehavior: endedContactForItem: withBoundaryIdentifier:動力項與邊界碰撞結(jié)束時調(diào)用赢底。
  • collisionBehavior: endedContactForItem: withItem:動力項與動力項之間碰撞結(jié)束時調(diào)用失都。

實際使用中,可以根據(jù)上面代理方法執(zhí)行其他操作幸冻。

3. 重力行為粹庞、碰撞行為和推動行為具體應(yīng)用

通過第一部分的學(xué)習(xí),我們已經(jīng)基本了解了UIKit Dynamics的使用洽损,但這個彈跳球在實際中沒有太大用途庞溜。基于此碑定,在第二部分將創(chuàng)建一個更有實用價值的示例流码。

在顯示、隱藏菜單組件時不傅,使用UIKit Dynamics可以獲得更好的動畫效果旅掂。這一部分示例想要達(dá)到的效果為:使用UISwipeGestureRecognizer手勢自左向右滑赏胚,從左側(cè)彈出一個占據(jù)半個屏幕的菜單访娶。使用同樣手勢自右向左滑動,隱藏彈出菜單觉阅。在彈出菜單后面崖疤,另一個半透明的視圖將會顯示在主視圖之上,防止誤點擊到主視圖典勇;當(dāng)彈出菜單隱藏時劫哼,半透明視圖隱藏。如下所示:

Dynamics2MenuTable.gif

上面彈出菜單中的選項將不可使用割笙,只需要實現(xiàn)顯示权烧、隱藏功能眯亦。

為實現(xiàn)彈出、隱藏菜單功能我們添加一個UIView般码,而沒有添加另一個視圖控制器妻率。對于整個app的菜單,你應(yīng)該創(chuàng)建一個視圖控制器板祝,以便于你可以從任何視圖控制器訪問菜單宫静。這里將不會這樣做,因為這已成為自定義視圖控制器轉(zhuǎn)換券时,不再符合這里要說明的問題孤里。

彈出菜單中的選項將使用UITableView顯示。最終橘洞,菜單視圖捌袜、表視圖和背景視圖共同用于顯示彈出項,UIDynamicAnimator處理所有動畫炸枣。

進(jìn)入SecondViewController.m琢蛤,聲明以下屬性。

#import "SecondViewController.h"

@interface SecondViewController ()

@property (strong, nonatomic) UIView *menuView;
@property (strong, nonatomic) UIView *backgroundView;
@property (strong, nonatomic) UITableView *menuTable;
@property (strong, nonatomic) UIDynamicAnimator *animator;

@end

在實現(xiàn)部分前抛虏,添加以下預(yù)定義博其,用于指定menuView的寬度為視圖控制器視圖寬度的二分之一。

@end

#define menuWidth self.view.frame.size.width/2

@implementation SecondViewController

在實現(xiàn)部分使用懶加載初始化剛聲明的屬性迂猴。

// 1.設(shè)置背景視圖
- (UIView *)backgroundView {
    if (!_backgroundView) {
        _backgroundView = [[UIView alloc] initWithFrame:self.view.frame];
        _backgroundView.backgroundColor = [UIColor lightGrayColor];
        _backgroundView.alpha = 0.0;
    }
    return _backgroundView;
}

// 2.設(shè)置菜單視圖
- (UIView *)menuView {
    if (!_menuView) {
        _menuView = [[UIView alloc] initWithFrame:CGRectMake(-menuWidth, 20, menuWidth, self.view.frame.size.height - self.tabBarController.tabBar.frame.size.height)];
        _menuView.backgroundColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1.0];
    }
    return _menuView;
}

// 3.設(shè)置表視圖
- (UITableView *)menuTable {
    if (!_menuTable) {
        _menuTable = [[UITableView alloc] initWithFrame:self.menuView.bounds style:UITableViewStylePlain];
        _menuTable.backgroundColor = [UIColor clearColor];
        _menuTable.alpha = 1.0;
        _menuTable.scrollEnabled = NO;
        _menuTable.separatorStyle = UITableViewCellSeparatorStyleNone;
        
        _menuTable.delegate = self;
        _menuTable.dataSource = self;
    }
    return _menuTable;
}

// 4.初始化UIDynamicAnimator
- (UIDynamicAnimator *)animator {
    if (!_animator) {
        _animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    }
    return _animator;
}

上述代碼的分步說明如下:

  1. 背景視圖大小與當(dāng)前視圖大小一致慕淡,設(shè)置為透明色。
  2. 菜單視圖寬度為窗口寬度一半沸毁,初始化時使其位于左側(cè)不可見區(qū)域峰髓。
  3. 設(shè)置表視圖大小和菜單視圖大小一致,初始化完成后將其添加到菜單視圖息尺。
  4. 初始化UIDynamicAnimator携兵。

上面的代碼都很簡單,就不再詳細(xì)說明搂誉。Xcode此時會發(fā)出警告徐紧,這是因為我們已經(jīng)將我們的類設(shè)置為表視圖的委托和數(shù)據(jù)源,但卻沒有遵守各自的協(xié)議炭懊。在interface后添加協(xié)議后代碼如下:

@interface SecondViewController () <UITableViewDelegate, UITableViewDataSource>

遵守上述協(xié)議后并级,Xcode會提示沒有實現(xiàn)必需實現(xiàn)的協(xié)議方法。現(xiàn)在實現(xiàn)所有我們需要的方法侮腹,其中表視圖共五行嘲碧,每行一個選項,點擊每行時其狀態(tài)從選中變?yōu)槿∠x中父阻。

#pragma mark - UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 5;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath];
    
    cell.textLabel.text = [NSString stringWithFormat:@"Option %li",indexPath.row + 1];
    cell.textLabel.textColor = [UIColor lightGrayColor];
    cell.textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
    cell.textLabel.textAlignment = NSTextAlignmentCenter;
    cell.backgroundColor = [UIColor clearColor];
    
    return cell;
}

#pragma mark - UITableViewDelegate

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 50;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [[tableView cellForRowAtIndexPath:indexPath] setSelected:NO];
}

開始下一步之前愈涩,在實現(xiàn)部分前定義cell的標(biāo)識符如下:

@end

#define menuWidth self.view.frame.size.width
static NSString * const reuseIdentifier = @"CellIdentifier";

@implementation SecondViewController

進(jìn)入viewDidLoad方法望抽,將背景視圖和菜單視圖添加到視圖控制器視圖,將表視圖添加到菜單視圖履婉,最后注冊cell糠聪。更新后如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 添加背景視圖 菜單視圖 表視圖
    [self.view addSubview:self.backgroundView];
    [self.view addSubview:self.menuView];
    [self.menuView addSubview:self.menuTable];
    
    // 注冊cell
    [self.menuTable registerClass:[UITableViewCell class] forCellReuseIdentifier:reuseIdentifier];
}

backgroundViewmenuView添加到視圖控制器視圖上,menuTable添加到menuView上谐鼎。

現(xiàn)在開始實現(xiàn)彈出舰蟆、隱藏動畫部分。如果我們仔細(xì)考慮一下狸棍,你會發(fā)現(xiàn)菜單視圖彈出和隱藏的動畫是相同的身害,只是方向相反。這意味著可以在一個方法內(nèi)定義動力行為草戈,其方向根據(jù)menuView所處狀態(tài)而定塌鸯。

在實現(xiàn)之前,先看一下要顯示菜單的動力行為唐片。首先丙猬,需要一個碰撞行為,以便讓視圖在指定邊界發(fā)生碰撞费韭,而不是從屏幕一邊滑動到另一邊茧球。其次,添加方向向右的重力行為星持,以便讓menuView看起來像被邊界拖拽著滑動抢埋。再添加一個UIDynamicItemBehavior屬性中的elasticity,這樣看起來就很好了督暂。但還可以添加一個UIPushBehaviormenuView移動的快一些揪垄。在隱藏menuView時使用相同動力行為,只是方向相反逻翁。

我們將在實現(xiàn)部分添加以下方法饥努,用于實現(xiàn)動畫的主要部分。

- (void)toggleMenu:(BOOL)shouldOpenMenu {
    // 移除所有動力行為
    [self.animator removeAllBehaviors];
    
    // 根據(jù)參數(shù)shouldOpenMenu 獲取重力方向 推力方向 邊界位置
    CGFloat gravityDirectionX = shouldOpenMenu ? 1.0 : -1.0;
    CGFloat pushMagnitude = shouldOpenMenu ? 20.0 : -20.0;
    CGFloat boundaryPointX = shouldOpenMenu ? menuWidth : -menuWidth;
    
    // 添加重力行為
    UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.menuView]];
    gravityBehavior.gravityDirection = CGVectorMake(gravityDirectionX, 0);
    [self.animator addBehavior:gravityBehavior];
    
    // 添加碰撞行為
    UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.menuView]];
    [collisionBehavior addBoundaryWithIdentifier:@"menuBoundary" fromPoint:CGPointMake(boundaryPointX, 20) toPoint:CGPointMake(boundaryPointX, self.tabBarController.tabBar.frame.origin.y)];
    [self.animator addBehavior:collisionBehavior];
    
    // 添加推力行為
    UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.menuView] mode:UIPushBehaviorModeInstantaneous];
    pushBehavior.magnitude = pushMagnitude;
    [self.animator addBehavior:pushBehavior];
    
    // 設(shè)置menuView的elasticity屬性
    UIDynamicItemBehavior *menuViewBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.menuView]];
    menuViewBehavior.elasticity = 0.4;
    [self.animator addBehavior:menuViewBehavior];
    
    //  設(shè)置backgroundView alpha值
    self.backgroundView.alpha = shouldOpenMenu ? 0.5 : 0;
}

上述代碼每一步都很簡單八回,這里有以下幾點需要注意:

  1. 在代碼開始部分酷愧,移除animator內(nèi)所有動力行為。因為當(dāng)使用相反手勢辽社,隱藏menuView時伟墙,需要添加的行為與已存在的行為沖突,同時存在動畫不能正常運(yùn)行滴铅。
  2. 初始化推力行為中參數(shù)mode:有兩個可用參數(shù),UIPushBehaviorModeInstantaneous表示施加瞬時力就乓,即一個沖量汉匙;UIPushBehaviorModeContinuous表示施加連續(xù)的力拱烁。
  3. backgroundViewalpha由現(xiàn)在所處狀態(tài)決定。

如果你想要施加的重力需要結(jié)合物體自身質(zhì)量噩翠,請使用后面講到的UIFieldBehavior戏自,場行為支持線性和徑向引力場,其引力大小和動力項自身質(zhì)量有關(guān)伤锚,且力大小與距場行為中心遠(yuǎn)近相關(guān)擅笔,導(dǎo)致施加到不同物體上的力大小不同。

在主屏幕上使用手勢右滑屯援,menuView出現(xiàn)猛们;在menuView上左滑,menuView隱藏狞洋。最方便的方法就是當(dāng)手勢觸發(fā)時調(diào)用同一個方法弯淘,在調(diào)用方法內(nèi)根據(jù)手勢方向調(diào)用toggleMenu:方法,并設(shè)置對應(yīng)參數(shù)吉懊。

進(jìn)入viewDidLoad庐橙,添加手勢。

- (void)viewDidLoad {
    ...
    // 添加右滑手勢
    UISwipeGestureRecognizer *showMenuGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
    showMenuGesture.direction = UISwipeGestureRecognizerDirectionRight;
    [self.view addGestureRecognizer:showMenuGesture];
    
    // 添加左滑手勢
    UISwipeGestureRecognizer *hideMenuGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
    hideMenuGesture.direction = UISwipeGestureRecognizerDirectionLeft;
    [self.menuView addGestureRecognizer:hideMenuGesture];
}

上面添加手勢的代碼有兩點不同借嗽。其一态鳖,手勢方向相反;其二恶导,手勢添加到的視圖不同郁惜,右滑手勢添加到view,左滑手勢添加到menuView甲锡。你也可以在視圖控制器視圖上再添加一個左滑手勢兆蕉,方便用戶在主視圖上左滑隱藏menuView,如果遇到問題缤沦,可以通過文章底部網(wǎng)址下載demo源碼查看虎韵。

在實現(xiàn)部分實現(xiàn)手勢中handleGesture:方法,根據(jù)手勢方向為toggleMenu:設(shè)置不同參數(shù)缸废。

- (void)handleGesture:(UISwipeGestureRecognizer *)gesture {
    if (gesture.direction == UISwipeGestureRecognizerDirectionRight) {
        [self toggleMenu:YES];
    } else {
        [self toggleMenu:NO];
    }
}

現(xiàn)在運(yùn)行app包蓝,嘗試使用手勢彈出、隱藏menuView企量,也可以自行修改其他屬性测萎。

Dynamics2MenuTable.gif

這篇文章將多次用到手勢識別器(UIGestureRecognizer),如果你還不熟悉届巩,可以查看我的另一篇文章:手勢控制:點擊硅瞧、滑動、平移恕汇、捏合腕唧、旋轉(zhuǎn)或辖、長按、輕掃

4. 附著行為UIAttachmentBehavior

在這一部分枣接,將添加UIPushBehavior颂暇、UIAttachmentBehaviorUIDynamicItemBehavior幾種動力行為到image,隨后拖動image產(chǎn)生如下效果:

Dynamics3UIPushBehavior.gif

4.1 構(gòu)建界面

下載圖片并添加到Assets.xcassets但惶。進(jìn)入到ThirdViewController.m耳鸯,添加以下聲明。

@interface ThirdViewController ()

@property (strong, nonatomic) UIImageView *imageView;
@property (strong, nonatomic) UIView *redView;
@property (strong, nonatomic) UIView *blueView;
@property (assign, nonatomic) CGRect orangeBounds;
@property (assign, nonatomic) CGPoint orangeCenter;
@property (strong, nonatomic) UIDynamicAnimator *animator;
@property (strong, nonatomic) UIAttachmentBehavior *attachmentBehavior;
@property (strong, nonatomic) UIPushBehavior *pushBehavior;
@property (strong, nonatomic) UIDynamicItemBehavior *itemBehavior;

@end

redViewblueView用于表示UIDynamics物理引擎動畫的點膀曾,blueView表示觸摸開始的點县爬,redView表示表示手指觸摸點,也是imageView的錨點妓肢。

使用懶加載初始化捌省、配置前三個視圖屬性。

- (UIImageView *)imageView {
    if (!_imageView) {
        CGPoint center = self.view.center;
        _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 240, 240)];
        _imageView.center = CGPointMake(center.x, center.y*2/3);
        _imageView.image = [UIImage imageNamed:@"AppleLogo.jpg"];
        _imageView.userInteractionEnabled = YES;
    }
    return _imageView;
}

- (UIView *)redView {
    if (!_redView) {
        _redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
        _redView.center = CGPointMake(self.view.center.x, self.view.center.y*2/3);
        _redView.backgroundColor = [UIColor redColor];
    }
    return _redView;
}

- (UIView *)blueView {
    if (!_blueView) {
        _blueView = [[UIView alloc] initWithFrame:CGRectMake(80, 400, 10, 10)];
        _blueView.backgroundColor = [UIColor blueColor];
    }
    return _blueView;
}

將上面初始化的屬性添加到view碉钠。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.view addSubview:self.imageView];
    [self.view addSubview:self.redView];
    [self.view addSubview:self.blueView];
}

viewDidLoad中為imageView添加平移手勢識別器UIPanGestureRecognizer纲缓,用于拖動圖片視圖。

- (void)viewDidLoad {
    ...
    // 為imageView添加平移手勢識別器
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleAttachmentGesture:)];
    [self.imageView addGestureRecognizer:pan];
}

imageView識別到平移手勢時喊废,會調(diào)用以下方法祝高。在該方法內(nèi)用viewimageView不同坐標(biāo)系統(tǒng)輸出當(dāng)前手勢位置。

- (void)handleAttachmentGesture:(UIPanGestureRecognizer *)panGesture {
    CGPoint location = [panGesture locationInView:self.view];
    CGPoint boxLocation = [panGesture locationInView:self.imageView];
    
    switch (panGesture.state) {
        // 手勢開始
        case UIGestureRecognizerStateBegan:
            NSLog(@"Touch started position: %@",NSStringFromCGPoint(location));
            NSLog(@"Location in image started is %@",NSStringFromCGPoint(boxLocation));
            break;
            
        // 手勢結(jié)束
        case UIGestureRecognizerStateEnded:
            NSLog(@"Touch ended position: %@",NSStringFromCGPoint(location));
            NSLog(@"Location in image ended is %@",NSStringFromCGPoint(boxLocation));
            break;
            
        default:
            break;
    }
}

現(xiàn)在運(yùn)行app污筷,拖動視圖上的Apple logo工闺,可以看到控制臺輸出如下:

Touch started position: {237.66665649414062, 294.66665649414062}
Location in image started is {150.66665649414062, 169.33332316080728}
Touch ended position: {144, 288.66665649414062}
Location in image ended is {57, 163.33332316080728}

4.2 添加UIDynamicAnimatorUIAttachmentBehavior

現(xiàn)在要做的就是通過使用UIAttachmentBehaviorimageView隨手指移動。在此之前瓣蛀,先將imageView的frame保存到屬性中陆蟆。更新后viewDidLoad方法如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.orangeBounds = self.imageView.bounds;
    self.orangeCenter = self.imageView.center;
    ...
}

當(dāng)手勢識別器識別到手勢開始時,初始化UIAttachmentBehavior惋增,更新redViewblueView位置叠殷。最后添加UIAttachmentBehaviorUIDynamicAnimator。更新后的handleAttachmentGesture:方法如下:

- (void)handleAttachmentGesture:(UIPanGestureRecognizer *)panGesture {
    ...
    switch (panGesture.state) {
        // 手勢開始
        case UIGestureRecognizerStateBegan:
//            NSLog(@"Touch started position: %@",NSStringFromCGPoint(location));
//            NSLog(@"Location in image started is %@",NSStringFromCGPoint(boxLocation));
            // 1.移除所有動力行為
            [self.animator removeAllBehaviors];
                
            // 2.通過改變錨點移動imageView
            UIOffset centerOffset = UIOffsetMake(boxLocation.x - CGRectGetMidX(self.imageView.bounds), boxLocation.y - CGRectGetMidY(self.imageView.bounds));
            self.attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:self.imageView offsetFromCenter:centerOffset attachedToAnchor:location];
            
            // 3.redView中心設(shè)置為attachmentBehavior的anchorPoint  blueView的中心設(shè)置為手勢手勢開始的位置
            self.redView.center = self.attachmentBehavior.anchorPoint;
            self.blueView.center = location;
            
            // 4.添加附著行為到animator
            self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
            [self.animator addBehavior:self.attachmentBehavior];
            
            break;
            
        // 手勢結(jié)束
        ...
}

上述代碼分布說明如下:

  1. 首先移除已存在的動力行為诈皿,不然動力行為間會產(chǎn)生沖突林束。
  2. 先計算出手勢與imageView中心偏移,初始化attachmentBehavior稽亏。
  3. redView中心設(shè)置為attachmentBehavioranchorPoint壶冒,blueView的中心設(shè)置為手勢手勢開始的位置location
  4. 初始化animator時指定其坐標(biāo)系統(tǒng)為self.view截歉,添加附著行為到animator胖腾。

現(xiàn)在讓anchorPoint跟隨手指移動,在上面代碼后添加如下代碼:

- (void)handleAttachmentGesture:(UIPanGestureRecognizer *)panGesture {
        ...  
        // 手勢改變
        case UIGestureRecognizerStateChanged:
            self.attachmentBehavior.anchorPoint = [panGesture locationInView:self.view];
            self.redView.center = self.attachmentBehavior.anchorPoint;
            break;
            
        // 手勢結(jié)束
        ...
}

上面的代碼可以讓anchorPointredView中心與當(dāng)前手指位置一致,當(dāng)手指移動時胸嘁,手勢識別器調(diào)用該方法更新anchorPoint瓶摆,animator會自動動態(tài)更新視圖位置凉逛。如果手動更新了動力項位置性宏,則需要使用animator調(diào)用updateItemUsingCurrentState:方法更新動力項位置。

運(yùn)行demo状飞,拖拽圖片毫胜,顯示如下:

Dynamics3UIAttachmentBehavior.gif

在拖拽結(jié)束后,讓imageView恢復(fù)到原來位置更美觀一些诬辈。在實現(xiàn)部分添加如下方法:

- (void)resetDemo {
    [self.animator removeAllBehaviors];
    
    [UIView animateWithDuration:0.5 animations:^{
        self.imageView.bounds = self.originalBounds;
        self.imageView.center = self.originalCenter;
        self.imageView.transform = CGAffineTransformIdentity;
    }];
}

handleAttachmentGesture:方法UIGestureRecognizerStateEnded部分調(diào)用該方法酵使。

- (void)handleAttachmentGesture:(UIPanGestureRecognizer *)panGesture {
        ...    
        // 手勢結(jié)束
        case UIGestureRecognizerStateEnded:
//            NSLog(@"Touch ended position: %@",NSStringFromCGPoint(location));
//            NSLog(@"Location in image ended is %@",NSStringFromCGPoint(boxLocation));
            [self resetDemo];
            break;
            
        default:
            break;
    }
}

運(yùn)行demo,這次拖拽完畢imageView會自動回到初始位置焙糟。

Dynamics3AttachmentBack.gif

4.3 添加UIPushBehavior

在這一部分口渔,需要在停止拖動時分離imageView,并施加沖量穿撮,以便在釋放時可以繼續(xù)其軌跡缺脉。

在實現(xiàn)部分前添加以下兩個常量。

@end

static CGFloat const ThrowingThreshold = 1000;
static CGFloat const ThrowingVelocityPadding = 15;

@implementation ThirdViewController

ThrowingThreshold表示imageView要想繼續(xù)移動而不立即返回到原始位置所需要的最低速度悦穿。ThrowingVelocityPadding是一個魔術(shù)數(shù)字攻礼,用于影響推力大小。

繼續(xù)在handleAttachmentGesture:方法栗柒,更新UIGestureRecognizerStateEnded內(nèi)代碼后如下:

- (void)handleAttachmentGesture:(UIPanGestureRecognizer *)panGesture {
        ...   
        // 手勢結(jié)束
        case UIGestureRecognizerStateEnded:
//            NSLog(@"Touch ended position: %@",NSStringFromCGPoint(location));
//            NSLog(@"Location in image ended is %@",NSStringFromCGPoint(boxLocation));
            // 1.移除attachmentBehavior
            [self.animator removeBehavior:self.attachmentBehavior];
            
            // 2.當(dāng)前運(yùn)行速度 推動行為力向量大小
            CGPoint velocity = [panGesture velocityInView:self.view];
            CGFloat magnitude = sqrtf(velocity.x * velocity.x + velocity.y * velocity.y);
            
            if (magnitude > ThrowingThreshold) {
                // 3.添加pushBehavior
                UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.imageView] mode:UIPushBehaviorModeInstantaneous];
                pushBehavior.pushDirection = CGVectorMake(velocity.x / 10, velocity.y / 10);
                pushBehavior.magnitude = magnitude / ThrowingVelocityPadding;
                
                self.pushBehavior = pushBehavior;
                [self.animator addBehavior:self.pushBehavior];
                
                // 4.修改itemBehavior屬性 以便讓imageView產(chǎn)生飛起來的感覺
                NSInteger angle = arc4random_uniform(20) - 10;
                
                self.itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.imageView]];
                self.itemBehavior.friction = 0.2;
                self.itemBehavior.allowsRotation = YES;
                [self.itemBehavior addAngularVelocity:angle forItem:self.imageView];
                [self.animator addBehavior:self.itemBehavior];
                
                // 5.一定時間后 imageView回到初始位置
                [self performSelector:@selector(resetDemo) withObject:nil afterDelay:0.3];
            }
            else
            {
                [self resetDemo];
            }
            
            break;
            
        default:
            break;
    }
}

上述代碼的分布說明如下:

  1. 移除attachmentBehavior礁扮,否則手勢停止時imageView會立即停止。這里的移除不需要判斷被移除項是否存在瞬沦,如果被移除項為nil太伊,或不是該animator的動力項,animator會自動忽略該移除方法。
  2. 通過手勢獲得當(dāng)前速度构资,計算出力大小乾闰。
  3. imageView與手勢分離時的速度大于ThrowingThreshold,添加UIPushBehavior叠赐。這里的推力為瞬時力,所以mode:參數(shù)為UIPushBehaviorModeInstantaneous屡江。力的方向根據(jù)向量的xy決定芭概。最后添加pushBehavioranimator
  4. 這一部分設(shè)置UIDynamicItemBehavior屬性惩嘉,讓imageView產(chǎn)生飛起來的感覺罢洲。你可以自行修改其他屬性,體會各種屬性產(chǎn)生的不同效果。
  5. 通過performSelector: withObject: afterDelay:方法惹苗,在一定時間后殿较,讓imageView回歸到初始位置。

運(yùn)行demo桩蓉,可以看到效果如下:

Dynamics3UIPushBehavior.gif

5. 吸附行為UISnapBehavior

使用UISnapBehavior可以把視圖吸附到一個新位置淋纲,視圖移動的過程就像受彈簧拉力而動。在這一部分院究,我們將通過點擊屏幕將視圖吸附到點擊處洽瞬。

進(jìn)入到FourthViewController.m,聲明以下屬性业汰。

@interface FourthViewController ()

@property (strong, nonatomic) UIView *purpleView;
@property (strong, nonatomic) UIDynamicAnimator *animator;
@property (strong, nonatomic) UISnapBehavior *snapBehavior;

@end

初始化purpleViewanimator伙窃,并設(shè)置purpleView背景顏色為purpleColor

- (UIView *)purpleView {
    if (!_purpleView) {
        _purpleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
        _purpleView.center = self.view.center;
        _purpleView.backgroundColor = [UIColor purpleColor];
    }
    return _purpleView;
}

- (UIDynamicAnimator *)animator {
    if (!_animator) {
        _animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    }
    return _animator;
}

viewDidLoad方法中添加點擊手勢識別器样漆,手指點擊的位置用于吸附purpleView为障。在添加點擊手勢前,添加purpleViewview放祟。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.view addSubview:self.purpleView];
    
    // 添加點擊手勢
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
    [self.view addGestureRecognizer:tapGesture];
}

當(dāng)用戶點擊屏幕時鳍怨,點擊手勢識別器會調(diào)用handleTapGesture:方法,下面實現(xiàn)該方法舞竿。

- (void)handleTapGesture:(UITapGestureRecognizer *)tapGesture {
    // 1.獲得點擊屏幕位置
    CGPoint touchPoint = [tapGesture locationInView:self.view];
    
    // 2.snapBehavior存在時 移除
    if (self.snapBehavior) {
        [self.animator removeBehavior:self.snapBehavior];
    }
    
    // 3.初始化snapBehavior 設(shè)定damping值
    self.snapBehavior = [[UISnapBehavior alloc] initWithItem:self.purpleView snapToPoint:touchPoint];
    self.snapBehavior.damping = 0.3;
    [self.animator addBehavior:self.snapBehavior];
}

上述代碼的分布說明如下:

  1. 獲得點擊屏幕位置京景。
  2. 因為只能存在一個激活的snapBehavior,當(dāng)snapBehavior存在時骗奖,將其從animator移除确徙。這里也可不做判斷直接移除。
  3. 初始化snapBehavior执桌,初始化時指定動力項為purpleView鄙皇,snapToPoint:參數(shù)指定吸附點為屏幕點擊位置。減震系數(shù)damping值范圍是0.01.0仰挣,值越小震動幅度越大伴逸,默認(rèn)值是0.5。最后添加snapBehavioranimator膘壶。

運(yùn)行demo错蝴,點擊屏幕任意位置查看效果。

Dynamics4UISnapBehavior.gif

6. 場行為UIFieldBehavior

UIFieldBehavior將基于場的物理學(xué)應(yīng)用于動力項對象颓芭,場行為定義了可以應(yīng)用諸如重力(gravity)顷锰、磁力(magnetism)、阻力(drag)亡问、速度(velocity)官紫、湍流(turbulence)等力的區(qū)域。使用UIFieldBehavior時,要先創(chuàng)建所需場行為束世,之后配置場強(qiáng)度(strength)屬性和其他所需屬性酝陈。

創(chuàng)建UIFieldBehavior后,使用addItem:方法將該場行為與動力項關(guān)聯(lián)起來毁涉。對于很多場行為沉帮,你還必須為動力項配置UIDynamicItemBehavior屬性以便讓場可以對其施加作用,如density屬性薪丁。最后將配置好的UIFieldBehavior添加到UIDynamicAnimator以便在用戶界面執(zhí)行動畫遇西。

場行為的position屬性用于定義場行為在用戶界面中的位置馅精,場行為的region屬性定義了場行為作用的區(qū)域严嗜。region區(qū)域以position為中心,region可以是圓形或長方形洲敢,也可以用不同方式組建區(qū)域復(fù)雜的region漫玄。

所有場行為都有strength屬性,用于定義場強(qiáng)度压彭。大部分場行為也會用到falloff屬性睦优,使場強(qiáng)隨距離變化。根據(jù)場的類型和需要配置其他屬性壮不,如minimumRadius汗盘、directionsmoothnessanimationSpeed等屬性询一。

在這里將創(chuàng)建radialGravityFieldvortexField兩個場行為隐孽,并與UIView對象blueView關(guān)聯(lián)起來。因這里場行為的類型健蕊,需要使用UIDynamicItemBehavior對象配置blueViewdensity屬性菱阵。

進(jìn)入FifthViewController.m,聲明以下屬性缩功。

@interface FifthViewController ()

@property (strong, nonatomic) UIView *blueView;
@property (strong, nonatomic) UIDynamicAnimator *animator;
@property (strong, nonatomic) UIFieldBehavior *radialGravityField;
@property (strong, nonatomic) UIFieldBehavior *vortexField;

@end

使用懶加載配置blueViewanimator晴及,初始化后在viewDidLoad方法中將blueView添加到view

- (UIView *)blueView {
    if (!_blueView) {
        _blueView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
        _blueView.center = CGPointMake(self.view.center.x*2/3, self.view.center.y*2/3);
        _blueView.layer.cornerRadius = _blueView.frame.size.width/2;
        _blueView.layer.backgroundColor = [UIColor blueColor].CGColor;
    }
    return _blueView;
}

- (UIDynamicAnimator *)animator {
    if (!_animator) {
        _animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    }
    return _animator;
}

使用懶加載初始化radialGravityField嫡锌。

- (UIFieldBehavior *)radialGravityField {
    if (!_radialGravityField) {
        // 1.使用類方法初始化radialGravityField
        _radialGravityField = [UIFieldBehavior radialGravityFieldWithPosition:self.view.center];
        // 2.指定radialGravityField區(qū)域
        _radialGravityField.region = [[UIRegion alloc] initWithRadius:300];
        // 3.設(shè)置場強(qiáng)
        _radialGravityField.strength = 1.5;
        // 4.場強(qiáng)隨距離變化
        _radialGravityField.falloff = 4.0;
        // 5.場強(qiáng)變化的最小半徑
        _radialGravityField.minimumRadius = 50.0;
    }
    return _radialGravityField;
}

上述代碼分步說明如下:

  1. 使用類方法radialGravityFieldWithPosition:初始化radialGravityBehavior虑稼,模擬地心引力,指定場行為的位置在視圖的中心势木。
  2. region指定場行為作用區(qū)域為半徑300point的圓蛛倦。radialGravityBehavior對區(qū)域外動力項不能施加作用。
  3. 設(shè)定場強(qiáng)為1.5跟压,strength的默認(rèn)值為1.0胰蝠。相同strength值在不同類型場行為中有不同作用力大小,確定該值最佳方法是嘗試不同值,直到獲得所需的效果茸塞。
  4. fallOff定義了場強(qiáng)strength隨距離場行為中心距離增加而減小的速度躲庄。距離每超過minimumRadius時,falloff會施加一次作用钾虐。該屬性默認(rèn)值是0噪窘,產(chǎn)生場強(qiáng)不會隨距離變化的均勻區(qū)域。在這里效扫,設(shè)置fallOff值為4.0倔监。
  5. 開始計算場強(qiáng)度新值的最小距離。距離小于minimumRadius時菌仁,按照等于minimumRadius計算浩习。除此之外,只有到達(dá)最小距離時falloff才會有效济丘。minimumRadius默認(rèn)值很小谱秽,但不為零。在這里摹迷,設(shè)置minimumRadius值為50.0疟赊。

初始化vortexField

- (UIFieldBehavior *)vortexField {
    if (!_vortexField) {
        // 1.初始化vortexField
        _vortexField = [UIFieldBehavior vortexField];
        // 2.設(shè)置position為視圖中心
        _vortexField.position = self.view.center;
        
        _vortexField.region = [[UIRegion alloc] initWithRadius:200];
        _vortexField.strength = 0.005;
    }
    return _vortexField;
}

上述代碼的分布說明如下:

  1. 使用類方法vortexField初始化vortexField峡碉,模擬渦流力近哟。
  2. 設(shè)定vortexFieldposition為視圖控制器中心。

viewDidAppear:方法添加動力項到動力行為鲫寄,將動力行為添加到animator吉执。

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    
    // 1.開啟調(diào)試模式
    [self.animator setValue:[NSNumber numberWithBool:YES] forKey:@"debugEnabled"];
    
    // 2.配置動力項密度
    UIDynamicItemBehavior *blueViewBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.blueView]];
    blueViewBehavior.density = 0.5;
    [self.animator addBehavior:blueViewBehavior];
    
    // 3.添加動力項到動力行為
    [self.vortexField addItem:self.blueView];
    [self.radialGravityField addItem:self.blueView];
    
    // 4.添加動力行為到animator
    [self.animator addBehavior:self.vortexField];
    [self.animator addBehavior:self.radialGravityField];
}

在1中開啟調(diào)試模式,可以讓我們看到場行為的強(qiáng)度和方向(下圖中紅線)塔拳。強(qiáng)度越大鼠证,紅色虛線越顯著。下圖1strength1.5靠抑,圖2strength50量九,其他屬性相同。

Dynamics5FieldBehaviorStrength.png

運(yùn)行demo颂碧,顯示如下:

Dynamics5UIFieldBehavior.gif

你可以添加其他場行為到blueView荠列,查看各種場行為的效果。

總結(jié)

UIKit Dynamics是一個非常有用的庫载城,利用系統(tǒng)提供的各種動力行為并設(shè)置適當(dāng)?shù)膶傩约∷疲梢詾閁I添加很多逼真動畫。使用物理引擎是要耗費一定CPU資源的诉瓦,特別是在碰撞檢測時川队。使用中可以組合使用多種動力行為力细,但不要讓彼此沖突。UIKit Dynamics可以與他動畫結(jié)合使用固额,以創(chuàng)建更復(fù)雜的UI動畫眠蚂。

Demo名稱:UIKitDynamics
源碼地址:https://github.com/pro648/BasicDemos-iOS

參考資料:

  1. iOS 7 UIKit Dynamic 學(xué)習(xí)總結(jié)
  2. WWDC 2013 Session筆記 - UIKit Dynamics入門
  3. Adding Animated Effects to iOS App Using UIKit Dynamics
  4. How To Toss Views Using UIKit Dynamics
  5. Add Snap Behaviour Tutorial in iOS8 with Swift
  6. Dynamic animations: UIFieldBehavior

歡迎更多指正:https://github.com/pro648/tips/wiki

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市斗躏,隨后出現(xiàn)的幾起案子逝慧,更是在濱河造成了極大的恐慌,老刑警劉巖啄糙,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笛臣,死亡現(xiàn)場離奇詭異,居然都是意外死亡隧饼,警方通過查閱死者的電腦和手機(jī)沈堡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桑李,“玉大人踱蛀,你說我怎么就攤上這事」蟀祝” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵崩泡,是天一觀的道長禁荒。 經(jīng)常有香客問我,道長角撞,這世上最難降的妖魔是什么呛伴? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮谒所,結(jié)果婚禮上热康,老公的妹妹穿的比我還像新娘。我一直安慰自己劣领,他們只是感情好姐军,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尖淘,像睡著了一般奕锌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上村生,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天惊暴,我揣著相機(jī)與錄音,去河邊找鬼趁桃。 笑死辽话,一個胖子當(dāng)著我的面吹牛肄鸽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播油啤,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼贴捡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了村砂?” 一聲冷哼從身側(cè)響起烂斋,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎础废,沒想到半個月后汛骂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡评腺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年帘瞭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒿讥。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡蝶念,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出芋绸,到底是詐尸還是另有隱情媒殉,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布摔敛,位于F島的核電站廷蓉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏马昙。R本人自食惡果不足惜桃犬,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望行楞。 院中可真熱鬧攒暇,春花似錦、人聲如沸子房。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽池颈。三九已至尾序,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間躯砰,已是汗流浹背每币。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留琢歇,地道東北人兰怠。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓梦鉴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親揭保。 傳聞我的和親對象是個殘疾皇子肥橙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容