追求美好是人的天性,這是猿們無法避免的。我們總是追求更為酷炫的實現,如果足夠仔細,我們不難發現一個好的動畫通過步驟分解後本質上不過是一個個簡單的動畫實現,正是這些基本的動畫在經過合理的搭配組合後化腐朽為神奇,令人驚艷。因此,掌握最基本的動畫是完成酷炫開發之旅的根本。
作為動畫篇的第二篇文章,我在從UIView動畫說起簡單介紹了關於UIView的幾種基本動畫,這幾種動畫的搭配讓我們的登錄界面富有靈性生動,但是這幾種動畫總是無法滿足我們對於動畫的需求。同樣的,本文將從一個小demo開始講解強大的transform
動畫以及關鍵幀keyFrame
動畫。
可以看到兩個動畫:葉子被風吹落以及左邊的文字從summer
變化到autumn
,這兩個動畫都是基於強大的transform
形變,其中葉子的飄落動畫通過關鍵幀動畫實現。demo鏈接
transform
是一個非常重要的屬性,它在矩陣變換的層面上改變視圖的顯示效果,完成旋轉、形變、平移等等操作。在它被修改的同時,視圖的frame也會被真實改變。有兩個數據類型用來表示transform
,分別是CGAffineTransform
和CATransform3D
。前者作用於UIView
,後者為layer
層次的變換類型。基於後者可以實現更加強大的功能,但我們需要先掌握CGAffineTransform
類型的使用。同時,本文講解也是這個變換類型。
對於想要了解矩陣變換是如何作用實現的,可以參考這篇博客:CGAffineTransform 放射變換
talk is cheap show you the code
在開始使用transform
實現你的動畫之前,我先介紹幾個常用的函數:
/// 用來連接兩個變換效果並返回。返回的t = t1 * t2 CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2) /// 矩陣初始值。[ 1 0 0 1 0 0 ] CGAffineTransformIdentity /// 自定義矩陣變換,需要掌握矩陣變換的知識才知道怎麼用。參照上面推薦的原理鏈接 CGAffineTransformMake(CGFloat a, CGFloat b, CGFloat c, CGFloat d, CGFloat tx, CGFloat ty) /// 旋轉視圖。傳入參數為 角度 * (M_PI / 180)。等同於 CGAffineTransformRotate(self.transform, angle) CGAffineTransformMakeRotation(CGFloat angle) CGAffineTransformRotate(CGAffineTransform t, CGFloat angle) /// 縮放視圖。等同於CGAffineTransformScale(self.transform, sx, sy) CGAffineTransformMakeScale(CGFloat sx, CGFloat sy) CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy) /// 縮放視圖。等同於CGAffineTransformTranslate(self.transform, tx, ty) CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty) CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)
我把demo左下角文字的變形過程記錄下來。這裡推薦mac上面的一款截取動圖的程序licecap,非常簡單好用。博主用它來分解動畫步驟,然後進行重現。
不難看出在文字的動畫中做了兩個處理:y軸上的形變縮小、透明度的漸變過程。首先在項目中新增兩個UILabel,分別命名為label1、label2.然後在viewDidAppear中加入這麼一段代碼:
- (void)viewDidAppear: (BOOL)animated { label1.transform = CGAffineTransformMakeScale(0, 0); label1.alpha = 0; [UIView animateWithDuration: 3. animations: ^ { label1.transform = CGAffineTransformMakeScale(0, 1); label2.transform = CGAffineTransformMakeScale(0, 0.1); label1.alpha = 1; label2.alpha = 0; }]; }
這裡解釋一下為什麼label2為什麼在動畫中y軸逐漸縮小為0.1而不是0。如果我們設為0的話,那麼在動畫提交之後,label2會直接保持動畫結束的狀態(這是出於性能優化自動完成的),因此在使用任何縮小的形變時,你可以將縮小值設置的很小,只要不是0。
運行你的代碼,文字的形變過程你已經做出來了,但是demo中的動畫不僅僅是形變,還包括位移的過程。很顯然,我們可以通過改變center
的位置來實現這個效果,但這顯然不是我們今天想要的結果,實現新的動畫方式來實現更有意義。
動畫開始時形變出現的label高度為0,然後逐漸的的變高變為height
,而label從頭到尾基於頂部的位置不發生改變。因此動畫開始前這個label在y軸上的位置是0,在完成顯示之後的y軸中心點為height / 2
(基於label自身的坐標系而言),那麼動畫的代碼就可以寫成這樣:
- (void)viewDidAppear: (BOOL)animated { /// 初始化動畫開始前label的位置 CGFloat offset = label1.frame.size.height * 0.5; label1.transform = CGAffineTransformConcat( CGAffineTransformMakeScale(0, 0), CGAffineTransformTranslate(0, -offset) ); label1.alpha = 0; [UIView animateWithDuration: 3. animations: ^ { /// 還原label1的變換狀態並形變和偏移label2 label1.transform = CGAffineTransformIdentifier; label1.transform = CGAffineTransformConcat( CGAffineTransformMakeScale(0, 0), CGAffineTransformTranslate(0, offset) ); label1.alpha = 1; label2.alpha = 0; }]; }
調整兩個label的位置,並且設置其中一個透明顯示。然後運行這段代碼,你會發現文字轉變過程的動畫完成了。
將文章開頭的gif圖另存為到本地,然後使用預覽打開看看,你會發現預覽中的gif圖變成了很多張的圖片。實際上,無論是動畫、電影、CG等動態效果,都可以看做是一張張圖片接連渲染實現的,而這些圖片切換的速度足夠快時我們就會當做是動畫。在此之前我們所講述的平移視圖在UIView動畫提交之後系統會根據動畫時長計算出視圖移動的所有幀界面,然後逐個渲染。
回到我們demo中的落葉動畫來,我總共對葉子的center
進行過五次修改,我將落葉平移的線性路徑繪制出來並且標注關鍵的轉折點:
上面這個平移用UIView動畫代碼要如何實現呢?毫無疑問,我們需要不斷的嵌套UIView動畫的使用來實現,具體代碼如下:
[self moveLeafWithOffset: (CGPoint){ 15, 80 } completion: ^(BOOL finished) { [self moveLeafWithOffset: (CGPoint){ 30, 105 } completion: ^(BOOL finished) { [self moveLeafWithOffset: (CGPoint){ 40, 110 } completion: ^(BOOL finished) { [self moveLeafWithOffset: (CGPoint){ 90, 80 } completion: ^(BOOL finished) { [self moveLeafWithOffset: (CGPoint){ 80, 60 } completion: nil duration: 0.6]; } duration: 1.2]; } duration: 1.2]; } duration: 0.6]; } duration: 0.4]; - (void)moveLeafWithOffset: (CGPoint)offset completion: (void(^)(BOOL finished))completion duration: (NSTimeInterval)duration { [UIView animateWithDuration: duration delay: 0 options: UIViewAnimationOptionCurveLinear animations: ^{ CGPoint center = _leaf.center; center.x += offset.x; center.y += offset.y; _leaf.center = center; } completion: completion]; }
看起來還蠻容易的,上面的代碼只是移動葉子,在gif圖中我們的葉子還有旋轉,因此我們還需要加上這麼一段代碼:
[UIView animateWithDuration: 4 animations: ^{ _leaf.transform = CGAffineTransformMakeRotation(M_PI); }];
那麼ok,運行這段代碼看看,落葉的移動非常的生硬,我們可以明顯的看到拐角。其次,這段代碼中的duration
傳入是沒有任何意義的(傳入一個固定的動畫時長無法體現出在落葉飄下這一過程中的層次步驟)
對於這兩個問題,UIView也提供了另一種動畫方式來幫助我們解決這兩個問題 —— keyframe動畫:
+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion + (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations
第一個方法是創建一個關鍵幀動畫,第二個方法用於在動畫的代碼塊中插入關鍵幀動畫信息,兩個參數的意義表示如下:
frameStartTime 表示關鍵幀動畫開始的時刻在整個動畫中的百分比
frameDuration 表示這個關鍵幀動畫占用整個動畫時長的百分比。
我做了一張圖片來表示參數含義:
對比UIView
動畫跟關鍵幀動畫,關鍵幀動畫引入了動畫占比時長的概念,這讓我們能控制每個關鍵幀動畫的占用比例而不是傳入一個無意義的動畫時長 —— 這讓我們的代碼更加難以理解。當然,除了動畫占比之外,關鍵幀動畫的options
參數也讓動畫變得更加平滑,下面是關鍵幀特有的配置參數:
UIViewKeyframeAnimationOptionCalculationModeLinear // 連續運算模式,線性 UIViewKeyframeAnimationOptionCalculationModeDiscrete // 離散運算模式,只顯示關鍵幀 UIViewKeyframeAnimationOptionCalculationModePaced // 均勻執行運算模式,線性 UIViewKeyframeAnimationOptionCalculationModeCubic // 平滑運算模式 UIViewKeyframeAnimationOptionCalculationModeCubicPaced // 平滑均勻運算模式
在demo中我使用的是UIViewKeyframeAnimationOptionCalculationModeCubic
,這個參數使用了貝塞爾曲線讓落葉的下落動畫變得更加平滑。效果可見最開始的gif動畫,你可以修改demo傳入的不同參數來查看效果。接下來我們就根據新的方法把上面的UIView
動畫轉換成關鍵幀動畫代碼,具體代碼如下:
[UIView animateKeyframesWithDuration: 4 delay: 0 options: UIViewKeyframeAnimationOptionCalculationModeLinear animations: ^{ __block CGPoint center = _leaf.center; [UIView addKeyframeWithRelativeStartTime: 0 relativeDuration: 0.1 animations: ^{ _leaf.center = (CGPoint){ center.x + 15, center.y + 80 }; }]; [UIView addKeyframeWithRelativeStartTime: 0.1 relativeDuration: 0.15 animations: ^{ _leaf.center = (CGPoint){ center.x + 45, center.y + 185 }; }]; [UIView addKeyframeWithRelativeStartTime: 0.25 relativeDuration: 0.3 animations: ^{ _leaf.center = (CGPoint){ center.x + 90, center.y + 295 }; }]; [UIView addKeyframeWithRelativeStartTime: 0.55 relativeDuration: 0.3 animations: ^{ _leaf.center = (CGPoint){ center.x + 180, center.y + 375 }; }]; [UIView addKeyframeWithRelativeStartTime: 0.85 relativeDuration: 0.15 animations: ^{ _leaf.center = (CGPoint){ center.x + 260, center.y + 435 }; }]; [UIView addKeyframeWithRelativeStartTime: 0 relativeDuration: 1 animations: ^{ _leaf.transform = CGAffineTransformMakeRotation(M_PI); }]; } completion: nil];
可以看到相比UIView
的動畫,關鍵幀動畫更加直觀的讓我們明白每一次平移動畫的時間占比,代碼也相對的更加簡潔。
本文作為動畫篇的第二篇博客,到了這裡UIView
的所有動畫教程已經完成,在之後的文章中將進一步講解autolayout
動畫和圖層層次的動畫。時值新年,祝願各位????年快樂,心想事成!本文demo地址
轉載請注明地址和原文作者