核心動畫(一)

核心動畫知識導入

CoreAnimation框架是基于OpenGL ES 手機端/OpenGL PC端(iOS13開始為Metal)與CoreGraphics圖像處理框架的一個跨平臺的框架。

了解CoreAnimation
核心動畫
  • CoreAnimation的封裝核心就是去簡化OpenGL圖形處理,原因是OpenGL的學習成本是非常高的;CoreAnimation也可以用作Mac OS開發(fā)
  • Core Graphics 核心繪圖
  • Graphics Hardware 圖形加速硬件,這個圖形硬件就是GPU芯片,GPU專門用來做計算,GPU并不是顯卡,而是顯卡需要GPU
  • iOS 13之后OpenGL更新為Metal,Metal只針對iOSMac OS系統(tǒng);而OpenGL ES是可以針對整個嵌入式環(huán)境(安卓、黑莓等都可以使用)
GPUCPU的區(qū)別

CPU: 中央處理器(英文Central Processing Unit)是一臺計算機的運算核心和控制核心;其功能主要是解釋計算機指令以及處理計算機軟件中的數(shù)據(jù)。
GPU:圖形處理器(英文Graphic Processing Unit)是一個專門的圖形核心處理器;GPU是顯卡的大腦,決定了該顯卡的檔次和大部分性能,同時也是2D顯卡3D顯卡的區(qū)別依據(jù);2D顯示芯片在處理3D圖像和特效時主要依賴CPU的處理能力,稱為軟加速;3D顯示芯片是將三維圖像和特效處理功能集中在顯示芯片內(nèi),也即所謂的硬件加速

主要區(qū)別如下:

  • CPU需要很強的通用性來處理各種不同的數(shù)據(jù)類型,同時又要邏輯判斷又會引入大量的分支跳轉(zhuǎn)中斷的處理,這些都使得CPU的內(nèi)部結(jié)構(gòu)異常復雜;而GPU面對的則是類型高度統(tǒng)一的相互無依賴的大規(guī)模數(shù)據(jù)和不需要被打斷純凈的計算環(huán)境
  • GPU采用了數(shù)量眾多的計算單元超長的流水線,但只有非常簡單的控制邏輯并省去了Cache,而CPU不僅被Cache占據(jù)了大量空間,而且還有有復雜的控制邏輯和諸多優(yōu)化電路,相比之下計算能力只是CPU很小的一部分。
核心動畫的優(yōu)點
  • 簡單易用的高性能混合編程模型
  • 用類似于UIView一樣,使?圖層來創(chuàng)建復雜的編程接口,更加高效的使用
  • 輕量化的數(shù)據(jù)結(jié)構(gòu),它可以同時顯示讓上百個圖層產(chǎn)?動畫效果
  • 一套?常簡單的動畫接口,能讓動畫運?在獨立的線程中,并可以獨?于主線程之外
  • 一旦動畫配置完成并啟動,核?動畫就能獨立并完全控制相應的動畫幀
  • 提?應用性能,應?程序只有當發(fā)生改變的時候才會重繪內(nèi)容,使用Core Animation 可以不使?其他圖形API,例如OpenGL來獲取高效的動畫性能.
  • 靈活的布局管理模型,允許圖層相對同級圖層的關系來設置屬性的位置和?小

核心動畫圖層樹結(jié)構(gòu)

CoreAnimation核心動畫的結(jié)構(gòu)圖
CoreAnimation分類

CAAnimation是所有動畫對象的父類(抽象類,虛類),實現(xiàn)CAMediaTiming協(xié)議,負責控制動畫的時間、速度和時間曲線等等,是一個抽象類。

核心動畫類中可以直接使用的類有五個,其中CAAnimationCAPropertyAnimation是抽象類,不能直接使用。

  • CAAnimationGroup: 動畫組,可以將很多種動畫合并到一起,組成動畫效果
  • CATransition: 轉(zhuǎn)場動畫效果
  • CAKeyframeAnimation: 關鍵幀動畫效果;values: 一個NSArray對象;里面的元素稱為關鍵幀(keyframe),動畫對象會在指定的時間(duration)內(nèi),依次顯示values數(shù)組中的每一個關鍵幀;簡單理解為,很多動畫幀執(zhí)行
  • CABasicAnimation: 基礎動畫,簡單常見的動畫效果
  • CASpringAnimation: iOS9.0之后新增的彈簧效果動畫,是CABasicAnimation的子類
CALayerUIView的區(qū)別
  • CALayer:繼承于NSObject,所以不具備響應不能處理用戶交互,負責繪制、渲染圖形
  • UIView: 繼承于UIResponder,所以可以進行事件響應,屬性CALayer負責圖形繪制與渲染;UIViewCALayer的delegate,可以實現(xiàn)一些簡單的CALayer的方法,但要實現(xiàn)稍微復雜些的動畫效果,就需要借助CALayer,如:陰影,圓角,帶顏色的邊框、3D變換、非矩形范圍透明遮罩、多級非線性動畫等,這也就是開發(fā)者為什么要使用CALayer的原因
  • UIView是用來管理CALayerCALayer才是用來展示

疑問:蘋果為什么要拆分成CALayerUIView兩個類呢?
CoreAnimation是iOS與Mac OS共用的框架,而iOS與Mac OX兩者的用戶交互方式是不同的,iOS是通過手勢觸摸,Mac OX是通過鍵盤鼠標;為了兼容兩者,單獨把CALayer拆出來只用來繪制渲染圖形

CALayerUIView的關系
CALayer與UIView的關系

每一個UIView上面都會有一個CALayer作為它的實例圖層屬性;我們添加的動畫實際上是針對CALayer來做。

CALayer的樹級關系一
CALayer的樹級關系二

Layer Tree圖層樹(模型數(shù))主要是設置一些屬性

  • 模型樹( layer tree):程序中接觸最頻繁,模型樹的對象是模型對象,儲存著動畫的目標值;當你修改layer的屬性時,便是通過模型樹上的對象
  • 呈現(xiàn)樹(presentation tree):包含正在運行中的動畫的動態(tài)值,與模型樹不同,呈現(xiàn)樹始終存儲著layer在屏幕當前的狀態(tài)值,呈現(xiàn)樹無法修改,只讀;可以通過讀取當前值,來做一些其他處理
  • 渲染樹(render tree):執(zhí)行實際的動畫,為CoreAnimation私有

小結(jié):動畫的三個動作創(chuàng)建執(zhí)行動畫的CALayer創(chuàng)建動畫、添加動畫

CALayer常用屬性詳解

動畫案例準備工作:新建空工程CoreAnimation,在Main.storyboard文件拖入一個UIView,背景色配置成紅色并進行關連,命名為redView

動畫案例一
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *redView;
@property (nonatomic,strong) CALayer *layer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 新建layer,并添加到self.view.layer上
    CALayer *layer = [CALayer layer];
    layer.frame = CGRectMake(100, 100, 100, 100);
    layer.backgroundColor = [UIColor greenColor].CGColor;
    _layer = layer;
    [self.view.layer addSublayer:layer];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    CABasicAnimation *animation = [CABasicAnimation animation];
    // 修改動畫y的位置到600
    animation.keyPath = @"position.y";
    animation.toValue = @600;
    animation.duration = 1;
    [_redView.layer addAnimation:animation forKey:nil];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

運行工程并點擊屏幕,我們會發(fā)現(xiàn)動畫執(zhí)行完畢后,又回到了初始位置,為什么會這樣?
其實在執(zhí)行動畫的過程中會有兩個圖層:layer層presentation層,真正移動的是presentation層;動畫開始時會先把layer層隱藏,讓presentation層做動畫;動畫結(jié)束后presentation層就會移除,layer層再出現(xiàn);原因是視圖的layer層根本沒有發(fā)生變化,動畫結(jié)束就會恢復到原來的狀態(tài)。

解決辦法:設置animation的兩個屬性

//解決動畫恢復到初始位置
//當動畫完成后,不把presentation層從render樹中移除(默認是移除的)
animation.removedOnCompletion = NO;
//當動畫結(jié)束后,把layer層狀態(tài)同步到presentation層;此時_redView的frame才會發(fā)生變化
animation.fillMode = kCAFillModeForwards;

CABasicAnimation相當于是一個數(shù)據(jù)模型,把該數(shù)據(jù)模型綁定到layer上面。

CABasicAnimation動畫的fillMode屬性介紹
  • kCAFillModeForwards:動畫結(jié)束后,layer會一直保持動畫最后的狀態(tài)
  • kCAFillModeBackwards:動畫開始前,只要將動畫加入一個layer,layer便立即進入動畫的初始狀態(tài)并等待動畫開始
  • kCAFillModeBothkCAFillModeForwardskCAFillModeBackwards兩者的結(jié)合,開始前保持動畫初始狀態(tài),結(jié)束后保持動畫的最后狀態(tài)
  • kCAFillModeRemoved:默認屬性
動畫案例二:隱式動畫

疑問:上面紅色圖層動畫結(jié)束恢復到原來的狀態(tài),恢復的過程給人的感覺是回彈動畫,這就是隱式動畫?

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    _layer.backgroundColor = [UIColor orangeColor].CGColor;
}

我們沒有給_layer層添加動畫,但是點擊頁面_layer層由綠色變成橙色的過程,有一種動畫的效果,這就是隱式動畫;隱式動畫是由CoreAnimation框架幫我們做的,其默認動畫時長是0.25秒,通過runloop來執(zhí)行。上面添加的CABasicAnimation屬于顯式動畫

動畫案例三:修改隱式動畫

如何修改系統(tǒng)的隱式動畫呢?

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //begin a new transaction
    [CATransaction begin];
    //設置隱式動畫的時長
    [CATransaction setAnimationDuration:2.0];
    _layer.backgroundColor = [UIColor orangeColor].CGColor;
    //動畫執(zhí)行完成的回調(diào)
    [CATransaction setCompletionBlock:^{
        //添加轉(zhuǎn)場動畫
        //rotate the layer 90 degrees
        CGAffineTransform transform = self.layer.affineTransform;
        transform = CGAffineTransformRotate(transform, M_PI_2);
        self.layer.affineTransform = transform;

    }];
    [CATransaction commit];
}

CATransaction類沒有屬性,也沒有實例方法;不能通過alloc init去創(chuàng)建,只能通過begincommit壓棧出棧的方式來管理。

動畫案例四:CALayer常用屬性 - Contents
- (void)viewDidLoad {
    [super viewDidLoad];
    //除了UIImageView 能夠顯示圖片,layer也可以加載圖片
    UIImage *image = [UIImage imageNamed:@"test.png"];
    //不用CGImage的話,圖片加載不出來
    self.view.layer.contents = (__bridge id)(image.CGImage);
    //填充方式
    self.view.contentMode = UIViewContentModeScaleAspectFit;
    self.view.layer.contentsGravity = kCAGravityResizeAspect;
}

Contents屬性是id類型,原因是在Mac OS系統(tǒng)上Contents屬性對CGImageNSImage都會起作用,image.CGImage實際上賦值的是CGImageRef類型,CGImageRef指向的是CGImage結(jié)構(gòu),需要進行橋接處理,開發(fā)中如果需要設置背景圖可以使用layerContents是id類型就可以直接在layercontents上面加載一張圖片。

  • CALayer常用屬性 - contentsScale
self.view.layer.contentsScale = [[UIScreen mainScreen] scale];

當用代碼設置contents圖片時,要?動設置圖層的contentsScale屬性,避免Retina屏幕顯示錯誤。

  • CALayer常用屬性 - makeToBounds
    makeToBounds屬性類似于UIView中的clipsToBounds屬性,含義:是否顯示超出邊界的內(nèi)容?

  • CALayer常用屬性 - contentsRect

  1. contentsRect不是按點來計算的,而是按照單位坐標
  2. OpenGL的坐標系橫向是從-1到1,縱向從1到-1的過程,center坐標就是{0, 0},而手機都是長方形的,所以OpenGL會把單位坐標系轉(zhuǎn)換為設備坐標系,不同設備有不同的坐標;
點- 像素 - 單位區(qū)分
contentsRect屬性

contentsRectcontentsGravity屬性要靈活很多,contentsGravity屬性只能展示圖片固定的位置與大小,而contentsRect可以展示圖片的任意內(nèi)容(只要把單位坐標計算好即可);如果contentsGravity不能滿足我們的需求時,可以使用contentsRect屬性。

CALayer中HitTest屬性的實際使用

下面我們來了解一下UIViewCALayer圖層幾何

圖層幾何

frame是相對于父視圖的坐標,bounds是從視圖自身出發(fā)即內(nèi)部坐標,centerposition相當于父視圖上面的一個錨點

錨點

錨點就是視圖中的center屬性position屬性,實際上就是一個坐標,錨點anchorPointlayer的屬性(即position)。

  • CALayer常用屬性 - ZPosition
ZPosition

手機開發(fā)是基于二維平面的,并不是一個優(yōu)秀的三維圖形顯示載體,但是手機中會出現(xiàn)一些立體的粒子效果,于是CALayer就提供了一個屬性ZPosition,也就意味著CALayer是三維的;OpenGLMetal默認是一個3D圖形api,默認坐標系是三維坐標系,在描述平面圖形的時候,z坐標是0;核心動畫把z坐標單獨摘出來就是ZPosition;呈現(xiàn)粒子的時候就必須要用到ZPosition。

動畫案例準備工作:新建空工程CoreAnimation2,在Main.storyboard文件拖入兩個UIView,背景色配置成橙色紅色并進行關連,命名為view1view2,兩個view的層級關系如下

兩個view的層級
// ViewController.h文件
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view1.layer.zPosition = 1.0;
}

運行工程,我們發(fā)現(xiàn)橙色view出現(xiàn)在了紅色view上面,原因是什么呢?
在平面圖形上面z軸默認值是0,這里把橙色viewz軸值設置為1,就意味著把橙色view放在了上面;修改了zPosition屬性的值,就更改了深度緩沖區(qū)-深度測試,這里主要與深度緩沖區(qū)有關,本質(zhì)并不是修改圖層的層級關系。

  • CALayer常用屬性 -Hit Testing

動畫案例準備工作:新建空工程CoreAnimation3,在Main.storyboard文件拖入一個UIView,背景色配置成紅色并進行關連,命名為layerView,如下圖所示

CoreAnimation3工程
#import "ViewController.h"

@interface ViewController ()
@property (strong, nonatomic) UIView *layerView;
@property (strong,nonatomic) CALayer *blueLayer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //1.Create sublayer
    self.blueLayer = [CALayer layer];
    self.blueLayer.frame = CGRectMake(0, 0, 100, 100);
    self.blueLayer.backgroundColor = [UIColor blueColor].CGColor;
    [self.layerView.layer addSublayer:self.blueLayer];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //1.get touchu position relative to main view(獲取相對于主視圖的觸摸位置)
    CGPoint point = [[touches anyObject]locationInView:self.view];
    //2.get touched layer
    CALayer *layer = [self.layerView.layer hitTest:point];
    //3.get layer using using hitTest
    if(layer == self.blueLayer)
    {           
        NSLog(@"Inside Blue layer");
    } else if (layer == self.layerView.layer) {
        NSLog(@"Inside Red layer");
    }
}

@end

// 運行工程,查看打印日志,成功獲取點擊的layer層
2022-09-23 22:25:56.044062+0800 CoreAnimation3[22331:14483795] Inside Blue layer
2022-09-23 22:25:59.342807+0800 CoreAnimation3[22331:14483795] Inside Red layer

CALayer不能響應事件,但是CALayerHit Testing屬性能夠獲取到點擊的圖層。

hitTest方法介紹
// point : 在接收器的局部坐標系(界)中指定的點
// event : 系統(tǒng)保證調(diào)用此方法的事件。如果從事件處理代碼外部調(diào)用此方法,則可以指定nil
// returnValue : 視圖對象是當前視圖和包含點的最遠的后代。
//               如果點完全位于接收方的視圖層次結(jié)構(gòu)之外,則返回nil
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event

不止CALayer中有hitTest方法,UIView中同樣有hitTest方法;UIView中該方法的作用:在視圖的層次結(jié)構(gòu)中尋找一個最合適的view來響應觸摸事件;該方法會被系統(tǒng)調(diào)用,調(diào)用時如果返回nil,即事件被丟棄,否則返回最合適的view來響應事件。

  • Hit Test調(diào)用順序
touch -> UIApplication -> UIWindow -> UIViewController.view -> subViews -> ....-> 合適的view
  • 事件的傳遞順序,與Hit Test調(diào)用順序剛好相反
 view -> superView ...- > UIViewController.view -> UIViewController ->UIWindow -> UIApplication -> 丟棄事件

說明:

  1. 首先由view來嘗試處理事件,如果處理不了,事件將被傳遞到父視圖superView
  2. superView也嘗試處理事件,如果處理不了,繼續(xù)傳遞給它的父視圖UIViewController.view
  3. UIViewController.view嘗試處理事件,如果處理不了,把該事件傳遞給UIViewController
  4. UIViewController嘗試處理事件,如果處理不了,把事件傳遞給UIWindow
  5. 主窗口UIWindow嘗試來處理事件, 如果處理不了,將傳遞給應用單例UIApplication
  6. 如果UIApplication也處理不了,該事件將被丟棄
UIViewHit Test底層實現(xiàn)思路

常見的hitTest不實現(xiàn)的四種情況(即view不響應事件情況)

  • view.userInteractionEnabled = NO;
  • view.hidden = YES;
  • view.alpha < 0.05;
  • view 超出 superview 的 bounds;
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //1.是否響應事件的必然性條件
    if (self.userInteractionEnabled == NO || self.alpha < 0.05 || self.hidden == YES)
    {
        return nil;
    }
    
    //2.touch的point在self.bounds內(nèi)
    if ([self pointInside:point withEvent:event])
    {
        for (UIView *subView in self.subviews)
        {
            //進行坐標轉(zhuǎn)化
            CGPoint coverPoint = [subView convertPoint:point fromView:self];
            // 調(diào)用子視圖的 hitTest 重復上面的步驟。找到了,返回hitTestview ,沒找到返回有自身處理
            UIView *hitTestView = [subView hitTest:coverPoint withEvent:event];
            if (hitTestView)
            {
                return hitTestView;
            }
        }
        return self;
    }
    return nil;
}

Hit Testing應?場景-?視圖超出?視圖范圍,點擊沒有響應;如果需求是讓超出父視圖的范圍也能點擊,代碼實現(xiàn)如下

?視圖超出?視圖范圍
實現(xiàn)思路

仿射變換數(shù)學原理講解

圖層變換
  • 剛體變換:只有物體的位置(平移變換)和朝向(旋轉(zhuǎn)變換)發(fā)生改變,而形狀不變;剛性變換是最一般的變換。
  • 仿射變換:仿射變換具有兩個旋轉(zhuǎn)因子兩個縮放因子,因此具有6個自由度。不具有保角性和保持距離比的性質(zhì),但是原圖平行線變換后仍然是平行線。仿射變換主要包括平移變換、旋轉(zhuǎn)變換縮放變換(也叫尺度變換)、傾斜變換(也叫錯切變換、剪切變換、偏移變換)、翻轉(zhuǎn)變換,有六個自由度。
  • 投影變換:是最一般的線性變換,有8個自由度;射影變換保持重合關系和交比不變。但不會保持平行性。即它會使得仿射變換產(chǎn)生非線性效應
仿射變換
投影的兩種類型

對于復雜的立體圖形,我們要想平移是非常困難的;但是通過仿射變換,我們就可以很容易的實現(xiàn),可以對立體圖形的任意頂點進行平移,實現(xiàn)代碼如下圖所示:

對立體圖形進行平移

核心動畫官方文檔地址

下面我們介紹官方文檔中的幾個矩陣
矩陣

上圖展現(xiàn)了常見的transformations的矩陣配置;任何乘以identity矩陣的coordinate將不會變化,當乘以其他矩陣時,coordinate的變化和矩陣每個分量都有關;例如,沿著X軸平移,我們需要提供非零的 tx 分量并讓tytz為0;對于旋轉(zhuǎn)操作,我們應該提供合適的 sinecosine 值。

  • Identity:單元矩陣
  • Translate:平移
  • Scale:縮放
  • Rotate around X axis:圍繞X軸旋轉(zhuǎn),X值不變;這里的四維原因是OpenGL ES/Metal中描述頂點除了圍繞X、Y、Z軸,還有一個W縮放因子。
  • Rotate around Y axis:圍繞Y軸旋轉(zhuǎn),Y值不變
  • Rotate around Z axis:圍繞Z軸旋轉(zhuǎn),Z值不變
圍繞任意軸旋轉(zhuǎn)

圍繞任意軸旋轉(zhuǎn)中參數(shù)n表示向量(x,y,z),第二個參數(shù)表示角度。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

  • [toc] 前言 Core Animation提供了一種通用系統(tǒng),可對應用程序的視圖和其他視覺元素進行動畫處理。C...
    清風烈酒2157閱讀 388評論 0 1
  • iOS核心動畫 核心動畫框架 CoreAnimation框架是基于OpenGL與CoreGraphics圖像處理框...
    小白PK大牛閱讀 435評論 0 0
  • 本文是要點型筆記,建議先閱讀以下參考內(nèi)容后再閱讀,完成知識體系的構(gòu)建:[1] View Programming ...
    SvenLearn閱讀 3,233評論 2 43
  • 最近買了幾本書,接下來會陸續(xù)寫寫筆記,把書中干貨分享出來 筆記主要總結(jié)了書中講了什么內(nèi)容,希望讀者讀完后遇到相應的...
    搬磚人666閱讀 2,152評論 0 6
  • 基礎 核心動畫是 iOS 和 MacOS 上的圖形渲染和動畫基礎結(jié)構(gòu),用于為應用的視圖和其他視覺元素設置動畫。 核...
    davon閱讀 2,287評論 0 8

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