iOS Core Animation Advanced Techniques-高效绘图
上十二章节:
这篇随笔主要介绍有关高效绘图。
软件绘图:
- 由Core Graphics框架完成来完成,不由GPU协助的绘图
- 软件绘图不仅效率低,还会消耗可观的内存。
- 一旦实现了CALayerDelegate协议中的-drawLayer:inContext:方法或者UIView中的-drawRect:方法(其实就是前者的包装方法),
- 图层就创建了一个绘制上下文,这个上下文需要的大小的内存可从这个算式得出:
- 图层宽*图层高*4字节,宽高的单位均为像素,对于一个在Retina iPad上的全屏图层来说,这个内存量就是 2048*1526*4字节,相当于12MB内存
- 图层每次重绘的时候都需要重新抹掉内存然后重新分配。
- 因此:
- 除非绝对必要,应该避免重绘视图。
- 提高绘制性能的秘诀就在于尽量避免去绘制。
矢量图形:
- 用Core Graphics来绘图的一个通常原因就是只是用图片或是图层效果不能轻易地绘制出矢量图形。
- 矢量绘图包含以下这些:
- 1.任意多边形(不仅仅是一个矩形)
- 2.斜线或曲线
- 3.文本
- 4.渐变
- 示范例子://将用户的触摸手势转换成一个UIBezierPath上的点,然后绘制成视图。
- #import "DrawingView.h"
- @interface DrawingView ()
- @property (nonatomic, strong) UIBezierPath *path;
- @end
- @implementation DrawingView
- - (id)initWithFrame:(CGRect)frame
- {
- self=[super initWithFrame:frame];
- if (self) {
- //create a mutable path
- self.path = [[UIBezierPath alloc] init];
- self.path.lineJoinStyle = kCGLineJoinRound;
- self.path.lineCapStyle = kCGLineCapRound;
- self.path.lineWidth = 5;
- }
- return self;
- }
- - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- {
- //get the starting point
- CGPoint point = [[touches anyObject] locationInView:self];
- //move the path drawing cursor to the starting point
- [self.path moveToPoint:point];
- }
- - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- {
- //get the current point
- CGPoint point = [[touches anyObject] locationInView:self];
- //add a new line segment to our path
- [self.path addLineToPoint:point];
- //redraw the view
- [self setNeedsDisplay];
- }
- - (void)drawRect:(CGRect)rect
- {
- //draw path
- [[UIColor clearColor] setFill];
- [[UIColor redColor] setStroke];
- [self.path stroke];
- }
- @end
- /*
- 例子问题在于:
- 画得越多,程序就会越慢。
- 因为每次移动手指的时候都会重绘整个贝塞尔路径(UIBezierPath),随着路径越来越复杂,每次重绘的工作就会增加,直接导致了帧数的下降。
- 解决方案如下:
- */
- Core Animation为这些图形类型的绘制提供了专门的类(以前提到的专用图层),并给他们提供硬件支持:
- CAShapeLayer可以绘制多边形,直线和曲线。
- CATextLayer可以绘制文本。
- CAGradientLayer用来绘制渐变。
- 这些总体上都比Core Graphics更快,同时他们也避免了创造一个寄宿图。
- 示范例子://用CAShapeLayer替代Core Graphics
- #import "DrawingView.h"
- #import <QuartzCore/QuartzCore.h>
- @interface DrawingView ()
- @property (nonatomic, strong) UIBezierPath *path;
- @end
- @implementation DrawingView
- + (Class)layerClass
- {
- //this makes our view create a CAShapeLayer
- //instead of a CALayer for its backing layer
- return [CAShapeLayer class];
- }
- - (id)initWithFrame:(CGRect)frame{
- self=[super initWithFrame:frame];
- if (self) {
- //create a mutable path
- self.path = [[UIBezierPath alloc] init];
- //configure the layer
- CAShapeLayer *shapeLayer = (CAShapeLayer *)self.layer;
- shapeLayer.strokeColor = [UIColor redColor].CGColor;
- shapeLayer.fillColor = [UIColor clearColor].CGColor;
- shapeLayer.lineJoin = kCALineJoinRound;
- shapeLayer.lineCap = kCALineCapRound;
- shapeLayer.lineWidth = 1;
- }
- return self;
- }
- - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- {
- //get the starting point
- CGPoint point = [[touches anyObject] locationInView:self];
- //move the path drawing cursor to the starting point
- [self.path moveToPoint:point];
- }
- - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- {
- //get the current point
- CGPoint point = [[touches anyObject] locationInView:self];
- //add a new line segment to our path
- [self.path addLineToPoint:point];
- //update the layer with a copy of the path
- ((CAShapeLayer *)self.layer).path = self.path.CGPath;
- }
- @end
脏矩形:
- 如果想像一个黑板一样工作,然后用『粉笔』来绘制线条。模拟粉笔最简单的方法就是用一个『线刷』图片然后将它粘贴到用户手指碰触的地方,但是这个方法用CAShapeLayer没办法实现。
- 我们可以给每个『线刷』创建一个独立的图层,但是实现起来有很大的问题。屏幕上允许同时出现图层上线数量大约是几百,那样我们很快就会超出的。
- 这种情况下我们没什么办法,就用Core Graphics
- 示范例子:
-
- #import "DrawView.h"
- #define BRUSH_SIZE 32
- @interface DrawView()
- @property (nonatomic, strong) NSMutableArray *strokes;
- @end
- @implementation DrawView
- - (id)initWithFrame:(CGRect)frame{
- self=[super initWithFrame:frame];
- if (self) {
- self.strokes = [NSMutableArray array];
- }
- return self;
- }
- - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- {
- //get the starting point
- CGPoint point = [[touches anyObject] locationInView:self];
- //add brush stroke
- [self addBrushStrokeAtPoint:point];
- }
- - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- {
- //get the touch point
- CGPoint point = [[touches anyObject] locationInView:self];
- //add brush stroke
- [self addBrushStrokeAtPoint:point];
- }
- - (void)addBrushStrokeAtPoint:(CGPoint)point
- {
- //add brush stroke to array
- [self.strokes addObject:[NSValue valueWithCGPoint:point]];
- //needs redraw
- [self setNeedsDisplay];
- }
- - (void)drawRect:(CGRect)rect
- {
- //redraw strokes
- for (NSValue *value in self.strokes) {
- //get point
- CGPoint point = [value CGPointValue];
- //get brush rect
- CGRect brushRect = CGRectMake(point.x - BRUSH_SIZE/2, point.y - BRUSH_SIZE/2, BRUSH_SIZE, BRUSH_SIZE);
- //draw brush stroke
- [[UIImage imageNamed:@"praise.png"] drawInRect:brushRect];
- }
- @end
- /*
- 范例问题:
- 每次手指移动的时候我们就会重绘之前的线刷,即使场景的大部分并没有改变。我们绘制地越多,就会越慢。随着时间的增加每次重绘需要更多的时间,帧数也会下降
- 解决方案:
- 如下
- */
- 为了减少不必要的绘制,Mac OS和iOS设备将会把屏幕区分为需要重绘的区域和不需要重绘的区域。
- 那些需要重绘的部分被称作『脏区域』。
- 当检测到指定视图或图层的指定部分需要被重绘,
- 直接调用-setNeedsDisplayInRect:来标记它,然后将影响到的矩形作为参数传入。
- 这样就会在一次视图刷新时调用视图的-drawRect:(或图层代理的-drawLayer:inContext:方法)。
- 传入-drawLayer:inContext:的CGContext参数会自动被裁切以适应对应的矩形。
- 为了确定矩形的尺寸大小,你可以用CGContextGetClipBoundingBox()方法来从上下文获得大小。
- 调用-drawRect()会更简单,因为CGRect会作为参数直接传入。
- 示范例子://修改上面例子, 用-setNeedsDisplayInRect:来减少不必要的绘制
- #import "DrawView.h"
- #define BRUSH_SIZE 32
- @interface DrawView()
- @property (nonatomic, strong) NSMutableArray *strokes;
- @end
- @implementation DrawView
- - (id)initWithFrame:(CGRect)frame{
- self=[super initWithFrame:frame];
- if (self) {
- self.strokes = [NSMutableArray array];
- }
- return self;
- }
- - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- {
- //get the starting point
- CGPoint point = [[touches anyObject] locationInView:self];
- //add brush stroke
- [self addBrushStrokeAtPoint:point];
- }
- - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- {
- //get the touch point
- CGPoint point = [[touches anyObject] locationInView:self];
- //add brush stroke
- [self addBrushStrokeAtPoint:point];
- }
- - (void)addBrushStrokeAtPoint:(CGPoint)point
- {
- //add brush stroke to array
- [self.strokes addObject:[NSValue valueWithCGPoint:point]];
- //set dirty rect
- [self setNeedsDisplayInRect:[self brushRectForPoint:point]];
- }
- - (CGRect)brushRectForPoint:(CGPoint)point
- {
- return CGRectMake(point.x - BRUSH_SIZE/2, point.y - BRUSH_SIZE/2, BRUSH_SIZE, BRUSH_SIZE);
- }
- - (void)drawRect:(CGRect)rect
- {
- //redraw strokes
- for (NSValue *value in self.strokes) {
- //get point
- CGPoint point = [value CGPointValue];
- //get brush rect
- CGRect brushRect = [self brushRectForPoint:point];
- //only draw brush stroke if it intersects dirty rect
- if (CGRectIntersectsRect(rect, brushRect)) {
- //draw brush stroke
- [[UIImage imageNamed:@"praise.png"] drawInRect:brushRect];
- }
- }
- }
- @end
-