????有時候用CAShapeLayer
或者其他矢量圖形圖層替代Core Graphics并不是那么切實可行。比如我們的繪圖應用:我們用線條完美地完成了矢量繪制。但是設想一下如果我們能進一步提高應用的性能,讓它就像一個黑板一樣工作,然后用『粉筆』來繪制線條。模擬粉筆最簡單的方法就是用一個『線刷』圖片然后將它粘貼到用戶手指碰觸的地方,但是這個方法用CAShapeLayer
沒辦法實現(xiàn)。
????我們可以給每個『線刷』創(chuàng)建一個獨立的圖層,但是實現(xiàn)起來有很大的問題。屏幕上允許同時出現(xiàn)圖層上線數(shù)量大約是幾百,那樣我們很快就會超出的。這種情況下我們沒什么辦法,就用Core Graphics吧(除非你想用OpenGL做一些更復雜的事情)。
????我們的『黑板』應用的最初實現(xiàn)見清單13.3,我們更改了之前版本的DrawingView
,用一個畫刷位置的數(shù)組代替UIBezierPath
。圖13.2是運行結果
清單13.3 簡單的類似黑板的應用
#import "DrawingView.h"
#import
#define BRUSH_SIZE 32
@interface DrawingView ()
@property (nonatomic, strong) NSMutableArray *strokes;
@end
@implementation DrawingView
- (void)awakeFromNib
{
//create array
self.strokes = [NSMutableArray array];
}
- (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:@"Chalk.png"] drawInRect:brushRect];
}
}
@end
圖13.3 幀率和線條質量會隨時間下降。
????為了減少不必要的繪制,Mac OS和iOS設備將會把屏幕區(qū)分為需要重繪的區(qū)域和不需要重繪的區(qū)域。那些需要重繪的部分被稱作『臟區(qū)域』。在實際應用中,鑒于非矩形區(qū)域邊界裁剪和混合的復雜性,通常會區(qū)分出包含指定視圖的矩形位置,而這個位置就是『臟矩形』。
????當一個視圖被改動過了,TA可能需要重繪。但是很多情況下,只是這個視圖的一部分被改變了,所以重繪整個寄宿圖就太浪費了。但是Core Animation通常并不了解你的自定義繪圖代碼,它也不能自己計算出臟區(qū)域的位置。然而,你的確可以提供這些信息。
????當你檢測到指定視圖或圖層的指定部分需要被重繪,你直接調(diào)用-setNeedsDisplayInRect:
來標記它,然后將影響到的矩形作為參數(shù)傳入。這樣就會在一次視圖刷新時調(diào)用視圖的-drawRect:
(或圖層代理的-drawLayer:inContext:
方法)。
????傳入-drawLayer:inContext:
的CGContext
參數(shù)會自動被裁切以適應對應的矩形。為了確定矩形的尺寸大小,你可以用CGContextGetClipBoundingBox()
方法來從上下文獲得大小。調(diào)用-drawRect()
會更簡單,因為CGRect
會作為參數(shù)直接傳入。
????你應該將你的繪制工作限制在這個矩形中。任何在此區(qū)域之外的繪制都將被自動無視,但是這樣CPU花在計算和拋棄上的時間就浪費了,實在是太不值得了。
????相比依賴于Core Graphics為你重繪,裁剪出自己的繪制區(qū)域可能會讓你避免不必要的操作。那就是說,如果你的裁剪邏輯相當復雜,那還是讓Core Graphics來代勞吧,記住:當你能高效完成的時候才這樣做。
????清單13.4 展示了一個-addBrushStrokeAtPoint:
方法的升級版,它只重繪當前線刷的附近區(qū)域。另外也會刷新之前線刷的附近區(qū)域,我們也可以用CGRectIntersectsRect()
來避免重繪任何舊的線刷以不至于覆蓋已更新過的區(qū)域。這樣做會顯著地提高繪制效率(見圖13.4)
????清單13.4 用-setNeedsDisplayInRect:
來減少不必要的繪制
- (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:@"Chalk.png"] drawInRect:brushRect];
}
}
}
更多建議: