序言
追求美好是人的天性犁功,這是猿們無(wú)法避免的囚霸。我們總是追求更為酷炫的實(shí)現(xiàn)瞧省,如果足夠仔細(xì)硼一,我們不難發(fā)現(xiàn)一個(gè)好的動(dòng)畫通過(guò)步驟分解后本質(zhì)上不過(guò)是一個(gè)個(gè)簡(jiǎn)單的動(dòng)畫實(shí)現(xiàn)累澡,正是這些基本的動(dòng)畫在經(jīng)過(guò)合理的搭配組合后化腐朽為神奇,令人驚艷欠动。因此永乌,掌握最基本的動(dòng)畫是完成酷炫開發(fā)之旅的根本。
作為動(dòng)畫篇的第二篇文章具伍,我在從UIView動(dòng)畫說(shuō)起簡(jiǎn)單介紹了關(guān)于UIView的幾種基本動(dòng)畫,這幾種動(dòng)畫的搭配讓我們的登錄界面富有靈性生動(dòng)圈驼,但是這幾種動(dòng)畫總是無(wú)法滿足我們對(duì)于動(dòng)畫的需求人芽。同樣的,本文將從一個(gè)小demo開始講解強(qiáng)大的transform動(dòng)畫以及關(guān)鍵幀keyFrame動(dòng)畫绩脆。
demo動(dòng)效圖
可以看到兩個(gè)動(dòng)畫:葉子被風(fēng)吹落以及左邊的文字從summer變化到autumn萤厅,這兩個(gè)動(dòng)畫都是基于強(qiáng)大的transform形變橄抹,其中葉子的飄落動(dòng)畫通過(guò)關(guān)鍵幀動(dòng)畫實(shí)現(xiàn)。demo鏈接
transform動(dòng)畫
transform是一個(gè)非常重要的屬性惕味,它在矩陣變換的層面上改變視圖的顯示效果楼誓,完成旋轉(zhuǎn)、形變名挥、平移等等操作疟羹。在它被修改的同時(shí),視圖的frame也會(huì)被真實(shí)改變禀倔。有兩個(gè)數(shù)據(jù)類型用來(lái)表示transform榄融,分別是CGAffineTransform和CATransform3D。前者作用于UIView救湖,后者為layer層次的變換類型愧杯。基于后者可以實(shí)現(xiàn)更加強(qiáng)大的功能鞋既,但我們需要先掌握CGAffineTransform類型的使用力九。同時(shí),本文講解也是這個(gè)變換類型邑闺。
對(duì)于想要了解矩陣變換是如何作用實(shí)現(xiàn)的畏邢,可以參考這篇博客:CGAffineTransform 放射變換
talk is cheap show you the code
在開始使用transform實(shí)現(xiàn)你的動(dòng)畫之前,我先介紹幾個(gè)常用的函數(shù):
/// 用來(lái)連接兩個(gè)變換效果并返回检吆。返回的t = t1 * t2CGAffineTransformConcat(CGAffineTransformt1,CGAffineTransformt2)/// 矩陣初始值舒萎。[ 1 0 0 1 0 0 ]CGAffineTransformIdentity/// 自定義矩陣變換,需要掌握矩陣變換的知識(shí)才知道怎么用蹭沛。參照上面推薦的原理鏈接CGAffineTransformMake(CGFloata,CGFloatb,CGFloatc,CGFloatd,CGFloattx,CGFloatty)/// 旋轉(zhuǎn)視圖臂寝。傳入?yún)?shù)為 角度 * (M_PI / 180)。等同于 CGAffineTransformRotate(self.transform, angle)CGAffineTransformMakeRotation(CGFloatangle)CGAffineTransformRotate(CGAffineTransformt,CGFloatangle)/// 縮放視圖摊灭。等同于CGAffineTransformScale(self.transform, sx, sy)CGAffineTransformMakeScale(CGFloatsx,CGFloatsy)CGAffineTransformScale(CGAffineTransformt,CGFloatsx,CGFloatsy)/// 縮放視圖咆贬。等同于CGAffineTransformTranslate(self.transform, tx, ty)CGAffineTransformMakeTranslation(CGFloattx,CGFloatty)CGAffineTransformTranslate(CGAffineTransformt,CGFloattx,CGFloatty)
我把demo左下角文字的變形過(guò)程記錄下來(lái)。這里推薦mac上面的一款截取動(dòng)圖的程序licecap帚呼,非常簡(jiǎn)單好用掏缎。博主用它來(lái)分解動(dòng)畫步驟,然后進(jìn)行重現(xiàn)煤杀。
文字變形過(guò)程
不難看出在文字的動(dòng)畫中做了兩個(gè)處理:y軸上的形變縮小眷蜈、透明度的漸變過(guò)程。首先在項(xiàng)目中新增兩個(gè)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為什么在動(dòng)畫中y軸逐漸縮小為0.1而不是0。如果我們?cè)O(shè)為0的話枯途,那么在動(dòng)畫提交之后忌怎,label2會(huì)直接保持動(dòng)畫結(jié)束的狀態(tài)(這是出于性能優(yōu)化自動(dòng)完成的)籍滴,因此在使用任何縮小的形變時(shí),你可以將縮小值設(shè)置的很小榴啸,只要不是0孽惰。
運(yùn)行你的代碼,文字的形變過(guò)程你已經(jīng)做出來(lái)了鸥印,但是demo中的動(dòng)畫不僅僅是形變勋功,還包括位移的過(guò)程。很顯然辅甥,我們可以通過(guò)改變center的位置來(lái)實(shí)現(xiàn)這個(gè)效果酝润,但這顯然不是我們今天想要的結(jié)果,實(shí)現(xiàn)新的動(dòng)畫方式來(lái)實(shí)現(xiàn)更有意義璃弄。
動(dòng)畫開始時(shí)形變出現(xiàn)的label高度為0要销,然后逐漸的的變高變?yōu)閔eight,而label從頭到尾基于頂部的位置不發(fā)生改變夏块。因此動(dòng)畫開始前這個(gè)label在y軸上的位置是0疏咐,在完成顯示之后的y軸中心點(diǎn)為height / 2(基于label自身的坐標(biāo)系而言),那么動(dòng)畫的代碼就可以寫成這樣:
- (void)viewDidAppear: (BOOL)animated {///? 初始化動(dòng)畫開始前l(fā)abel的位置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的變換狀態(tài)并形變和偏移label2label1.transform = CGAffineTransformIdentifier;? ? ? ? label1.transform = CGAffineTransformConcat(? ? ? ? ? CGAffineTransformMakeScale(0,0),? ? ? ? ? CGAffineTransformTranslate(0, offset)? ? ? ? );? ? ? ? label1.alpha =1;? ? ? ? label2.alpha =0;? ? }];}
調(diào)整兩個(gè)label的位置脐供,并且設(shè)置其中一個(gè)透明顯示浑塞。然后運(yùn)行這段代碼,你會(huì)發(fā)現(xiàn)文字轉(zhuǎn)變過(guò)程的動(dòng)畫完成了政己。
keyframe動(dòng)畫
將文章開頭的gif圖另存為到本地酌壕,然后使用預(yù)覽打開看看,你會(huì)發(fā)現(xiàn)預(yù)覽中的gif圖變成了很多張的圖片歇由。實(shí)際上卵牍,無(wú)論是動(dòng)畫、電影沦泌、CG等動(dòng)態(tài)效果糊昙,都可以看做是一張張圖片接連渲染實(shí)現(xiàn)的,而這些圖片切換的速度足夠快時(shí)我們就會(huì)當(dāng)做是動(dòng)畫谢谦。在此之前我們所講述的平移視圖在UIView動(dòng)畫提交之后系統(tǒng)會(huì)根據(jù)動(dòng)畫時(shí)長(zhǎng)計(jì)算出視圖移動(dòng)的所有幀界面释牺,然后逐個(gè)渲染。
回到我們demo中的落葉動(dòng)畫來(lái)回挽,我總共對(duì)葉子的center進(jìn)行過(guò)五次修改没咙,我將落葉平移的線性路徑繪制出來(lái)并且標(biāo)注關(guān)鍵的轉(zhuǎn)折點(diǎn):
1.png
上面這個(gè)平移用UIView動(dòng)畫代碼要如何實(shí)現(xiàn)呢?毫無(wú)疑問(wèn)厅各,我們需要不斷的嵌套UIView動(dòng)畫的使用來(lái)實(shí)現(xiàn)镜撩,具體代碼如下:
[selfmoveLeafWithOffset:(CGPoint){15,80} completion: ^(BOOLfinished){[selfmoveLeafWithOffset:(CGPoint){30,105} completion: ^(BOOLfinished){[selfmoveLeafWithOffset:(CGPoint){40,110} completion: ^(BOOLfinished){[selfmoveLeafWithOffset:(CGPoint){90,80} completion: ^(BOOLfinished){[selfmoveLeafWithOffset:(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(^)(BOOLfinished))completion duration:(NSTimeInterval)duration{[UIViewanimateWithDuration: duration delay:0options: UIViewAnimationOptionCurveLinear animations: ^{? ? ? ? CGPoint center = _leaf.center;center.x += offset.x;center.y += offset.y;_leaf.center = center;} completion: completion];}
看起來(lái)還蠻容易的,上面的代碼只是移動(dòng)葉子队塘,在gif圖中我們的葉子還有旋轉(zhuǎn)袁梗,因此我們還需要加上這么一段代碼:
[UIViewanimateWithDuration:4animations: ^{? ? _leaf.transform = CGAffineTransformMakeRotation(M_PI);}];
那么ok,運(yùn)行這段代碼看看憔古,落葉的移動(dòng)非常的生硬遮怜,我們可以明顯的看到拐角。其次鸿市,這段代碼中的duration傳入是沒(méi)有任何意義的(傳入一個(gè)固定的動(dòng)畫時(shí)長(zhǎng)無(wú)法體現(xiàn)出在落葉飄下這一過(guò)程中的層次步驟)
對(duì)于這兩個(gè)問(wèn)題锯梁,UIView也提供了另一種動(dòng)畫方式來(lái)幫助我們解決這兩個(gè)問(wèn)題 —— keyframe動(dòng)畫:
+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void(^)(void))animations completion:(void(^ __nullable)(BOOLfinished))completion+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void(^)(void))animations
第一個(gè)方法是創(chuàng)建一個(gè)關(guān)鍵幀動(dòng)畫,第二個(gè)方法用于在動(dòng)畫的代碼塊中插入關(guān)鍵幀動(dòng)畫信息焰情,兩個(gè)參數(shù)的意義表示如下:
frameStartTime 表示關(guān)鍵幀動(dòng)畫開始的時(shí)刻在整個(gè)動(dòng)畫中的百分比
frameDuration 表示這個(gè)關(guān)鍵幀動(dòng)畫占用整個(gè)動(dòng)畫時(shí)長(zhǎng)的百分比陌凳。
我做了一張圖片來(lái)表示參數(shù)含義:
添加關(guān)鍵幀方法參數(shù)說(shuō)明
對(duì)比UIView動(dòng)畫跟關(guān)鍵幀動(dòng)畫,關(guān)鍵幀動(dòng)畫引入了動(dòng)畫占比時(shí)長(zhǎng)的概念内舟,這讓我們能控制每個(gè)關(guān)鍵幀動(dòng)畫的占用比例而不是傳入一個(gè)無(wú)意義的動(dòng)畫時(shí)長(zhǎng) —— 這讓我們的代碼更加難以理解合敦。當(dāng)然,除了動(dòng)畫占比之外验游,關(guān)鍵幀動(dòng)畫的options參數(shù)也讓動(dòng)畫變得更加平滑充岛,下面是關(guān)鍵幀特有的配置參數(shù):
UIViewKeyframeAnimationOptionCalculationModeLinear// 連續(xù)運(yùn)算模式,線性UIViewKeyframeAnimationOptionCalculationModeDiscrete// 離散運(yùn)算模式耕蝉,只顯示關(guān)鍵幀UIViewKeyframeAnimationOptionCalculationModePaced// 均勻執(zhí)行運(yùn)算模式崔梗,線性UIViewKeyframeAnimationOptionCalculationModeCubic// 平滑運(yùn)算模式UIViewKeyframeAnimationOptionCalculationModeCubicPaced// 平滑均勻運(yùn)算模式
在demo中我使用的是UIViewKeyframeAnimationOptionCalculationModeCubic,這個(gè)參數(shù)使用了貝塞爾曲線讓落葉的下落動(dòng)畫變得更加平滑垒在。效果可見最開始的gif動(dòng)畫蒜魄,你可以修改demo傳入的不同參數(shù)來(lái)查看效果。接下來(lái)我們就根據(jù)新的方法把上面的UIView動(dòng)畫轉(zhuǎn)換成關(guān)鍵幀動(dòng)畫代碼场躯,具體代碼如下:
[UIView animateKeyframesWithDuration:4delay:0options: UIViewKeyframeAnimationOptionCalculationModeLinear animations: ^{? ? __block CGPoint center = _leaf.center;? ? [UIView addKeyframeWithRelativeStartTime:0relativeDuration:0.1animations: ^{? ? ? ? _leaf.center = (CGPoint){ center.x +15, center.y +80};? ? }];? ? [UIView addKeyframeWithRelativeStartTime:0.1relativeDuration:0.15animations: ^{? ? ? ? _leaf.center = (CGPoint){ center.x +45, center.y +185};? ? }];? ? [UIView addKeyframeWithRelativeStartTime:0.25relativeDuration:0.3animations: ^{? ? ? ? _leaf.center = (CGPoint){ center.x +90, center.y +295};? ? }];? ? [UIView addKeyframeWithRelativeStartTime:0.55relativeDuration:0.3animations: ^{? ? ? ? _leaf.center = (CGPoint){ center.x +180, center.y +375};? ? }];? ? [UIView addKeyframeWithRelativeStartTime:0.85relativeDuration:0.15animations: ^{? ? ? ? _leaf.center = (CGPoint){ center.x +260, center.y +435};? ? }];? ? [UIView addKeyframeWithRelativeStartTime:0relativeDuration:1animations: ^{? ? ? ? _leaf.transform = CGAffineTransformMakeRotation(M_PI);? ? }];} completion: nil];
可以看到相比UIView的動(dòng)畫谈为,關(guān)鍵幀動(dòng)畫更加直觀的讓我們明白每一次平移動(dòng)畫的時(shí)間占比,代碼也相對(duì)的更加簡(jiǎn)潔推盛。