iOS核心动画高级技巧-1

作者 : 开心源码 本文共7566个字,预计阅读时间需要19分钟 发布时间: 2022-05-13 共205人阅读

image

1. 图层树

图层的树状结构

巨妖有图层,洋葱也有图层,你有吗?我们都有图层 — 史莱克

Core Animation其实是一个令人误会的命名。你可能认为它只是用来做动画的,但实际上它是从一个叫做Layer Kit这么一个不怎样和动画有关的名字演变而来,所以做动画这只是Core Animation特性的冰山一角。

Core Animation是一个复合引擎,它的职责就是尽可能快地组合屏幕上不同的可视内容,这个内容是被分解成独立的图层,存储在一个叫做图层树的体系之中。于是这个树形成了UIKit以及在iOS应用程序当中你所能在屏幕上看见的一切的基础。

在我们探讨动画之前,我们将从图层树开始,涉及一下Core Animation的静态组合以及布局特性。

1.1 图层与视图

图层与视图

假如你曾经在iOS或者者Mac OS平台上写过应用程序,你可能会对视图的概念比较熟习。一个视图就是在屏幕上显示的一个矩形块(比方图片,文字或者者视频),它能够阻拦相似于鼠标点击或者者触摸手势等客户输入。视图在层级关系中可以互相嵌套,一个视图可以管理它的所有子视图的位置。图1.1显示了一种典型的视图层级关系

1.2 图层的能力

图层的能力

假如说CALayer是UIView内部实现细节,那我们为什么要全面地理解它呢?苹果当然为我们提供了柔美简洁的UIView接口,那么我们能否就没必要直接去解决Core Animation的细节了呢?

一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:1012951431, 分享BAT,阿里面试题、面试经验,探讨技术, 大家一起交流学习成长!希望帮助开发者少走弯路。

某种意义上说确实是这样,对少量简单的需求来说,我们的确没必要解决CALayer,由于苹果已经通过UIView的高级API间接地使得动画变得很简单。

但是这种简单会不可避免地带来少量灵活上的缺陷。假如你稍微想在底层做少量改变,或者者使用少量苹果没有在UIView上实现的接口功能,这时除了介入Core Animation底层之外别无选择。

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

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

我们将会在后续章节中探究这些功能,首先我们要关注一下在应用程序当中CALayer是怎么被利用起来的。

1.3 使用图层

使用图层

首先我们来创立一个简单的项目,来操纵少量layer的属性。打开Xcode,使用Single View Application模板创立一个工程。

在屏幕中央创立一个小视图(大约200 X 200的尺寸),当然你可以手工编码,或者者使用Interface Builder(随你方便)。确保你的视图控制器要增加一个视图的属性以便可以直接访问它。我们把它称作layerView。

运行项目,应该能在浅灰色屏幕背景中看见一个白色方块,假如没看见,可能需要调整一下背景window或者者view的颜色

之后即可以在代码中直接引用CALayer的属性和方法。在清单1.1中,我们用创立了一个CALayer,设置了它的backgroundColor属性,而后增加到layerView背后相关图层的子图层(这段代码的前提是通过IB创立了layerView并做好了连接),图1.5显示了结果。

清单1.1 给视图增加一个蓝色子图层

#import "ViewController.h"#import @interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *layerView;@end@implementation ViewController- (void)viewDidLoad{    [super viewDidLoad];    //create sublayer    CALayer *blueLayer = [CALayer layer];    blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);    blueLayer.backgroundColor = [UIColor blueColor].CGColor;    //add it to our view    [self.layerView.layer addSublayer:blueLayer];}@end

1.4 总结

总结

这一章阐述了图层的树状结构,说明了如何在iOS中由UIView的层级关系形成的一种平行的CALayer层级关系,在后面的试验中,我们创立了自己的CALayer,并把它增加到图层树中。

在第二章,“图层关联的图片”,我们将要研究一下CALayer关联的图片,以及Core Animation提供的操作显示的少量特性。

2. 寄宿图

寄宿图

图片胜过千言万语,界面抵得上千图片 ——Ben Shneiderman

我们在第一章『图层树』中详情了CALayer类并创立了一个简单的有蓝色背景的图层。背景颜色还好啦,但是假如它仅仅是展示了一个单调的颜色未免也太无聊了。事实上CALayer类能够包含一张你喜欢的图片,这一章节我们将来探究CALayer的寄宿图(即图层中包含的图)。

2.1 contents属性

contents属性

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

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

头疼的不仅仅是我们刚才提到的这个问题。事实上,你真正要赋值的类型应该是CGImageRef,它是一个指向CGImage结构的指针。UIImage有一个CGImage属性,它返回一个”CGImageRef”,假如你想把这个值直接赋值给CALayer的contents,那你将会得到一个编译错误。由于CGImageRef并不是一个真正的Cocoa对象,而是一个Core Foundation类型。

虽然Core Foundation类型跟Cocoa对象在运行时貌似很像(被称作toll-free bridging),他们并不是类型兼容的,不过你可以通过bridged关键字转换。假如要给图层的寄宿图赋值,你可以按照以下这个方法:

layer.contents = (__bridge id)image.CGImage;

假如你没有使用ARC(自动引用计数),你就不需要 __bridge 这部分。但是,你干嘛不用ARC?!

让我们来继续修改我们在第一章新建的工程,以便能够展现一张图片而不仅仅是一个背景色。我们已经用代码的方式建立一个图层,那我们就不需要额外的图层了。那么我们就直接把layerView的宿主图层的contents属性设置成图片。

清单2.1 升级后的代码。

@implementation ViewController- (void)viewDidLoad{  [super viewDidLoad]; //load an image  UIImage *image = [UIImage imageNamed:@"Snowman.png"];  //add it directly to our view's layer  self.layerView.layer.contents = (__bridge id)image.CGImage;}@end

图表2.1 在UIView的宿主图层中显示一张图片

image

我们用这些简单的代码做了一件很有趣的事情:我们利用CALayer在一个普通的UIView中显示了一张图片。这不是一个UIImageView,它不是我们通常用来展现图片的方法。通过直接操作图层,我们使用了少量新的函数,使得UIView更加有趣了。

contentGravity

你可能已经注意到了我们的雪人看起来有点。。。胖 ==! 我们加载的图片并不恰好是一个方的,为了适应这个视图,它有一点点被拉伸了。在使用UIImageView的时候遇到过同样的问题,处理方法就是把contentMode属性设置成更合适的值,像这样:

view.contentMode = UIViewContentModeScaleAspectFit;

这个方法基本和我们遇到的情况的处理方法已经接近了(你可以试一下 🙂 ),不过UIView大多数视觉相关的属性比方contentMode,对这些属性的操作其实是对对应图层的操作。

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

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

cotentMode一样,contentsGravity的目的是为了决定内容在图层的边界中怎样对齐,我们将使用kCAGravityResizeAspect,它的效果等同于UIViewContentModeScaleAspectFit, 同时它还能在图层中等比例拉伸以适应图层的边界。

self.layerView.layer.contentsGravity = kCAGravityResizeAspect;

图2.2 可以看到结果

图2.3 用错误的contentsScale属性显示Retina图片

如你所见,我们的雪人不仅有点大还有点像素的颗粒感。那是由于和UIImage不同,CGImage没有拉伸的概念。当我们使用UIImage类去读取我们的雪人图片的时候,他读取了高质量的Retina版本的图片。但是当我们用CGImage来设置我们的图层的内容时,拉伸这个因素在转换的时候就丢失了。不过我们可以通过手动设置contentsScale来修复这个问题(如2.2清单),图2.4是结果

@implementation ViewController- (void)viewDidLoad{  [super viewDidLoad]; //load an image  UIImage *image = [UIImage imageNamed:@"Snowman.png"]; //add it directly to our view's layer  self.layerView.layer.contents = (__bridge id)image.CGImage; //center the image  self.layerView.layer.contentsGravity = kCAGravityCenter;  //set the contentsScale to match image  self.layerView.layer.contentsScale = image.scale;}@end

图2.5 使用masksToBounds来修建图层内容

contentsRect

CALayer的contentsRect属性允许我们在图层边框里显示寄宿图的一个子域。这涉及到图片是如何显示和拉伸的,所以要比contentsGravity灵活多了和boundsframe不同,contentsRect不是按点来计算的,它使用了单位坐标,单位坐标指定在0到1之间,是一个相对值(像素和点就是绝对值)。所以他们是相对与寄宿图的尺寸的。iOS使用了以下的坐标系统:

  • 点 —— 在iOS和Mac OS中最常见的坐标体系。点就像是虚拟的像素,也被称作逻辑像素。在标准设施上,一个点就是一个像素,但是在Retina设施上,一个点等于2*2个像素。iOS用点作为屏幕的坐标测算体系就是为了在Retina设施和普通设施上能有一致的视觉效果。
  • 像素 —— 物理像素坐标并不会用来屏幕布局,但是依然与图片有相对关系。UIImage是一个屏幕分辨率处理方案,所以指定点来度量大小。但是少量底层的图片表示如CGImage就会使用像素,所以你要清楚在Retina设施和普通设施上,他们体现出来了不同的大小。
  • 单位 —— 对于与图片大小或者是图层边界相关的显示,单位坐标是一个方便的度量方式, 当大小改变的时候,也不需要再次调整。单位坐标在OpenGL这种纹理坐标系统中用得很多,Core Animation中也用到了单位坐标。

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

2.2 Custom Drawing

Custom Drawing

contents赋CGImage的值不是唯一的设置寄宿图的方法。我们也可以直接用Core Graphics直接绘制寄宿图。能够通过继承UIView并实现-drawRect:方法来自己设置绘制。

-drawRect: 方法没有默认的实现,由于对UIView来说,寄宿图并不是必需的,它不在意那究竟是单调的颜色还是有一个图片的实例。假如UIView检测到-drawRect:方法被调用了,它就会为视图分配一个寄宿图,这个寄宿图的像素尺寸等于视图大小乘以 contentsScale的值。

假如你不需要寄宿图,那就不要创立这个方法了,这会造成CPU资源和内存的白费,这也是为什么苹果建议:假如没有自己设置绘制的任务就不要在子类中写一个空的-drawRect:方法。

当视图在屏幕上出现的时候 -drawRect:方法就会被自动调用。-drawRect:方法里面的代码利用Core Graphics去绘制一个寄宿图,而后内容就会被缓存起来直到它需要被升级(通常是由于开发者调用了-setNeedsDisplay方法,虽然影响到体现效果的属性值被更改时,少量视图类型会被自动重绘,如bounds属性)。尽管-drawRect:方法是一个UIView方法,事实上都是底层的CALayer安排了重绘工作和保存了因而产生的图片。

CALayer有一个可选的delegate属性,实现了CALayerDelegate协议,当CALayer需要一个内容特定的信息时,就会从协议中请求。CALayerDelegate是一个非正式协议,其实就是说没有CALayerDelegate @protocol可以让你在类里面引用啦。你只要要调用你想调用的方法,CALayer会帮你做剩下的。(delegate属性被公告为id类型,所有的代理商方法都是可选的)。

当需要被重绘时,CALayer会请求它的代理商给他一个寄宿图来显示。它通过调用下面这个方法做到的:

(void)displayLayer:(CALayerCALayer *)layer;

趁着这个机会,假如代理商想直接设置contents属性的话,它即可以这么做,不然没有别的方法可以调用了。假如代理商不实现-displayLayer:方法,CALayer就会转而尝试调用下面这个方法:

 - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;

在调用这个方法之前,CALayer创立了一个合适尺寸的空寄宿图(尺寸由boundscontentsScale决定)和一个Core Graphics的绘制上下文环境,为绘制寄宿图做准备,他作为ctx参数传入。

让我们来继续第一章的项目让它实现CALayerDelegate并做少量绘图工作吧(见清单2.5).图2.12是他的结果

清单2.5 实现CALayerDelegate

@implementation ViewController- (void)viewDidLoad{  [super viewDidLoad];    //create sublayer  CALayer *blueLayer = [CALayer layer];  blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);  blueLayer.backgroundColor = [UIColor blueColor].CGColor;  //set controller as layer delegate  blueLayer.delegate = self;  //ensure that layer backing image uses correct scale  blueLayer.contentsScale = [UIScreen mainScreen].scale; //add layer to our view  [self.layerView.layer addSublayer:blueLayer];  //force layer to redraw  [blueLayer display];}- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{  //draw a thick red circle  CGContextSetLineWidth(ctx, 10.0f);  CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);  CGContextStrokeEllipseInRect(ctx, layer.bounds);}@end

2.3 总结

总结

本章详情了寄宿图和少量相关的属性。你学到了如何显示和放置图片, 使用拼合技术来显示, 以及用CALayerDelegate和Core Graphics来绘制图层内容。

另外,假如你想一起进阶,不妨增加一下交流群1012951431,选择加入一起交流,一起学习。期待你的加入!

在第三章,”图层几何学”中,我们将会讨论一下图层的几何,观察他们是如何放置和改变相互的尺寸的

转载地址:https://www.w3cschool.cn/ayuxgu/

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » iOS核心动画高级技巧-1

发表回复