iOS之UIView動(dòng)畫

UIView動(dòng)畫.png

概述

在AppStore中的應(yīng)用越來越重視動(dòng)畫效果的使用,一個(gè)良好動(dòng)畫效果可以讓兩個(gè)狀態(tài)之間平滑地過度,也可以利用動(dòng)畫吸引住用戶的眼球,在UIView類中共有三個(gè)類目(Category)用于實(shí)現(xiàn)動(dòng)畫功能,分為UIViewAnimation、UIViewAnimationWithBlocks以及UIViewKeyframeAnimations,他們是Apple對(duì)核心動(dòng)畫(Core Animation)的封裝,可以讓我們不進(jìn)行任何繪畫等復(fù)雜操作的前提下實(shí)現(xiàn)大部分動(dòng)畫需求

UIView動(dòng)畫的用處

UIView類中提供的動(dòng)畫功能,允許我們實(shí)現(xiàn)如下動(dòng)畫需求

  • 動(dòng)態(tài)修改"可動(dòng)畫屬性"
    • frame(位置及大小)
    • bounds(大小)
    • center(中心點(diǎn)位置)
    • transform(形變)
    • alpha(透明度)
    • backgroundColor(背景顏色)
    • contentStretch(可拉伸區(qū)域,從iOS6開始被廢棄)
  • 提供轉(zhuǎn)場動(dòng)畫效果
    • 修改已存在視圖的子視圖(也可以修改自身)
    • 在視圖層級(jí)中用一個(gè)視圖替換另一個(gè)視圖(Block動(dòng)畫提供)
本文中樣例素材

在介紹UIView動(dòng)畫的過程中,我們會(huì)使用一個(gè)UIImageView圖片為例,對(duì)各動(dòng)畫效果進(jìn)行演示

@property (nonatomic, strong) UIImageView *demoImageView;

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.demoImageView = [[UIImageView alloc] initWithFrame:CGRectMake(20, 20, [[UIScreen mainScreen] bounds].size.width-40, [[UIScreen mainScreen] bounds].size.height-40)];
    self.demoImageView.image = [UIImage imageNamed:@"old"];
    [self.view addSubview:self.demoImageView];
}

UIViewAnimation

Apple自iOS2開始提供了若干UIView的類方法來實(shí)現(xiàn)動(dòng)畫功能,Apple建議從iOS4系統(tǒng)開始盡量使用Block方式來實(shí)現(xiàn)動(dòng)畫功能,因此我們以后在開發(fā)中應(yīng)該遵從Apple的建議使用Block方式來實(shí)現(xiàn)動(dòng)畫功能,但是學(xué)習(xí)本部分對(duì)未來學(xué)好Block動(dòng)畫有很好的鋪墊作用,因此本文在這里會(huì)對(duì)系統(tǒng)提供的方法進(jìn)行詳盡地介紹

在iOS4之前,我們需要使用動(dòng)畫塊(begin/commit animation block)來進(jìn)行動(dòng)畫,一個(gè)完整的動(dòng)畫塊由以下四部分組成

  • 動(dòng)畫塊開始標(biāo)識(shí)
  • 配置動(dòng)畫選擇項(xiàng)
  • 修改可動(dòng)畫屬性
  • 動(dòng)畫塊結(jié)束標(biāo)識(shí)
動(dòng)畫塊開始標(biāo)識(shí)

標(biāo)識(shí)動(dòng)畫塊的開始,該方法是告訴系統(tǒng)將要執(zhí)行一個(gè)或多個(gè)動(dòng)畫,如果通過setAnimationWillStartSelector:或者setAnimationDidStopSelector:方法設(shè)置了代理回調(diào)方法,便可以在回調(diào)方法中收到該方法中的兩個(gè)參數(shù)animationID(動(dòng)畫標(biāo)志)和context(上下文,一般為nil)

// 格式
+ (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context;
// 樣例
[UIView beginAnimations:@"demo" context:nil];
動(dòng)畫塊結(jié)束標(biāo)識(shí)

標(biāo)識(shí)動(dòng)畫塊的結(jié)束,同時(shí)安排動(dòng)畫執(zhí)行

// 格式
+ (void)commitAnimations;
// 樣例
[UIView commitAnimations];
設(shè)置動(dòng)畫代理對(duì)象及代理回調(diào)方法

設(shè)置動(dòng)畫代理對(duì)象,以便在動(dòng)畫開始和結(jié)束時(shí)收到系統(tǒng)回調(diào)

// 格式
+ (void)setAnimationDelegate:(nullable id)delegate; 
// 樣例
[UIView setAnimationDelegate:self];

設(shè)置動(dòng)畫將要開始時(shí)調(diào)用代理對(duì)象的方法

// 格式
+ (void)setAnimationWillStartSelector:(nullable SEL)selector; 
// 樣例
[UIView setAnimationWillStartSelector:@selector(animationWillStart:context:)];

注1: 當(dāng)動(dòng)畫當(dāng)前不可用時(shí),該方法不會(huì)被調(diào)用

注2: 當(dāng)不設(shè)置該方法時(shí),系統(tǒng)默認(rèn)會(huì)調(diào)用- (void)animationWillStart:(NSString *)animationID context:(void *)context

設(shè)置動(dòng)畫已經(jīng)結(jié)束后調(diào)用代理對(duì)象的方法

// 格式
+ (void)setAnimationDidStopSelector:(nullable SEL)selector; 
// 樣例
[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];

注1: 當(dāng)動(dòng)畫當(dāng)前不可用時(shí),該方法依然會(huì)被調(diào)用

注2: 當(dāng)不設(shè)置該方法時(shí),系統(tǒng)默認(rèn)會(huì)調(diào)用- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context

設(shè)置動(dòng)畫持續(xù)時(shí)長

設(shè)置動(dòng)畫持續(xù)時(shí)長,默認(rèn)0.2s

// 格式
+ (void)setAnimationDuration:(NSTimeInterval)duration; 
// 樣例
[UIView setAnimationDuration:0.2f];

注: 該方法必須在修改"可變化屬性"之前調(diào)用

設(shè)置延時(shí)動(dòng)畫時(shí)長

設(shè)置延時(shí)動(dòng)畫時(shí)長,默認(rèn)0.0s

// 格式
+ (void)setAnimationDelay:(NSTimeInterval)delay; 
// 樣例
[UIView setAnimationDelay:0.0f];

注: 該方法必須在修改"可變化屬性"之前調(diào)用

設(shè)置動(dòng)畫開始時(shí)間(該方法設(shè)置后整個(gè)動(dòng)畫即不生效,不明原因)

設(shè)置動(dòng)畫開始時(shí)間,默認(rèn)當(dāng)前時(shí)間

// 格式
+ (void)setAnimationStartDate:(NSDate *)startDate; 
// 樣例
[UIView setAnimationStartDate:[NSDate date]];

注1: 該方法必須在修改"可變化屬性"之前調(diào)用

注2: 不要在Block動(dòng)畫中調(diào)用該方法

注3: 該方法設(shè)置后整個(gè)動(dòng)畫即不生效,作者嘗試過如下四種方式,結(jié)果都不生效

[UIView setAnimationStartDate:[NSDate date]];
[UIView setAnimationStartDate:[NSDate dateWithTimeIntervalSinceReferenceDate:CFAbsoluteTimeGetCurrent()]];

[UIView setAnimationStartDate:[NSDate dateWithTimeIntervalSinceNow:3]];
[UIView setAnimationStartDate:[NSDate dateWithTimeIntervalSinceReferenceDate:CFAbsoluteTimeGetCurrent()+3]];
設(shè)置時(shí)間函數(shù)

時(shí)間函數(shù)即動(dòng)畫進(jìn)行速度隨著時(shí)間的變化曲線,系統(tǒng)采用一個(gè)枚舉類型來保存

/**
 typedef NS_ENUM(NSInteger, UIViewAnimationCurve)
 {
     UIViewAnimationCurveEaseInOut // 默認(rèn),淡入淡出,先慢后快再慢
     UIViewAnimationCurveEaseIn // 淡入,先慢后快
     UIViewAnimationCurveEaseOut // 淡出,先快后慢
     UIViewAnimationCurveLinear // 線性
 };
 */

// 格式
+ (void)setAnimationCurve:(UIViewAnimationCurve)curve; 
// 樣例
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

注: 該方法必須在修改"可變化屬性"之前調(diào)用

設(shè)置動(dòng)畫重復(fù)次數(shù)

設(shè)置動(dòng)畫重復(fù)次數(shù),重復(fù)次數(shù)可以是小數(shù),小數(shù)部分會(huì)做相應(yīng)比例的動(dòng)畫,然后直接變化到動(dòng)畫結(jié)束位置

// 格式
+ (void)setAnimationRepeatCount:(float)repeatCount; 
// 樣例
[UIView setAnimationRepeatCount:2.5f];

注: 該方法必須在修改"可變化屬性"之前調(diào)用

設(shè)置是否反向動(dòng)畫

設(shè)置是否反向動(dòng)畫,默認(rèn)為NO,如果設(shè)置為YES,動(dòng)畫向前做完會(huì)做一次反向動(dòng)畫,那么一正一反算作一次repeatCount,動(dòng)畫所用時(shí)長會(huì)加倍

// 格式
+ (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses; 
// 樣例
[UIView setAnimationRepeatAutoreverses:YES];

注1: 該方法必須在修改"可變化屬性"之前調(diào)用

注2: 因?yàn)楫?dāng)動(dòng)畫結(jié)束后一定會(huì)定格在動(dòng)畫塊中設(shè)置的新位置上,為了不在最后產(chǎn)生一次不和諧的跳動(dòng),可以將repeatCount設(shè)置成0.5次即可

設(shè)置是否從當(dāng)前狀態(tài)開始動(dòng)畫

該方法用于處理當(dāng)舊動(dòng)畫正在運(yùn)行時(shí)開始一個(gè)新的動(dòng)畫,是否從舊動(dòng)畫的當(dāng)前狀態(tài)開始新動(dòng)畫,默認(rèn)為NO

  • 當(dāng)設(shè)置為NO時(shí),舊動(dòng)畫的結(jié)束狀態(tài)會(huì)被作為新動(dòng)畫的初始狀態(tài),且舊動(dòng)畫會(huì)在新動(dòng)畫開始的時(shí)候立刻結(jié)束動(dòng)畫
  • 當(dāng)設(shè)置為YES時(shí),舊動(dòng)畫的當(dāng)前狀態(tài)會(huì)被作為新動(dòng)畫的初始狀態(tài)
// 格式
+ (void)setAnimationBeginsFromCurrentState:(BOOL)fromCurrentState;
// 樣例
[UIView setAnimationBeginsFromCurrentState:NO];
設(shè)置轉(zhuǎn)場效果

設(shè)置轉(zhuǎn)場效果,該方法有如下三個(gè)參數(shù)

  • 參數(shù)1: 轉(zhuǎn)場動(dòng)畫方向,系統(tǒng)采用一個(gè)枚舉類型來保存
  • 參數(shù)2: 參與轉(zhuǎn)場動(dòng)畫的視圖(轉(zhuǎn)場動(dòng)畫效果發(fā)生在該視圖上)
  • 參數(shù)3: 如果設(shè)置為YES,則視圖在開始和結(jié)束時(shí)分別渲染一次,在動(dòng)畫過程中不可以更新視圖(這樣性能好),如果設(shè)置為NO,則視圖每一幀都會(huì)渲染,在動(dòng)畫過程中可以實(shí)時(shí)更新視圖(這樣能力強(qiáng))
/**
 typedef NS_ENUM(NSInteger, UIViewAnimationTransition)
 {
     UIViewAnimationTransitionNone,
     UIViewAnimationTransitionFlipFromLeft,
     UIViewAnimationTransitionFlipFromRight,
     UIViewAnimationTransitionCurlUp,
     UIViewAnimationTransitionCurlDown
 };
 */

// 格式
+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache;
// 樣例
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:NO];

注: 該方法每個(gè)動(dòng)畫塊中只可調(diào)用一次

設(shè)置是否動(dòng)畫可用

設(shè)置是否動(dòng)畫可用,默認(rèn)為YES,如果設(shè)置為NO,則沒有動(dòng)畫效果,屬性變化立即生效

// 格式
+ (void)setAnimationsEnabled:(BOOL)enabled;
// 樣例
[UIView setAnimationsEnabled:YES];

注: 該設(shè)置只影響在這句代碼之后設(shè)置的屬性變化

返回一個(gè)BOOL值標(biāo)識(shí)是否動(dòng)畫可用

// 格式
+ (BOOL)areAnimationsEnabled;
// 樣例
[UIView areAnimationsEnabled];

Apple在iOS7中新增加了一個(gè)簡單的封裝,該方法會(huì)先檢查動(dòng)畫當(dāng)前是否為可用狀態(tài),然后設(shè)置動(dòng)畫為不可用狀態(tài),執(zhí)行Block中的代碼,最后重新恢復(fù)動(dòng)畫為原來狀態(tài)

// 格式
+ (void)performWithoutAnimation:(void (^)(void))actionsWithoutAnimation;
// 樣例
[UIView performWithoutAnimation:^{
    self.demoImageView.alpha = 0.4;
}];
動(dòng)畫塊的嵌套

動(dòng)畫塊的嵌套使用,是指在commit提交前一個(gè)動(dòng)畫之前再次調(diào)用begin方法創(chuàng)建一個(gè)新的動(dòng)畫,該新動(dòng)畫可以根據(jù)需求指定與外層動(dòng)畫不同的"動(dòng)畫持續(xù)時(shí)長""時(shí)間函數(shù)"等動(dòng)畫選擇項(xiàng),當(dāng)調(diào)用commit提交一個(gè)動(dòng)畫時(shí),如果當(dāng)前動(dòng)畫塊是在最外層,該方法會(huì)開始做動(dòng)畫,如果當(dāng)前動(dòng)畫塊嵌套在另一個(gè)動(dòng)畫塊的里面,該方法會(huì)等待外層的動(dòng)畫塊提交,直到最外層動(dòng)畫塊提交之后一起做動(dòng)畫,動(dòng)畫是在單獨(dú)的線程運(yùn)行,這樣多個(gè)動(dòng)畫便可以一個(gè)一個(gè)地執(zhí)行起來

// 以"嵌套動(dòng)畫的外層歷時(shí)5s將視圖透明度修改為0.2,嵌套動(dòng)畫的內(nèi)層不繼承外層動(dòng)畫的持續(xù)時(shí)長,將視圖大小縮小一半,期間重復(fù)2.5次"為例,代碼如下
[UIView beginAnimations:@"parent" context:nil];
[UIView setAnimationDuration:5.0f];
self.demoImageView.alpha = 0.2f;

[UIView beginAnimations:@"demo2" context:nil];
[UIView setAnimationDuration:1.0f];
[UIView setAnimationRepeatCount:2.5f];
[UIView setAnimationRepeatAutoreverses:YES];
self.demoImageView.transform = CGAffineTransformMakeScale(0.5, 0.5);
[UIView commitAnimations];

[UIView commitAnimations];
嵌套動(dòng)畫.gif
兩個(gè)小例子

例子一

// 以"將視圖大小縮小一半,并做反向動(dòng)畫,最后平滑地停留在預(yù)定位置"為例,代碼如下
[UIView beginAnimations:@"demo" context:nil];
[UIView setAnimationDuration:1.0f];
[UIView setAnimationRepeatCount:2.5];
[UIView setAnimationRepeatAutoreverses:YES];
self.demoImageView.transform = CGAffineTransformMakeScale(0.5, 0.5);
[UIView commitAnimations];
demo1.png

例子二

// 以"從左側(cè)翻轉(zhuǎn)轉(zhuǎn)場動(dòng)畫,且將視圖大小縮小一半(動(dòng)畫效果發(fā)生在該視圖上)"為例,代碼如下
[UIView beginAnimations:@"demo" context:nil];
[UIView setAnimationDuration:1.0f];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.demoImageView cache:YES];
self.demoImageView.transform = CGAffineTransformMakeScale(0.5, 0.5);
[UIView commitAnimations];
demo2.gif

UIViewAnimationWithBlocks(Block動(dòng)畫)

Apple從iOS4開始加入了Block功能,UIView的普通動(dòng)畫方法全面增加了對(duì)Block的支持,自此我們便可以非常方便地使用動(dòng)畫功能,推薦使用

普通屬性動(dòng)畫
/**
 * 屬性動(dòng)畫
 *
 * @param duration   動(dòng)畫持續(xù)時(shí)長
 * @param delay      延時(shí)動(dòng)畫時(shí)長
 * @param options    動(dòng)畫選擇項(xiàng)
 * @param animations 動(dòng)畫Block
 * @param completion 動(dòng)畫執(zhí)行完執(zhí)行的Block
 *
 * @return 無返回值
 */
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;

// 以"將視圖大小縮小一半,動(dòng)畫結(jié)束后恢復(fù)"為例,代碼如下
[UIView animateWithDuration:3.0f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{
    self.demoImageView.transform = CGAffineTransformMakeScale(0.5, 0.5);
} completion:^(BOOL finished) {
    if (finished)
    {
        self.demoView.transform = CGAffineTransformIdentity;
    }
}];

上述Block動(dòng)畫共有5個(gè)參數(shù),使用起來需要設(shè)置的內(nèi)容比較多,系統(tǒng)為我們提供了兩個(gè)相對(duì)簡單的Block動(dòng)畫以用于簡單動(dòng)畫

// delay = 0.0,options = 0的屬性動(dòng)畫
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;

// delay = 0.0,options = 0,completion = NULL的屬性動(dòng)畫
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations;
普通動(dòng)畫.gif
彈性屬性動(dòng)畫

Apple從iOS7開始在系統(tǒng)中大量使用SpringAnimation,該動(dòng)畫由于前期速度很快,可以給用戶一種干凈利落的感覺,所以推薦大家使用以與系統(tǒng)動(dòng)畫接軌

/**
 * SpringAnimation(彈性動(dòng)畫)
 *
 * @param duration     動(dòng)畫持續(xù)時(shí)長
 * @param delay        延時(shí)動(dòng)畫時(shí)長
 * @param dampingRatio 當(dāng)dampingRatio設(shè)置為1時(shí),動(dòng)畫會(huì)毫無震蕩感地平穩(wěn)地運(yùn)動(dòng)到最終狀態(tài),如果dampingRatio的值少于1,值越小在最終狀態(tài)上震蕩效果會(huì)越明顯
 * @param velocity     初始速度,數(shù)值越大一開始移動(dòng)速度越快(注: 初始速度取值較高而時(shí)間取值較短時(shí),也會(huì)產(chǎn)生震蕩效果)
 * @param options      動(dòng)畫選擇項(xiàng)
 * @param animations   動(dòng)畫Block
 * @param completion   動(dòng)畫執(zhí)行完執(zhí)行的Block
 *
 * @return 無返回值
 */
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;

// 以"將視圖大小縮小一半,動(dòng)畫結(jié)束后恢復(fù)"為例,代碼如下
[UIView animateWithDuration:3.0f delay:0.0f usingSpringWithDamping:0.7f initialSpringVelocity:5.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{
    self.demoImageView.transform = CGAffineTransformMakeScale(0.5, 0.5);
} completion:^(BOOL finished) {
    if (finished)
    {
        self.demoView.transform = CGAffineTransformIdentity;
    }
}];
彈性動(dòng)畫.gif
執(zhí)行系統(tǒng)動(dòng)畫

Apple從iOS7開始加入了一個(gè)方法,允許我們執(zhí)行系統(tǒng)動(dòng)畫,且可以并行地執(zhí)行附加動(dòng)畫

/**
 * 在一個(gè)或多個(gè)視圖上執(zhí)行系統(tǒng)動(dòng)畫
 *
 * @param animation          系統(tǒng)動(dòng)畫,暫時(shí)只有UISystemAnimationDelete一個(gè)
 * @param views              執(zhí)行動(dòng)畫的views數(shù)組
 * @param options            動(dòng)畫選擇項(xiàng)
 * @param parallelAnimations 并行附加動(dòng)畫Block
 * @param completion         動(dòng)畫執(zhí)行完執(zhí)行的Block
 *
 * @return  無返回值
 */
+ (void)performSystemAnimation:(UISystemAnimation)animation onViews:(NSArray<__kindof UIView *> *)views options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))parallelAnimations completion:(void (^ __nullable)(BOOL finished))completion;

// 以"執(zhí)行系統(tǒng)刪除動(dòng)畫"為例,代碼如下
[UIView performSystemAnimation:UISystemAnimationDelete onViews:@[self.demoImageView] options:UIViewAnimationOptionCurveEaseInOut animations:^{
    // do something....
} completion:^(BOOL finished) {
    if (finished)
    {
        // do something....
    }
}];

注: 附加動(dòng)畫不要修改正在被系統(tǒng)動(dòng)畫修改的屬性

系統(tǒng)動(dòng)畫.gif
轉(zhuǎn)場動(dòng)畫

Apple為我們提供了兩個(gè)方法用于進(jìn)行轉(zhuǎn)場動(dòng)畫,為我們解決兩個(gè)動(dòng)畫需求

  • 修改已存在視圖的子視圖(也可以修改自身)
/**
 * TransitionAnimation(轉(zhuǎn)場動(dòng)畫)
 *
 * @param view       參與轉(zhuǎn)場動(dòng)畫的視圖(轉(zhuǎn)場動(dòng)畫效果發(fā)生在該視圖上)
 * @param duration   動(dòng)畫持續(xù)時(shí)長
 * @param options    動(dòng)畫選擇項(xiàng)
 * @param animations 動(dòng)畫Block
 * @param completion 動(dòng)畫執(zhí)行完執(zhí)行的Block
 *
 * @return  無返回值
 */
+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;

// 以"父視圖從左側(cè)翻轉(zhuǎn)轉(zhuǎn)場動(dòng)畫,且將子視圖大小縮小一半,動(dòng)畫結(jié)束后恢復(fù)"為例,代碼如下
[UIView transitionWithView:self.view duration:3.0f options:UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionAllowAnimatedContent animations:^{
    self.demoImageView.transform = CGAffineTransformMakeScale(0.5, 0.5);
} completion:^(BOOL finished) {
    if (finished)
    {
        self.demoImageView.transform = CGAffineTransformIdentity;
    }
}];

注: 該轉(zhuǎn)場動(dòng)畫作用于view上

轉(zhuǎn)場動(dòng)畫1.gif
  • 在視圖層級(jí)中用一個(gè)視圖替換另一個(gè)視圖
/**
 * TransitionAnimation(轉(zhuǎn)場動(dòng)畫)
 *
 * @param fromView   參與轉(zhuǎn)場動(dòng)畫的視圖(被移除視圖)
 * @param toView     參與轉(zhuǎn)場動(dòng)畫的視圖(被添加視圖)
 * @param duration   動(dòng)畫持續(xù)時(shí)長
 * @param options    動(dòng)畫選擇項(xiàng)
 * @param completion 動(dòng)畫執(zhí)行完執(zhí)行的Block
 *
 * @return  無返回值
 */
+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion;

// 以"從左側(cè)翻轉(zhuǎn)轉(zhuǎn)場動(dòng)畫,將原視圖移除,添加新視圖進(jìn)來"為例,代碼如下
UIImageView *toImageView = [[UIImageView alloc] initWithFrame:CGRectMake(20, 20, [[UIScreen mainScreen] bounds].size.width-40, [[UIScreen mainScreen] bounds].size.height-40)];
toImageView.image = [UIImage imageNamed:@"new"];

[UIView transitionFromView:self.demoImageView toView:toImageView duration:3.0f options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) {
    if (finished)
    {
        NSLog(@"TransitionAnimation finished");
    }
}];

注: 該轉(zhuǎn)場動(dòng)畫作用于fromView的父視圖上,相當(dāng)于將toView添加到fromView的父視圖上,并將fromView從父視圖上移除

轉(zhuǎn)場動(dòng)畫2.gif
Block動(dòng)畫的嵌套

Block動(dòng)畫的嵌套使用,是指在Block動(dòng)畫的animations中再創(chuàng)建一個(gè)新的Block動(dòng)畫,該新動(dòng)畫可以根據(jù)需求指定與外層動(dòng)畫不同的"動(dòng)畫選擇項(xiàng)",新動(dòng)畫會(huì)繼承外層動(dòng)畫的"動(dòng)畫持續(xù)時(shí)長"和"時(shí)間函數(shù)",但是一些"動(dòng)畫選擇項(xiàng)"不會(huì)繼承(在動(dòng)畫選擇項(xiàng)中提供了兩個(gè)用于嵌套動(dòng)畫的選擇項(xiàng),UIViewAnimationOptionOverrideInheritedDuration代表忽略嵌套動(dòng)畫中外層動(dòng)畫的時(shí)長設(shè)置,UIViewAnimationOptionOverrideInheritedCurve代表忽略嵌套動(dòng)畫中外層動(dòng)畫的時(shí)間函數(shù)設(shè)置)

// 以"嵌套動(dòng)畫的外層歷時(shí)5s將視圖透明度修改為0.2,嵌套動(dòng)畫的內(nèi)層不繼承外層動(dòng)畫的持續(xù)時(shí)長,將視圖大小縮小一半,期間重復(fù)2.5次"為例,代碼如下
[UIView animateWithDuration:5.0f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{
    self.demoImageView.alpha = 0.2f;

    [UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionOverrideInheritedDuration | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse animations:^{
        [UIView setAnimationRepeatCount:2.5];
         self.demoImageView.transform = CGAffineTransformMakeScale(0.5, 0.5);
    } completion:nil];

} completion:nil];

注: 如果不使用UIViewAnimationOptionOverrideInheritedDuration,嵌套動(dòng)畫的內(nèi)層會(huì)直接繼承外層的持續(xù)時(shí)長

嵌套動(dòng)畫.gif
Block動(dòng)畫選擇項(xiàng)
typedef NS_OPTIONS(NSUInteger, UIViewAnimationOptions)
{
    // 通用設(shè)置(可同時(shí)設(shè)置多個(gè))
    UIViewAnimationOptionLayoutSubviews            = 1 <<  0, // 動(dòng)畫過程中,子視圖隨著一起動(dòng)畫
    UIViewAnimationOptionAllowUserInteraction      = 1 <<  1, // 動(dòng)畫過程中,允許用戶交互
    UIViewAnimationOptionBeginFromCurrentState     = 1 <<  2, // 是否從舊動(dòng)畫的當(dāng)前狀態(tài)開始新動(dòng)畫,參考UIView的+(void)setAnimationBeginsFromCurrentState:(BOOL)fromCurrentState;方法
    UIViewAnimationOptionRepeat                    = 1 <<  3, // 無限次數(shù)重復(fù)動(dòng)畫
    UIViewAnimationOptionAutoreverse               = 1 <<  4, // 是否開啟反向動(dòng)畫,參考UIView的+(void)setAnimationRepeatAutoreverses:方法(BOOL)repeatAutoreverses;方法
    UIViewAnimationOptionOverrideInheritedDuration = 1 <<  5, // 忽略嵌套動(dòng)畫中外層動(dòng)畫的時(shí)長設(shè)置(只適用于嵌套動(dòng)畫)
    UIViewAnimationOptionOverrideInheritedCurve    = 1 <<  6, // 忽略嵌套動(dòng)畫中外層動(dòng)畫的時(shí)間函數(shù)設(shè)置(只適用于嵌套動(dòng)畫)
    UIViewAnimationOptionAllowAnimatedContent      = 1 <<  7, // 視圖每一幀都會(huì)渲染,在動(dòng)畫過程中可以實(shí)時(shí)更新視圖(只適用于轉(zhuǎn)場動(dòng)畫)
    UIViewAnimationOptionShowHideTransitionViews   = 1 <<  8, // 視圖切換時(shí)直接隱藏舊視圖并顯示新視圖,而不是將舊視圖從父視圖移除并添加新視圖(只適用于轉(zhuǎn)場動(dòng)畫)
    UIViewAnimationOptionOverrideInheritedOptions  = 1 <<  9, // 不繼承動(dòng)畫選擇項(xiàng)(只適用于嵌套動(dòng)畫)
    
    // 時(shí)間函數(shù)控制(只可同時(shí)設(shè)置一項(xiàng)),參考UIView的+(void)setAnimationCurve:(UIViewAnimationCurve)curve;方法
    UIViewAnimationOptionCurveEaseInOut            = 0 << 16, // 默認(rèn),淡入淡出,先慢后快再慢
    UIViewAnimationOptionCurveEaseIn               = 1 << 16, // 淡入,先慢后快
    UIViewAnimationOptionCurveEaseOut              = 2 << 16, // 淡出,先快后慢
    UIViewAnimationOptionCurveLinear               = 3 << 16, // 線性
 
    // 轉(zhuǎn)場動(dòng)畫方向(只適用于轉(zhuǎn)場動(dòng)畫,只可同時(shí)設(shè)置一項(xiàng)),參考UIView的+(void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache;方法
    UIViewAnimationOptionTransitionNone            = 0 << 20, // 默認(rèn),無效果
    UIViewAnimationOptionTransitionFlipFromLeft    = 1 << 20, // 從左側(cè)翻轉(zhuǎn)
    UIViewAnimationOptionTransitionFlipFromRight   = 2 << 20, // 從右側(cè)翻轉(zhuǎn)
    UIViewAnimationOptionTransitionCurlUp          = 3 << 20, // 向后翻頁
    UIViewAnimationOptionTransitionCurlDown        = 4 << 20, // 向前翻頁
    UIViewAnimationOptionTransitionCrossDissolve   = 5 << 20, // 舊視圖溶解顯示新視圖
    UIViewAnimationOptionTransitionFlipFromTop     = 6 << 20, // 從上方翻轉(zhuǎn)
    UIViewAnimationOptionTransitionFlipFromBottom  = 7 << 20, // 從下方翻轉(zhuǎn)
}

UIViewKeyframeAnimations(關(guān)鍵幀動(dòng)畫)

Apple從iOS7開始加入了關(guān)鍵幀動(dòng)畫,可以通過提供一個(gè)動(dòng)畫在多個(gè)關(guān)鍵幀處的屬性實(shí)現(xiàn)一系列動(dòng)畫效果,兩個(gè)關(guān)鍵幀中間會(huì)有系統(tǒng)自動(dòng)加入補(bǔ)間動(dòng)畫

/**
 * KeyframeAnimation(關(guān)鍵幀動(dòng)畫)
 *
 * @param duration   動(dòng)畫持續(xù)時(shí)長
 * @param delay      延時(shí)動(dòng)畫時(shí)長
 * @param options    關(guān)鍵幀動(dòng)畫選擇項(xiàng)
 * @param animations 動(dòng)畫Block
 * @param completion 動(dòng)畫執(zhí)行完執(zhí)行的Block
 *
 * @return 無返回值
 */
+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;

/**
 * 添加關(guān)鍵幀
 *
 * @param frameStartTime 幀動(dòng)畫相對(duì)開始時(shí)間,取值為0~1
 * @param frameDuration  幀動(dòng)畫相對(duì)持續(xù)時(shí)長,取值為0~1
 * @param animations     幀動(dòng)畫Block
 *
 * @return 無返回值
 */
+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations;

// 以"將視圖大小縮小一半,再順時(shí)針旋轉(zhuǎn)90度,再放大2倍,再逆時(shí)針旋轉(zhuǎn)90度恢復(fù)原樣"為例,代碼如下
[UIView animateKeyframesWithDuration:3.0f delay:0.0f options:UIViewKeyframeAnimationOptionCalculationModeLinear animations:^{
    [UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:0.25f animations:^{
        self.demoImageView.transform = CGAffineTransformScale(self.demoImageView.transform, 0.5, 0.5);
    }];
    [UIView addKeyframeWithRelativeStartTime:0.25f relativeDuration:0.25f animations:^{
        self.demoImageView.transform = CGAffineTransformRotate(self.demoImageView.transform, M_PI * 0.5);
    }];
    [UIView addKeyframeWithRelativeStartTime:0.5f relativeDuration:0.25f animations:^{
        self.demoImageView.transform = CGAffineTransformScale(self.demoImageView.transform, 2, 2);
    }];
    [UIView addKeyframeWithRelativeStartTime:0.75f relativeDuration:0.25f animations:^{
        self.demoImageView.transform = CGAffineTransformRotate(self.demoImageView.transform, -M_PI * 0.5);
    }];
} completion:^(BOOL finished) {
    if (finished)
    {
        NSLog(@"TransitionAnimation finished");
    }
    else
    {
        NSLog(@"在動(dòng)畫執(zhí)行過程中再次被觸發(fā)動(dòng)畫,會(huì)調(diào)用這里后重新開始動(dòng)畫");
    }
}];

注: 關(guān)鍵幀動(dòng)畫有兩種形式,UIView只支持屬性關(guān)鍵幀動(dòng)畫,暫不支持路徑關(guān)鍵幀動(dòng)畫

關(guān)鍵幀動(dòng)畫.gif
關(guān)鍵幀動(dòng)畫選擇項(xiàng)
typedef NS_OPTIONS(NSUInteger, UIViewKeyframeAnimationOptions)
{
    // 通用設(shè)置(可同時(shí)設(shè)置多個(gè))
    UIViewKeyframeAnimationOptionLayoutSubviews            = UIViewAnimationOptionLayoutSubviews, // 動(dòng)畫過程中,子視圖隨著一起動(dòng)畫
    UIViewKeyframeAnimationOptionAllowUserInteraction      = UIViewAnimationOptionAllowUserInteraction, // 動(dòng)畫過程中,允許用戶交互
    UIViewKeyframeAnimationOptionBeginFromCurrentState     = UIViewAnimationOptionBeginFromCurrentState, // 是否從舊動(dòng)畫的當(dāng)前狀態(tài)開始新動(dòng)畫
    UIViewKeyframeAnimationOptionRepeat                    = UIViewAnimationOptionRepeat, // 無限次數(shù)重復(fù)動(dòng)畫
    UIViewKeyframeAnimationOptionAutoreverse               = UIViewAnimationOptionAutoreverse, // 是否開啟反向動(dòng)畫
    UIViewKeyframeAnimationOptionOverrideInheritedDuration = UIViewAnimationOptionOverrideInheritedDuration, // 忽略嵌套動(dòng)畫中外層動(dòng)畫的時(shí)長設(shè)置(只適用于嵌套動(dòng)畫)
    UIViewKeyframeAnimationOptionOverrideInheritedOptions  = UIViewAnimationOptionOverrideInheritedOptions, // 忽略嵌套動(dòng)畫中外層動(dòng)畫的時(shí)間函數(shù)設(shè)置(只適用于嵌套動(dòng)畫)
    
    // 設(shè)置兩個(gè)關(guān)鍵幀之間的動(dòng)畫模式(可理解為補(bǔ)間動(dòng)畫模式)(只可同時(shí)設(shè)置一項(xiàng))
    UIViewKeyframeAnimationOptionCalculationModeLinear     = 0 << 10, // 默認(rèn),線性運(yùn)算模式(勻速補(bǔ)間動(dòng)畫)
    UIViewKeyframeAnimationOptionCalculationModeDiscrete   = 1 << 10, // 離散運(yùn)算模式(無補(bǔ)間動(dòng)畫)
    UIViewKeyframeAnimationOptionCalculationModePaced      = 2 << 10, // 均勻運(yùn)算模式
    UIViewKeyframeAnimationOptionCalculationModeCubic      = 3 << 10, // 平滑運(yùn)算模式
    UIViewKeyframeAnimationOptionCalculationModeCubicPaced = 4 << 10  // 平滑均勻運(yùn)算模式
}

參考文獻(xiàn)

  1. View Programming Guide for iOS
  2. iOS開發(fā)之讓你的應(yīng)用動(dòng)起來
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,699評(píng)論 6 30
  • 在iOS實(shí)際開發(fā)中常用的動(dòng)畫無非是以下四種:UIView動(dòng)畫,核心動(dòng)畫,幀動(dòng)畫,自定義轉(zhuǎn)場動(dòng)畫。 1.UIView...
    請(qǐng)叫我周小帥閱讀 3,342評(píng)論 1 23
  • 前言的前言 唐巧前輩在微信公眾號(hào)「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各項(xiàng)指標(biāo)...
    VincentHK閱讀 5,583評(píng)論 3 44
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜,今天將帶大家一窺iOS動(dòng)畫全貌。在這里你可以看...
    F麥子閱讀 5,275評(píng)論 5 13
  • 人和人交往時(shí)少不了語言,說話看似是人人都會(huì)的事,可很多人說出的話,不僅沒有起到溝通的作用,還傷害了彼此的感情。 在...
    陽光小房子zxf閱讀 815評(píng)論 0 0

友情鏈接更多精彩內(nèi)容