导语
说一下为什么写这一系列的文章,笔者之前看到动画就觉得头大,被CALayer、Transform等名词弄的晕头转向,也查了许多资料,但大部分的资料都是以一个具体的例子来说明动画的使用,涉及到很多具体的东西,而对很多基本的概念,动画宏观方面的阐述的比较少,所以想写下这一系列的文章,让初学动画的人有个快速的宏观认识。
1、iOS的绘图系统都有哪些呢?
iOS主要的绘图系统有UIKit、Core Graphics(也称Quartz 2D)、Core Animation、Core Image、OpenGL ES。每一个都主要起什么作用呢?大概介绍一下。
- UIKit 最高级的页面,也是大家页面布局经常用到的,比如UIView、UIButton等。可以通过UI前缀来识别UIKit元素。
- Core Graphics UIKit下的主要绘图系统,用于绘制自定义视图。可以通过CG前缀来识别Core Graphics元素。
- Core Animation 提供了强大的2D和3D动画。
- Core Image 对图片进行各种滤镜处理,比如高斯模糊、锐化等。
- OpenGL ES 主要用于游戏绘图。
2、视图绘制与视图布局的区别有哪些呢?
我们先来理解一下视图绘制周期的概念,这有助于理解视图绘制与视图布局的区别。
- iOS在运行循环(run loop)中整合所有的绘图请求,并一次将它们绘制出来。
- 不能在主线程中进行复杂的操作,否则会造成主线程的卡顿。
- 不能在主线程之外的主视图上下文中绘制,只要不是在主视图上下文中绘制,一些UIKit方法是可以在后台线程中使用的。比如可以在任意线程上使用CGBitmapCreateContext创建CGBitmapContext对象并在里面绘图。
视图绘制,是调用UIView中的drawRect方法。如果一个视图调用setNeedsDisplay方法,它就被标记为重新绘制,并且会在下一次绘图周期中重新绘制,也就是会自动调用drawRect方法。
视图布局,是调用UIView中的layoutSubviews方法。如果视图中的子视图布局发生变化,需要重新排列,UIKit会自动调用setNeedsLayout方法,也就是对于发生变化的视图逐层次调用layoutSubviews方法。比如frame发生变化、滚动视图等。
在画图的时候,我们应该尽量避免绘制,多使用布局,这是为什么呢?因为布局使用的是GPU(GPU基于硬件进行布局的),而绘制使用的是CPU(基于软件进行绘制的)。
补充:CPU VS GPU
- 关于绘图和动画有两种处理的方式:CPU(中央处理器)和GPU(图形处理器)。在现代iOS设备中,都有可以运行不同软件的可编程芯片,但是由于历史的原因,我们可以说CPU所做的工作都在软件层面,而GPU在硬件层面。
- 总的来说,我们可以用软件(使用CPU)做任何事情,但是对于图像处理,通常用硬件会更快,因为GPU使用图像对高度并行浮点运算做了优化。由于某些原因,我们想尽可能把屏幕渲染的工作交给硬件去处理。问题在于GPU并没有无限制处理性能,而且一旦资源用完的话,性能就会开始下降了(即使CPU并没有完全占用)
- 大多数动画性能优化都是关于智能利用GPU和CPU,使得它们都不会超出负荷。
什么时候会调用绘制,什么时候调用布局呢?我自己的理解是这样的,视图在第一次创建的时候,绘制和布局的都会调用的。如果子视图因为一些条件的改变,造成布局的改变,这个时候,系统会自动调用layoutSubviews方法。尽量避免调用setNeedsDisplay方法。
3、理解绘图系统中的坐标系
绘图系统中主要使用两种坐标系。
基于点的坐标
原点位于图层的左上角,向右为x轴的正方向,向下为y轴的正方向,一个点的x、y坐标以点为单位。
单位坐标系
原点位于图层的左上角,向右为x轴的正方向,向下为y轴的正方向,一个点的x、y坐标以相对x轴、y轴的比例为值,取值范围为[0, 1]。锚点(anchorPoint)使用单位坐标系。
锚点决定了动画在变化时,z轴的位置。关于锚点的具体作用,坐标变换的具体内容,可以参考下面的两篇文章:
4、管理图形上下文
图形上下文是什么意思呢?比如,我们要画一幅画,需要有一张画布的,然后在画布上绘制出一幅画来,图形上下文就和画布的概念一样的。在图形上下文中包含了大量信息,比如设置画笔的颜色、设置文本的字体、设置变形等等。
我们在使用drawRect方法进行绘制的时候,我们并没有创建CGContext,但是我们却可以画出来自己想要的东西,这是因为,在调用drawRect方法之前,系统为我们默认创建了一个图形上下文(CGContext)。
我们在绘图中经常会碰见这几个名词,CGContextSaveGState和CGContextRestoreGState,UIGraphicsPushContext和UIGraphicsPopContext,我们来分别介绍一下。
[[UIColor redColor] setFill];
CGContextSaveGState(UIGraphicsGetCurrentContext());
[[UIColor blackColor] setFill];
CGContextRestoreGState(UIGraphicsGetCurrentContext());
UIRectFill(CGRectMake(10, 10, 100, 100));
这段代码设置了画笔的颜色为红色,并保存图形上下文,之后将画笔的颜色改成黑色,并恢复图形上下文。那么最后的画笔颜色是红色还是黑色的呢?答案是红色的,通过这段代码能看出来CGContextSaveGState和CGContextRestoreGState的用法。
[[UIColor redColor] setFill];
UIGraphicsPushContext(UIGraphicsGetCurrentContext());
[[UIColor blackColor] setFill];
UIGraphicsPopContext();
UIRectFill(CGRectMake(10, 10, 100, 100));
看下此处的代码,通过运行,我们发现最后画笔的颜色为黑色,这说明UIGraphicsPushContext并没有保存图形上下文的信息,那么它的作用是什么呢?假如你正在图形上下文中绘制什么东西,如果这时想要在位图上下文中绘制完全不同的内容,这就是UIGraphicsPushContext的作用了,切换到一个新的位图上。当你在新的位图上,绘制完想要的东西后,在通过UIGraphicsPopContext将刚才的图形上下文出栈,也就是恢复到刚才的绘图状态。
切换到另外一个上下文,这是一个比较常见的操作,因为常用性,系统api提供了UIGraphicsBeginImageContext的快捷方式,它负责将旧的上下文入栈、为新上下文分配内存、创建新的上下文、翻转坐标系统,并使其作为当前上下文使用。
5、 透明、不透明、隐藏
视图上有三个比较容易混淆的属性:alpha(透明)、opaque(不透明)和hidden(隐藏)。下面就进行一下深入的区分。
alpha属性决定了视图会通过像素显示多少信息。alpha为1表示所有的视图信息都在像素上表现出来,而alpha为0表示没有视图信息能在像素上表示出来。
opaque,将视图标记为opaque,便是向绘图系统许诺即将绘制的每一个像素都要使用全不透明的颜色,这便允许绘图系统忽略被覆盖在下面的视图,这样可以改善性能,尤其是在进行变形的时候。与opaque紧密相关的是clearsContextBeforeDrawing。它的默认值是YES,会在调用drawRect之前将上下问设置为透明黑底。这样会避免视图中产生的任何垃圾数据。
hidden为YES的话,表示视图根本不会被绘制。
隐藏和透明视图不接受触摸事件,如果想创建一个透明视图并且接受触摸事件,可以这样来进行设置,设置它的alpha为1、opaque为NO且backgroundColor为你活[UIColor clearColor],来接手触摸事件。
6、 绘图工具神器
下面介绍一款绘图神器(PaintCode)
安装完成后,打开工具,界面如下:
在顶部的菜单栏,提供了一些常见的形状,比如矩形、五角星、多边形。我们以五角星为例,简单介绍一下其用法。拖动五角星到Canvas上,效果如下:
图1-2所示 绘制五角星我们还为其设置了填充颜色为红色的。接下来就是要把做好的图转换成代码导出来,可以在视图下方选择要导出的代码类型,效果如下:
图1-3所示 设置代码导出类型我们可以看下导出类的源码是怎么样的,如下图:
图1-4所示 .m文件该怎么在自己的工程中使用呢?我们只需要在自己的工程中创建一个UIView,在UIView的drawRect方法,调用其提供的方法即可。如下所示:
图1-5所示 使用范例至此本文结束了,下一篇将通过两个示例来展示怎么具体使用UIKit与CoreGraphics进行绘制。
参考文章链接:
https://tech.imdada.cn/2016/06/21/ios-core-animation/
http://www.jianshu.com/p/f4096e8dd52e
国士梅花
欢迎大家关注国士梅花,技术路上与你陪伴。
guoshimeihua.jpg
网友评论
就drawRect方法而言 drawRect内的图形绘制是使用的CPU 但是对于其他形式的绘制可能就不是这样了,使用core animation 对多张图片进行转场过渡显示处理。此处的图片过渡就是GPU绘制的。