当前位置: 移动技术网 > 移动技术>移动开发>IOS > iOS核心动画高级技巧-1

iOS核心动画高级技巧-1

2019年11月21日  | 移动技术网移动技术  | 我要评论

 

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的细节了呢?

某种意义上说的确是这样,对一些简单的需求来说,我们确实没必要处理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显示了结果。

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

清单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来绘制图层内容。

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网