核心动画(1)

[toc]

前言

Core Animation提供了一种通用系统,可对应用程序的视图和其他视觉元素进行动画处理。Core Animation不能替代您的应用程序视图。相反,它是一种与视图集成以提供更好的性能和动画效果的技术。它通过将视图的内容缓存到可以由图形硬件直接操纵的位图中来实现此行为。在某些情况下,这种缓存行为可能需要您重新考虑如何呈现和管理应用程序的内容,但是大多数情况下,您在不知道它存在的情况下使用Core Animation。除了缓存视图内容之外,Core Animation还定义了一种方法,该方法可以指定任意视觉内容,将该内容与视图集成以及将其与其他所有内容一起设置为动画

核心动画知识

image.png

从图中可以看出,最底层是图形硬件(GPU);上层是OpenGLCoreGraphics,提供一些接口来访问GPU;再上层的CoreAnimation在此基础上封装了一套动画的API。最上面的UIKit属于应用层,处理与用户的交互。所以,学习这CoreAnimation`也会涉及一些图形学的知识,了解这些有助于我们更顺手的使用以及更高效的解决问题。

  • Core Animation 结构图
image.png
  • Core Animation Introduction
  1. 简单易易⽤用的⾼高性能混合编程模型
  2. ⽤用类似于视图⼀一样,使⽤用图层来创建复杂的编程接⼝口
  3. 轻量量化的数据结构,它可以同时显示让上百个图层产⽣生动画效果
  4. 一套⾮非常较简单的动画接⼝口,能让动画运⾏行行在独⽴立的线程中,并可以独⽴立于主线程之外.
  5. 一旦动画配置完成并启动,核⼼心动画就能独⽴立并完全控制相应的动画帧.
  6. 提⾼高应⽤用性能.应⽤用程序只有当发⽣生改变的时候才会重绘内容. 使⽤用Core Animation 可以不不使⽤用其他图形API,例例如 OpenGL 来获取⾼高效的动画性能.
  7. . 灵活的布局管理理模型,允许图层相对同级图层的关系来设置属性的位置和⼤大⼩小

核心动画图层树结构

使用Core Animation的应用程序具有三组图层对象.

  • 图层树是最的那些您的应用程序进行交互。该树中的对象是存储任何动画的目标值的模型对象。每当更改图层的属性时,都将使用这些对象之一。
  • 呈现树中的对象包含任何正在运行的动画的运行中值。图层树对象包含动画的目标值,而表示树中的对象则反映屏幕上显示的当前值。您永远不要修改此树中的对象。相反,您可以使用这些对象读取当前动画值,也许可以从这些值开始创建新动画。
  • 渲染树中的对象执行实际的动画,并且是Core Animation专有的。

与窗口关联的图层树

image.png

三组图层对象

image.png

图层与视图

一个视图就是在屏幕上显示的一个矩形块(比如图片,文字或者视频),它能够拦截类似于鼠标点击或者触摸手势等用户输入。视图在层级关系中可以互相嵌套,一个视图可以管理它的所有子视图的位置.

image.png

iOS当中,所有的视图都从一个叫做UIVIew的基类派生而来,UIView可以处理触摸事件,可以支持基于Core Graphics绘图,可以做仿射变换(例如旋转或者缩放),或者简单的类似于滑动或者渐变的动画。

图层与视图之间的关系

图层不能替代您应用程序的视图,也就是说,您不能仅基于图层对象创建可视界面。图层为您的视图提供基础结构。特别是,图层使绘制和动画化视图内容并使其保持动画状态并保持较高的帧速率更加容易和有效。但是,许多事情是图层无法做到的。层不处理事件,绘制内容,参与响应者链或执行其他许多事情。因此,每个应用程序仍必须具有一个或多个视图来处理这些类型的交互.

CALayer

CALayer类在概念上和UIView类似,同样也是一些被层级关系树管理的矩形块,同样也可以包含一些内容(像图片,文本或者背景色),管理子图层的位置。它们有一些方法和属性用来做动画和变换。和UIView最大的不同是CALayer不处理用户的交互

CALayer并不清楚具体的响应链(iOS通过视图层级关系用来传送触摸事件的机制),于是它并不能够响应事件,即使它提供了一些方法来判断是否一个触点在图层的范围之内.

平行的层级关系

每一个UIview都有一个CALayer实例的图层属性,也就是所谓的backing layer视图的职责就是创建并管理这个图层,以确保当子视图在层级关系中添加或者被移除的时候,他们关联的图层也同样对应在层级关系树当中有相同的操作.

image.png

实际上这些背后关联的图层才是真正用来在屏幕上显示和做动画,UIView仅仅是对它的一个封装,提供了一些iOS类似于处理触摸的具体功能,以及Core Animation底层方法的高级接口.

但是为什么iOS要基于UIView和CALayer提供两个平行的层级关系呢?
为什么不用一个简单的层级来处理所有事情呢?

原因在于要做职责分离,这样也能避免很多重复代码。在iOSMac OS两个平台上,事件和用户交互有很多地方的不同,基于多点触控的用户界面和基于鼠标键盘有着本质的区别,这就是为什么iOSUIKitUIView,但是Mac OSAppKitNSView的原因。他们功能上很相似,但是在实现上有着显著的区别

图层的能力

我们已经证实了图层不能像视图那样处理触摸事件,那么他能做哪些视图不能做的呢?这里有一些UIView没有暴露出来的CALayer的功能:

  1. 阴影,圆角,带颜色的边框
  2. 3D变换
  3. 非矩形范围
  4. 透明遮罩
  5. 多级非线性动画

简单使用图层
在屏幕上添加一个蓝色view,可以通过设置CALayerbackgroundColor

 CALayer *layer = [CALayer layer];
    layer.frame = CGRectMake(100, 100, 100, 100);
    layer.backgroundColor = [UIColor greenColor].CGColor;
    _layer = layer;
    [self.view.layer addSublayer:layer];

然而,当满足以下条件的时候,你可能更需要使用CALayer而不是UIView:

  • 开发同时可以在Mac OS上运行的跨平台应用
  • 使用多种CALayer的子类(见第六章,“特殊的图层“),并且不想创建额外的UIView去包封装它们所有
  • 做一些对性能特别挑剔的工作,比如对UIView一些可忽略不计的操作都会引起显著的不同(尽管如此,你可能会直接想使用OpenGL绘图)

CALayer 常用属性详解

contents

CALayer 有一个属性叫做contents,这个属性的类型被定义为id,意味着它可以是任何类型的对象。在这种情况下,你可以给contents属性赋任何值,你的app仍然能够编译通过。但是,在实践中,如果你给contents赋的不是CGImage,那么你得到的图层将是空白的。

  • 如果要给图层的寄宿图赋值
UIImage *image = [UIImage imageNamed:@"test.png"];

  //add it directly to our view's layer
  self.layerView.layer.contents = (__bridge id)image.CGImage;

contents这个奇怪的表现是由Mac OS的历史原因造成的。它之所以被定义为id类型,是因为在Mac OS系统上,这个属性对CGImageNSImage类型的值都起作用。如果你试图在iOS平台上将UIImage的值赋给它,只能得到一个空白的图层。一些初识Core AnimationiOS开发者可能会对这个感到困惑

contentsGravity

CALayercontentMode对应的属性叫做contentsGravity,但是它是一个NSString类型,而不是像对应的UIKit部分,那里面的值是枚举。contentsGravity可选的常量值有以下一些:


* kCAGravityCenter
* kCAGravityTop
* kCAGravityBottom
* kCAGravityLeft
* kCAGravityRight
* kCAGravityTopLeft
* kCAGravityTopRight
* kCAGravityBottomLeft
* kCAGravityBottomRight
* kCAGravityResize
* kCAGravityResizeAspect
* kCAGravityResizeAspectFill

  • 使用
self.layerView.layer.contentsGravity = kCAGravityResizeAspect;

contentsScale
contentsScale属性定义了寄宿图的像素尺寸和视图大小的比例,默认情况下它是一个值为1.0的浮点数。

如果contentsScale设置为1.0,将会以每个点1个像素绘制图片,如果设置为2.0,则会以每个点2个像素绘制图片,这就是我们熟知的Retina屏幕。

  • 使用

当我们使用UIImage类去读取我们的雪人图片的时候,他读取了高质量的Retina版本的图片。但是当我们用CGImage来设置我们的图层的内容时,拉伸这个因素在转换的时候就丢失了。不过我们可以通过手动设置contentsScale来修复这个问题


//set the contentsScale to match image
  self.layerView.layer.contentsScale = image.scale;

maskToBounds

UIView有一个叫做clipsToBounds的属性可以用来决定是否显示超出边界的内容,CALayer对应的属性叫做masksToBounds.

contentsRect

CALayercontentsRect属性允许我们在图层边框里显示寄宿图的一个子域。这涉及到图片是如何显示和拉伸的,所以要比contentsGravity灵活多了

boundsframe不同,contentsRect不是按点来计算的,它使用了单位坐标,单位坐标指定在01之间,是一个相对值(像素和点就是绝对值)。所以他们是相对与寄宿图的尺寸的。iOS使用了以下的坐标系统:

  • 点 —— 在iOSMac OS中最常见的坐标体系。点就像是虚拟的像素,也被称作逻辑像素。在标准设备上,一个点就是一个像素,但是在Retina设备上,一个点等于2*2个像素。iOS用点作为屏幕的坐标测算体系就是为了在Retina设备和普通设备上能有一致的视觉效果。

  • 像素 —— 物理像素坐标并不会用来屏幕布局,但是仍然与图片有相对关系。UIImage是一个屏幕分辨率解决方案,所以指定点来度量大小。但是一些底层的图片表示如CGImage就会使用像素,所以你要清楚在Retina设备和普通设备上,他们表现出来了不同的大小。

  • 单位 —— 对于与图片大小或是图层边界相关的显示,单位坐标是一个方便的度量方式, 当大小改变的时候,也不需要再次调整。单位坐标在OpenGL这种纹理坐标系统中用得很多,Core Animation中也用到了单位坐标。

默认的contentsRect是{0, 0, 1, 1},这意味着整个寄宿图默认都是可见的,如果我们指定一个小一点的矩形,图片就会被裁剪


image.png

contentsCenter
contentsCenter其实是一个CGRect,它定义了一个固定的边框和一个在图层上可拉伸的区域。

  • self.view.layer.contentsCenter = CGRectMake(0, 0, 1, 1);
image.png
  • self.view.layer.contentsCenter = CGRectMake(0, 0, 0.5, 0.5);
image.png

图层几何学

UIView有三个比较重要的布局属性:frameboundscenterCALayer对应地叫做frameboundsposition。为了能清楚区分,图层用了“position”,视图用了“center”,但是他们都代表同样的值。

如图:

image.png

anchorPoint
图层的anchorPoint通过position来控制它的frame的位置,你可以认为anchorPoint是用来移动图层的把柄。

image.png
image.png

anchorPoint相当于支点,可以用作旋转变化、平移、缩放.

如果修改anchorPointlayerframe会发生改变,position不会发生改变.修改positionanchorPoint中任何一个属性都不影响另一个属性.

  • 视图frame计算公式:
frame.origin.x = position.x - anchorPoint.x * bounds.size.width;  
frame.origin.y = position.y - anchorPoint.y * bounds.size.height;

坐标系

Core Graphics源于Mac OS X系统,在Mac OS X中,坐标原点在左下方并且正y坐标是朝上的,而在iOS中,原点坐标是在左上方并且正y坐标是朝下的。在大多数情况下,这不会出现任何问题,因为图形上下文的坐标系统是会自动调节补偿的。但是创建和绘制一个CGImage`对象时就会暴露出倒置问题.

ZPosition

UIView严格的二维坐标系不同,CALayer存在于一个三维空间当中。除了我们已经讨论过的positionanchorPoint属性之外,CALayer还有另外两个属性,zPositionanchorPointZ,二者都是在Z轴上描述图层位置的浮点类型。

image.png

此属性的默认值为0。更改此属性的值会更改屏幕上各层的从前到后的顺序。较高的值比较低的值在视觉上更靠近该图层

CALayer中HitTest属性的实际使用

响应者对象(Responder Object),顾名思义,指的是有响应和处理事件能力的对象。响应者链就是由一系列的响应者对象构成的一个层次结构。

第一响应者(First responder)指的是当前接受触摸的响应者对象(通常是一个UIView对象),即表示当前该对象正在与用户交互,它是响应者链的开端。整个响应者链和事件分发的使命都是找出第一响应者。

  • hitTest:withEven:方法的处理流程如下

1. 首先调用当前视图的pointInside:withEvent:`方法判断触摸点是否在当前视图内;

若返回NO,则hitTest:withEvent:返回nil;

若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕.

如图:

image.png
  1. AUIWindow的根视图,因此,UIWindwo对象会首相对A进行hit-test

2、显然用户点击的范围是在A的范围内,因此,pointInside:withEvent:返回了YES,这时会继续检查A的子视图;

3、这时候会有两个分支,BC

点击的范围不再B内,因此B分支的pointInside:withEvent:返回NO,对应的hitTest:withEvent:返回nil

点击的范围在C内,即CpointInside:withEvent:返回YES;

4、这时候有DE两个分支:

点击的范围不再D内,因此DpointInside:withEvent:返回NO,对应的hitTest:withEvent:返回nil

点击的范围在E内,即EpointInside:withEvent:返回YES,由于E没有子视图(也可以理解成对E的子视图进行hit-test时返回了nil),因此,EhitTest:withEvent:会将E返回,再往回回溯,就是ChitTest:withEvent:返回E--->>AhitTest:withEvent:返回E

至此,本次点击事件的第一响应者就通过响应者链的事件分发逻辑成功的找到了。

若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;
如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。

如果最终hit-test没有找到第一响应者,或者第一响应者没有处理该事件,则该事件会沿着响应者链向上回溯,如果UIWindow实例和UIApplication实例都不能处理该事件,则该事件会被丢弃;

  • hitTest:withEvent:方法将会忽略

1 .忽略隐藏(hidden=YES)的视图

  1. 禁止用户操作(userInteractionEnabled=YES)的视图

  2. alpha级别小于0.01(alpha<0.01)的视图

  3. 如果一个子视图的区域超过父视图的bound区域(父视图的clipsToBounds 属性为NO,这样超过父视图bound区域的子视图内容也会显示).

那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法会返回NO,这样就不会继续向下遍历子视图了。当然,也可以重写pointInside:withEvent:方法来处理这种情况。

Hit Testing 应⽤用场景

  1. 事件穿透
image.png
  1. ⼦子视图超出父视图范围
image.png
image.png

仿射变换数学原理讲解

仿射变换 AffineTransform,在iOS中的实现类是CGAffineTransformCATransform3D,很多动画效果都需要用到仿射去完成,可以说仿射是动画基础.

原理是利用矩阵的相乘,得到变换后的坐标系.

CGAffineTransform

  • CGAffineTransform是一个可以和二维空间向量(例如CGPoint)做乘法的3X2的矩阵
image.png
CGAffineTransformMakeRotation(CGFloat angle)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)
  • 使用

CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4);
    self.layerView.layer.affineTransform = transform;

混合变换

Core Graphics提供了一系列的函数可以在一个变换的基础上做更深层次的变换,如果做一个既要缩放又要旋转的变换.

CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)

当操纵一个变换的时候,初始生成一个什么都不做的变换很重要--也就是创建一个CGAffineTransform类型的空值,矩阵论中称作单位矩阵,Core Graphics同样也提供了一个方便的常量:

CGAffineTransformIdentity
  • 使用
CGAffineTransform transform = CGAffineTransformIdentity; 
    //scale by 50%
    transform = CGAffineTransformScale(transform, 0.5, 0.5);
    //rotate by 30 degrees
    transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0);
    //translate by 200 points
    transform = CGAffineTransformTranslate(transform, 200, 0);
    //apply transform to layer
    self.layerView.layer.affineTransform = transform;

CATransform3D

CATransform3D也是一个矩阵,但是和2x3的矩阵不同,CATransform3D是一个可以在3维空间内做变换的4x4的矩阵(图5.6)。

image.png

苹果提供的可变换的矩阵:


image.png

3D平移旋转多处了一个z参数,并且旋转函数除了angle之外多出了x,y,z三个参数,分别决定了每个坐标轴方向上的旋转:

CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz) 
CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352