序言
追求美好是人的天性因谎,這是猿們無法避免的亏推。我們總是追求更為酷炫的實現(xiàn),如果足夠仔細叁巨,我們不難發(fā)現(xiàn)一個好的動畫通過步驟分解后本質上不過是一個個簡單的動畫實現(xiàn)斑匪,正是這些基本的動畫在經過合理的搭配組合后化腐朽為神奇,令人驚艷锋勺。因此蚀瘸,掌握最基本的動畫是完成酷炫開發(fā)之旅的根本。
作為動畫篇的第二篇文章宙刘,我在從UIView動畫說起簡單介紹了關于UIView的幾種基本動畫苍姜,這幾種動畫的搭配讓我們的登錄界面富有靈性生動,但是這幾種動畫總是無法滿足我們對于動畫的需求悬包。同樣的衙猪,本文將從一個小demo開始講解強大的transform
動畫以及關鍵幀keyFrame
動畫。
可以看到兩個動畫:葉子被風吹落以及左邊的文字從
summer
變化到autumn
布近,這兩個動畫都是基于強大的transform
形變垫释,其中葉子的飄落動畫通過關鍵幀動畫實現(xiàn)。demo鏈接
transform動畫
transform
是一個非常重要的屬性撑瞧,它在矩陣變換的層面上改變視圖的顯示效果棵譬,完成旋轉、形變预伺、平移等等操作订咸。在它被修改的同時曼尊,視圖的frame也會被真實改變。有兩個數(shù)據(jù)類型用來表示transform
脏嚷,分別是CGAffineTransform
和CATransform3D
骆撇。前者作用于UIView
,后者為layer
層次的變換類型父叙∩窠迹基于后者可以實現(xiàn)更加強大的功能,但我們需要先掌握CGAffineTransform
類型的使用趾唱。同時涌乳,本文講解也是這個變換類型。
對于想要了解矩陣變換是如何作用實現(xiàn)的甜癞,可以參考這篇博客:CGAffineTransform 放射變換
talk is cheap show you the code
在開始使用transform
實現(xiàn)你的動畫之前夕晓,我先介紹幾個常用的函數(shù):
/// 用來連接兩個變換效果并返回。返回的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)
/// 旋轉視圖乔煞。傳入?yún)?shù)為 角度 * (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雄右,非常簡單好用空骚。博主用它來分解動畫步驟,然后進行重現(xiàn)擂仍。
不難看出在文字的動畫中做了兩個處理:y軸上的形變縮小囤屹、透明度的漸變過程。首先在項目中新增兩個UILabel逢渔,分別命名為label1肋坚、label2.然后在viewDidAppear中加入這么一段代碼:
- (void)viewDidAppear: (BOOL)animated {
label1.transform = CGAffineTransformMakeScale(1, 0);
label1.alpha = 0;
[UIView animateWithDuration: 3. animations: ^ {
label1.transform = CGAffineTransformMakeScale(1, 1);
label2.transform = CGAffineTransformMakeScale(1, 0.1);
label1.alpha = 1;
label2.alpha = 0;
}];
}
這里解釋一下為什么label2為什么在動畫中y軸逐漸縮小為0.1而不是0。如果我們設為0的話肃廓,那么在動畫提交之后智厌,label2會直接保持動畫結束的狀態(tài)(這是出于性能優(yōu)化自動完成的),因此在使用任何縮小的形變時盲赊,你可以將縮小值設置的很小铣鹏,只要不是0。
運行你的代碼哀蘑,文字的形變過程你已經做出來了诚卸,但是demo中的動畫不僅僅是形變葵第,還包括位移的過程。很顯然合溺,我們可以通過改變center
的位置來實現(xiàn)這個效果卒密,但這顯然不是我們今天想要的結果,實現(xiàn)新的動畫方式來實現(xiàn)更有意義辫愉。
動畫開始時形變出現(xiàn)的label高度為0栅受,然后逐漸的的變高變?yōu)?code>height,而label從頭到尾基于頂部的位置不發(fā)生改變恭朗。因此動畫開始前這個label在y軸上的位置是0屏镊,在完成顯示之后的y軸中心點為height / 2
(基于label自身的坐標系而言),那么動畫的代碼就可以寫成這樣:
- (void)viewDidAppear: (BOOL)animated {
/// 初始化動畫開始前l(fā)abel的位置
CGFloat offset = label1.frame.size.height * 0.5;
label1.transform = CGAffineTransformConcat(
CGAffineTransformMakeScale(0, 0),
CGAffineTransformMakeTranslation(0, -offset)
);
label1.alpha = 0;
[UIView animateWithDuration: 3. animations: ^ {
/// 還原label1的變換狀態(tài)并形變和偏移label2
label1.transform = CGAffineTransformIdentifier;
label2.transform = CGAffineTransformConcat(
CGAffineTransformMakeScale(0, 0),
CGAffineTransformMakeTranslation(0, offset)
);
label1.alpha = 1;
label2.alpha = 0;
}];
}
調整兩個label的位置痰腮,并且設置其中一個透明顯示而芥。然后運行這段代碼,你會發(fā)現(xiàn)文字轉變過程的動畫完成了膀值。
keyframe動畫
將文章開頭的gif圖另存為到本地棍丐,然后使用預覽打開看看,你會發(fā)現(xiàn)預覽中的gif圖變成了很多張的圖片沧踏。實際上歌逢,無論是動畫、電影翘狱、CG等動態(tài)效果秘案,都可以看做是一張張圖片接連渲染實現(xiàn)的,而這些圖片切換的速度足夠快時我們就會當做是動畫潦匈。在此之前我們所講述的平移視圖在UIView動畫提交之后系統(tǒng)會根據(jù)動畫時長計算出視圖移動的所有幀界面阱高,然后逐個渲染。
回到我們demo中的落葉動畫來茬缩,我總共對葉子的center
進行過五次修改赤惊,我將落葉平移的線性路徑繪制出來并且標注關鍵的轉折點:
上面這個平移用UIView動畫代碼要如何實現(xiàn)呢?毫無疑問凰锡,我們需要不斷的嵌套UIView動畫的使用來實現(xiàn)未舟,具體代碼如下:
[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
傳入是沒有任何意義的(傳入一個固定的動畫時長無法體現(xiàn)出在落葉飄下這一過程中的層次步驟)
對于這兩個問題野揪,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
第一個方法是創(chuàng)建一個關鍵幀動畫访忿,第二個方法用于在動畫的代碼塊中插入關鍵幀動畫信息,兩個參數(shù)的意義表示如下:
- frameStartTime 表示關鍵幀動畫開始的時刻在整個動畫中的百分比
- frameDuration 表示這個關鍵幀動畫占用整個動畫時長的百分比斯稳。
我做了一張圖片來表示參數(shù)含義:
對比
UIView
動畫跟關鍵幀動畫海铆,關鍵幀動畫引入了動畫占比時長的概念,這讓我們能控制每個關鍵幀動畫的占用比例而不是傳入一個無意義的動畫時長 —— 這讓我們的代碼更加難以理解挣惰。當然卧斟,除了動畫占比之外,關鍵幀動畫的options
參數(shù)也讓動畫變得更加平滑憎茂,下面是關鍵幀特有的配置參數(shù):
UIViewKeyframeAnimationOptionCalculationModeLinear // 連續(xù)運算模式珍语,線性
UIViewKeyframeAnimationOptionCalculationModeDiscrete // 離散運算模式,只顯示關鍵幀
UIViewKeyframeAnimationOptionCalculationModePaced // 均勻執(zhí)行運算模式竖幔,線性
UIViewKeyframeAnimationOptionCalculationModeCubic // 平滑運算模式
UIViewKeyframeAnimationOptionCalculationModeCubicPaced // 平滑均勻運算模式
在demo中我使用的是UIViewKeyframeAnimationOptionCalculationModeCubic
板乙,這個參數(shù)使用了貝塞爾曲線讓落葉的下落動畫變得更加平滑。效果可見最開始的gif動畫拳氢,你可以修改demo傳入的不同參數(shù)來查看效果募逞。接下來我們就根據(jù)新的方法把上面的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地址
上一篇:從UIView動畫說起
下一篇:layout動畫初體驗
轉載請注明地址和原文作者