写在前面
关于在IOS端进行原生界面绘制,苹果开发文档里明确提供了几种方法:
- 使用系统提供的标准视图,例如lists, collections, alerts, images, progress bars, tables等。
- 使用Core Animation的图层,Core Animation不仅提供了动画的类,还提供了显示内容的图层类。
- 使用OpenGL ES,这个框架提供了一套开放标准的图形绘制库,主要面向游戏开发或者需要高帧速率的app。
- 使用UIWebView类展示基于web的图形界面。
很显然,如果你要开发一套K线框架:
- 第一种方法肯定不适合,因为你没办法去用标准的控件来显示K线;
- 第四种使用webview,这样的话就需要使用百度的echarts,或者还可以使用highcharts
- 所以,想要开发原生K线,就只能选择第二种和第三种方法了
- OpenGL ES框架使用起来比较麻烦,后续会单独在一篇文章中介绍如何使用
- 所以,在这里,就只介绍第二种方法,也就是使用Core Animation
- 还有一种办法,就是绕过Core Animation,直接使用Core Graphics进行绘制。这个在后面会说到。
它是什么
要使用它,就的先了解一下它是什么?
在苹果的开发文档中,有关于Core Animation的介绍,点击这儿。
这里放一张非常经典的图:
Core Animation是一个图形渲染和动画的基础库,是一个复合引擎,职责就是尽可能快地组合屏幕上不同的可视内容,这个内容是被分解成独立的图层,存储在一个叫图层树的体系之中。
Core Animation可以直接用在Max OS X和IOS平台上。
Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。
Core Animation是直接作用于CALayer,并非直接作用于UIView。
Core Animation有以下几个分类:
- 提供显示内容的图层类
- 动画和计时类
- 布局和约束类
- 事务类,在原子更新的时候组合图层类
CoreAnimation使用
在绘制k线时,主要是使用CALayer的子类CAShapeLayer,它是一个通过矢量图形而不是bitmap来绘制的图层子类。使用时,可以直接指定诸如颜色和线宽等属性,用CGPath来定义线稿绘制的图形,最后CAShapeLayer就自动渲染出来了。
首先先从最简单的开始,画一条线,代码如下:
//初始化一个线的图层
CAShapeLayer *lineLayer = [CAShapeLayer layer];
//初始化一个描述的路径
UIBezierPath *linePath = [UIBezierPath bezierPath];
//设置线段开始的点
[linePath moveToPoint:beginPoint];
//设置线段结束的点
//这里也可以添加多个点
[linePath addLineToPoint:endPoint];
//设置图层路径
lineLayer.path = linePath.CGPath;
//设置图层的其他属性
lineLayer.lineWidth = lineWidth;
lineLayer.strokeColor = lineColor.CGColor;
lineLayer.fillColor = [UIColor clearColor].CGColor;
执行结果如下:
从代码上可以看到,绘制线段的步骤很简单:
- 初始化一个图层
- 初始化用户线段的路径
- 添加线段开始的坐标点
- 再添加多个中间的坐标点
- 最后添加结束的坐标点
- 把路径设置到图层上去
- 设置图层的各个属性
其实现在再想一想K线中的分时线(如果不了解,可以点击这儿),可以直接用这段代码来绘制出来,当然,不包括分时线下方的背景颜色,并且得添加多个坐标点。
画完一条线后,再来画一个方块:
//初始化一个rect
CGRect frameRect = CGRectMake(x, y, width, height);
//初始化一个图层
CAShapeLayer *layer = [CAShapeLayer layer];
//初始化一个描述框的路径
UIBezierPath *path = [UIBezierPath bezierPathWithRect:frameRect];
//把路径设置到图层中
layer.path = path.CGPath;
//设置图层的各个属性
layer.strokeColor = strokeColor.CGColor;
layer.fillColor = backColor.CGColor;
执行结果如下:
绘制的步骤也很简单:
- 设置好框的frame
- 然后初始化路径的时候,直接把框的frame赋值进去
- 初始化一个图层
- 把路径设置到图层中
- 设置图层的各个属性
在这里,如果留心一下,其实就可以想到,框已经绘制好了,那一个蜡烛也就绘制好了,那绘制多个框,一整屏的蜡烛图不就绘制出来了。(没留心的,可以点这儿)如果你已经理解了上面所说的,那么我们在K线框架开发的道路上已经走出一大步。
Core Graphics的使用
上面有第一个地方说到,绘制界面除了使用Core Animation以外,还可以绕过Core Animation直接使用OpenGL ES或者Core Graphics。
在这里,介绍一下Core Graphics。Core Graphics是一套基于C的API框架,使用了Quartz作为绘图引擎。它提供了低级别、轻量级、高保真度的2D渲染。该框架可以用于基于路径的绘图、变换、颜色管理、脱屏渲染,模板、渐变、遮蔽、图像数据管理、图像的创建、遮罩以及PDF文档的创建、显示和分析。
和上面一样,也是从最基础的一条线开始:
//获取当前上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//开始记录路径
CGContextBeginPath(ctx);
//设置开始坐标点
CGContextMoveToPoint(ctx, beginPoint);
//添加坐标点,或者也可以只添加一个坐标点
CGContextAddLineToPoint(ctx, endPoint);
//结束记录路径
CGContextClosePath(ctx);
//设置线段宽度
CGContextSetLineWidth(ctx, lineWidth);
//设置颜色
CGContextSetStrokeColorWithColor(ctx, lineColor.CGColor);
//开始绘制路径
CGContextStrokePath(ctx);
执行效果和上面使用CAShapeLayer绘制线段的效果一样。
依次类推,可以绘制一条线,也就可以绘制一个框。
这里说明一下,代码中的CGContext 上下文定义了绘制的地方。在使用UIKit时,上下文是唯一的,UIKit会维护着一个上下文堆栈,而UIKit方法总是绘制到最顶层的上下文中。
一般使用Core Graphics进行绘制,都会重写drawRect方法,所以这里的上下文就是上下文堆栈最顶层的上下文,使用UIGraphicsGetCurrentContext就可以获取到。
两者比较
这里总结一下,上述介绍了两种绘制图形的方式:
- 使用Core Animation的 CAShapeLayer图层子类
- 使用Core Graphics
那两种方式有什么区别呢?
- CAShapeLayer渲染更快速。因为它使用了硬件加速,绘制同一图形会比用Core Graphics快很多。
- CAShapeLayer更高效使用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。
- CAShapeLayer不会被图层边界剪裁掉,一个 CAShapeLayer 可以在边界之外绘制。你的图层路径不会像在使用 Core Graphics 的普通 CALayer 一样被剪裁掉。
- CAShapeLayer不会出现像素化。当你给 CAShapeLayer 做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化。
其实最后一点最关键,因为它正好符合了k线框架开发的业务需求,一语中的。
一旦你实现了 CALayerDelegate 协议中的 -drawLayer:inContext: 方法或者 UIView 中的 -drawRect: 方法(其实就是前者的包装方法),图层就创建了一个绘制上下文,这个上下文需要的内存可从这个公式得出:图层宽x图层高x4字节,宽高的单位均为像素。对于一个在 Retina iPad 上的全屏图层来说,这个内存量就是 2048x1526x4字节,相当于12MB内存,图层每次重绘的时候都需要重新抹掉内存然后重新分配。
而当我们使用k线的时候,左滑或者右滑时,都会触发重新绘制,而每次重绘时都会重新获取一个绘制上下文。而左滑或者右滑时,会高频率的进行重绘,所以避免不了内存的重新分配。
当然,这里也不是说Core Graphics效率就很差,只是恰好在这样的业务需求下,会把某一个问题放大。并且,不要忘了CAShapeLayer绘制的图形是直接操作layer,不会作用于UIView,更不会去响应用户的交互。