標籤:
本文轉載至 http://www.tuicool.com/articles/e2qaYjA原文 https://tech.imdada.cn/2016/06/21/ios-core-animation/主題 Core Animation背景
隨著達達業務的擴大,越來越多的人開始使用達達用戶端,參加到眾包物流的行業中。達達用戶端分為iOS平台和安卓平台。
APP開發也從快速迭代的粗曠性開發轉向高可複用,提升使用者提現的精細化方向發展。iOS動畫互動良好,使用廣泛,良好的使用者體驗離不開流暢的介面變換。為此,達達iOS團隊對動畫實現以及背後原理做了學習探索。
目的
- 瞭解Core Animation 基本架構
- 理解圖層的定義
- 解析動畫的流程
- 掌握3D動畫的原理
- 實現3D動畫的代碼實現
基本架構
Core Animation位於AppKit、UIKit下方,整合於View的Cocoa、Cocoa Touch中。當然Core Animation向view提供很多介面,好讓開發人員更好的控制動畫。
圖層的定義
- 開發人員所有關於Core Animation的操作都是基於圖層,圖層對象是3D空間向後垂直投影的一個2D平面。
- 一個圖層抓取由View提供的內容,並把這些內容緩衝在一張位元影像中。
- 在硬體層面中對位元影像的操作遠遠快於在軟體層面。
理解
垂直投影可以想象成,一個物體站在一面牆前,一個垂直於牆面的光源,照射物體,在牆上留下的陰影,由於是垂直光源,無論物體是遠離牆面,還是靠近牆面,陰影的大小都不會發生變化。
iOS程式中每個View控制項都有自己的Layer,View上的子控制項,例如:Label,Image等,便是View向Layer提供的內容,而平移,旋轉,縮放等參數,便是狀態資訊。
基於Layer的動畫,Core Animation把layer保留的bitmap和狀態資訊傳給圖形硬體,圖形硬體負責使用新的資訊操作bitmap。基於View 繪製,是通過調用view的drawRect方法來使用新的參數重繪內容改變view。這種繪製代價很高,因為繪製在匯流排程消耗CPU完成工作。
動畫的流程
動畫的原理
下面內容將會涉及的知識,依次是數學座標系,線性代數矩陣,物理成像原理,數學相似三角形,數學方程組。不用擔心,我們會從基礎入手,讓理解更加高效。
座標系
在圖層平面座標系統中,使用兩種座標系。
原點位於圖層的左上方,向右為x軸的正方向,向下為y軸的正方向,一個點的x、y座標以點為單位。
原點位於圖層的左上方,向右為x軸的正方向,向下為y軸的正方向,一個點的x、y座標以相對x軸、y軸的比例為值,取值範圍[0,1]。錨點(anchorPoint)使用單位座標系, 如所示position根據錨點而變。
錨點的作用
錨點決定了動畫在變化時,z軸的位置。如,由於錨點不同,圖層繞z軸的旋轉效果也一樣。
矩陣
- 通過矩陣對圖層位元影像進行平移、旋轉、縮放變換。
- 矩陣相乘只有在第一個矩陣的列數(column)和第二個矩陣的行數(row)相同時才有意義。
- 圖層的bitmap由點組成,每個點可以對應1×4矩陣,乘以一個4×4變換矩陣,得到一個1×4矩陣,即為變換後的結果。
矩陣乘法
- 矩陣C的行數等於矩陣A的行數,C的列數等於B的列數。
- 乘積C的第m行第n列的元素等於矩陣A的第m行的元素與矩陣B的第n列對應元素乘積之和。
思考?
1. 點座標為什麼要轉換為1×4矩陣2. 變換矩陣為什麼必須是4×4矩陣3. 如何?移動,縮放,旋轉
齊次矩陣
- 齊次座標就是將一個原本是n維的向量用一個n+1維向量來表示。
- 使用1×4矩陣,是相對點的三維座標進行齊次座標。
齊次座標變換 (x, y, z) -> (x × h, y × h, z × h, h) -> (xˊ, yˊ, zˊ, h)齊次座標還原 (xˊ, yˊ, zˊ, h) -> (x / h, y / h, z / h, 1) -> (x, y, z)
如果不使用1×4齊次矩陣和4×4變換矩陣?
只使用3×3變換矩陣:
m11, m12, m13{x, y, z} * { m21, m22, m23 } = {x‘, y‘, z‘} m31, m32, m33
xˊ=x × m11 + y × m21 + z × m31 在預先不對變數係數(m11, m21, m31)做其他計算的情況下,只能實現在各個座標軸的縮放
但是使用使用1×4齊次矩陣和4×4變換矩陣後
xˊ= x × m11 + y × m21 + z × m31 + 1 × m41m11=2 m21=0 m31=0 m41=8可同時實現向x軸正方向放大2倍,在沿著x軸正方向平移8個單位
引入齊次座標的目的主要是合并矩陣運算中的乘法和加法。基本變換矩陣
- 矩陣就是利用矩陣內特殊位置的值,在做矩陣乘法時,達到對點座標進行變換,下面時常用變換矩陣
3D動畫效果
iOS中的CALayer的3D本質上並不能算真正的3D,而只是3D在二維平面上的投影,檢視平面就是手機螢幕也就是xy軸組成的平面。
如此,只使用基本變換矩陣實現的平移、縮放、旋轉,不會有近大遠小的透視效果。
那該如何產生近大遠小呢?
要達到近大遠小目的,需要在系統做垂直投影前,先對圖層做一次視點變換。如此垂直投影別是視點觀察到的近大遠小的物體。
Layer的z軸的位置則是通過anchorPoint來指定的,所謂的anchorPoint(錨點)就是在變換中保持不變的點,也就是某個Layer在變換中的原點,xyz三軸相交於此點。為錨點常用位置
- 在原點(0 , 0)沿著Y軸的正方向,得到座標系, 首先在Z軸選擇一個視點
- 添加兩個child layer,觀察地區便能看到兩個child layer頂部的短線,綠色在前,紅色在後,且長度相等
- 綠線、紅線本來長度相等,通過視點投影后造成了“近大遠小”的透視效果
所以只要在iOS垂直投影前,對layer作視點投影變換,就能得到透視效果實踐透視原理
- 使用的座標系,紅點為觀察地區一點,對紅點做視點投影,得到綠點,同時對紅點做z軸的垂直線得到黑點。
- 簡化公式後,得到
方程1 ,綠點x軸的值只於視點z軸值有關
- 對紅點做h = 1的齊次座標(6, 0, 5, 1),通過乘以一個矩陣,得到變換後的綠點的齊次矩陣
- 變換後的矩陣只與視點z軸值有關,所以只設定m34,對(6, 0, 5, 1 + 5r)還原得到
方程2
至此只要修改變換矩陣m34的值為視點z軸值,便能得到相應的視點投影變換矩陣動畫的代碼實現
- 使用達達啟動頁面來實踐以上部分內容。PS:為了查看簡介,未對方法封裝
屬性申明
@property (weak, nonatomic) IBOutlet UIImageView *logoImg; //達達Logo@property (weak, nonatomic) IBOutlet UILabel *nameLab;// 達達@property (weak, nonatomic) IBOutlet UILabel *desLab;// 可靠配送,在你身邊
初始化設定,對兩個Label設定透明度為0,縮小到原來的0.5倍
- (void)viewDidLoad{ [super viewDidLoad]; self.nameLab.alpha = 0.f; self.nameLab.layer.transform = CATransform3DMakeScale(0.5f, 0.5f, 1.f); self.desLab.alpha = 0.f; self.desLab.layer.transform = CATransform3DMakeScale(0.5f, 0.5f, 1.f); }
對Label的動畫,使用UIView內建的block方式
- (void) animationDaDaLabel{ [UIView animateWithDuration:0.5f animations:^{ // 放大並模糊 self.nameLab.alpha = 0.5f; self.nameLab.layer.transform = CATransform3DMakeScale(1.2f, 1.2f, 1.f); self.desLab.alpha = 0.5f; self.desLab.layer.transform = CATransform3DMakeScale(1.2f, 1.2f, 1.f); } completion:^(BOOL finished) { [UIView animateWithDuration:0.5f animations:^{ // 恢複並清晰 self.nameLab.alpha = 1.f; self.nameLab.layer.transform = CATransform3DMakeScale(1.f, 1.f, 1.f); self.desLab.alpha = 1.f; self.desLab.layer.transform = CATransform3DMakeScale(1.f, 1.f, 1.f); }]; }];}
對Logo的動畫,使用CABasicAnimation對象
- (void) animationDaDaLogo{ CATransform3D transform = CATransform3DIdentity; transform.m34 = - 1 / 100.0f; // 設定視點在Z軸正方形z=100 // 動畫結束時,在Z軸負方向60 CATransform3D startTransform = CATransform3DTranslate(transform, 0, 0, -60); // 動畫結束時,繞Y軸逆時針旋轉90度 CATransform3D firstTransform = CATransform3DRotate(startTransform, M_PI_2, 0, 1, 0); // 通過CABasicAnimation修改transform屬性 CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"transform"]; // 向後移動同時繞Y軸逆時針旋轉90度 animation1.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; animation1.toValue = [NSValue valueWithCATransform3D:firstTransform]; // 雖然只有一個動畫,但用Group只為以後好擴充 CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; animationGroup.animations = [NSArray arrayWithObjects:animation1, nil]; animationGroup.duration = 0.5f; animationGroup.delegate = self; // 動畫回調,在動畫結束調用animationDidStop animationGroup.removedOnCompletion = NO; // 動畫結束時停止,不回複原樣 // 對logoImg的圖層應用動畫 [self.logoImg.layer addAnimation:animationGroup forKey:@"FristAnimation"];}
實際上,只對Logo使用“一半動畫”,Logo一邊向後移動,一邊逆時針繞Z軸旋轉90度,在此動畫結束後,通過回調補全剩下的“一半動畫”。利用這兩部分,實現,向後移動同時逆時針旋轉,旋轉到90度時,向前移動,同時繼續逆時針旋轉90度
- (void) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{ if (flag) { if (anim == [self.logoImg.layer animationForKey:@"FristAnimation"]) { CATransform3D transform = CATransform3DIdentity; transform.m34 = - 1 / 100.0f; // 設定視點在Z軸正方形z=100 // 動畫開始時,在Z軸負方向60 CATransform3D startTransform = CATransform3DTranslate(transform, 0, 0, -60); // 動畫開始時,繞Y軸順時針旋轉90度 CATransform3D secondTransform = CATransform3DRotate(startTransform, -M_PI_2, 0, 1, 0); // 通過CABasicAnimation修改transform屬性 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"]; // 向前移動同時繞Y軸逆時針旋轉90度 animation.fromValue = [NSValue valueWithCATransform3D:secondTransform]; animation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; animation.duration = 0.5f; // 對logoImg的圖層應用動畫 [self.logoImg.layer addAnimation:animation forKey:@"SecondAnimation"]; } }}
最終效果(PS:僅用於講解)
小結
根據以上內容,總結以下Core Animation相關重點
- 理解圖層意義,圖層是動畫的核心和載體
- 理解兩種平面座標系統的用途,在做3D視點變換的時,要通過三維座標系來協助思考
- 理解矩陣,齊次座標的使用目的
- 如果對成像原理不瞭解,可以搜尋相關資料
- 通過代碼進一步實踐
申明:本文的圖片源於蘋果CoreAnimation Programming Guide,如果想進一步瞭解,推薦學習蘋果官方文檔
簡析iOS動畫原理及實現——Core Animation