iOS 圖形繪制方案

先說(shuō)下背景,項(xiàng)目里需要繪制音樂(lè)和視頻的波形圖,由于產(chǎn)品上的設(shè)計(jì),波形圖的長(zhǎng)度基本都可以達(dá)到屏幕長(zhǎng)度的幾十倍。并且圖形并不是折線圖而是柱狀圖,還要跟隨音樂(lè)音量變化,所以圖形肯定是無(wú)法直接拉伸擠壓的,所以當(dāng)時(shí)為了性能和內(nèi)存方面的考慮,嘗試了很多方案。

UIImage:
使用UIGraphicsGetImageFromCurrentImageContext方法將繪制的圖形生成圖片。
這種方式適用于圖片不長(zhǎng)并且圖片不變的情況。

優(yōu)點(diǎn):可在子線程繪制,方便緩存。
缺點(diǎn):占用內(nèi)存大,繪制不夠高效。
PS:注意此方法有個(gè)隱患,因?yàn)橄到y(tǒng)會(huì)對(duì)設(shè)置給UIImageView的圖片進(jìn)行緩存,如果一直調(diào)用,即使是完全相同的圖片,也會(huì)產(chǎn)生內(nèi)存占用。
示例:

- (UIImage*) drawImageFromCreaterWithMinValue:(int)minValue
                                     MaxValue:(int)maxValue {
    CGSize imageSize = CGSizeMake(imageWidth, imageHeigh);
    
    UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    //假裝有繪制代碼
    …………………………
   
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return newImage;

CALayer :
layer的基類(lèi),重寫(xiě)drawInContext方法進(jìn)行進(jìn)行繪制。
優(yōu)點(diǎn):量級(jí)輕(實(shí)在想不到優(yōu)點(diǎn))。
缺點(diǎn):主線程繪制,繪制不夠高效 。
示例:

//子類(lèi)重寫(xiě)drawInContext方法
- (void)drawInContext:(CGContextRef)ctx {
    UIGraphicsPushContext(ctx);
    [self drawSomthing];
    UIGraphicsPopContext();
}

CATiledLayer:
layer的子類(lèi),專(zhuān)門(mén)用于繪制大圖的方案,系統(tǒng)底層已進(jìn)行過(guò)優(yōu)化,子線程繪制,并且不會(huì)繪制屏幕外的內(nèi)容??蓪⒋髨D分割成若干個(gè)更小的單元進(jìn)行繪制,可通過(guò)tileSize設(shè)置單個(gè)繪制單元的大小。
優(yōu)點(diǎn):子線程繪制,性能極好。
缺點(diǎn):繪制較緩慢,能控制的變量較少。
示例:

與CALayer用法一致,可設(shè)置額外屬性

    CGSize tileSize = CGSizeMake(w, h);
    layer.tileSize = tileSize;   //設(shè)置單次繪制的單元

YYAsyncLayer:
知名的異步繪制的第三方控件,是CALayer的子類(lèi),內(nèi)部創(chuàng)建了隊(duì)列進(jìn)行管理,能將繪制的操作轉(zhuǎn)換為異步操作,并且引入了RunLoop機(jī)制進(jìn)行管理,只在RunLoop空閑的時(shí)候才進(jìn)行刷新操作。
優(yōu)點(diǎn):子線程繪制,性能很高,不阻塞用戶操作。
缺點(diǎn):因?yàn)橹辉诳臻e時(shí)執(zhí)行,圖形刷新不及時(shí)。
示例:

+ (Class)layerClass {
    return YYAsyncLayer.class;
}

- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {
    
    YYAsyncLayerDisplayTask *task = [YYAsyncLayerDisplayTask new];
    task.willDisplay = ^(CALayer *layer) {
        //...
    };
    
    
    __weak typeof(self) weakSelf = self;
    task.display = ^(CGContextRef context, CGSize size, BOOL(^isCancelled)(void)) {
        if (isCancelled()) return;
          //繪制的代碼寫(xiě)在這里
    };
    
    task.didDisplay = ^(CALayer *layer, BOOL finished) {
        if (finished) {
            // finished
        } else {
            // cancelled
        }
    };
    
    return task;
}

CAShapeLayer + UIBezierPath:
layer的子類(lèi),CAshapeLayer能在GPU上渲染,性能很高,繪制速度很快,而且曲線繪制既能選擇在主線程繪制,也能選擇在子線程繪制。
優(yōu)點(diǎn):無(wú)論子線程還是主線程都可繪制,且性能很高。
缺點(diǎn):曲線路徑量大的話,還是影響性能。
示例:

    UIBezierPath*  wavePath = [self drawLayerPath];
    CAShapeLayer *shaperlayer = [[CAShapeLayer alloc]init];
    shaperlayer.path = wavePath.CGPath;

最終還是選擇了CAShapeLayer + UIBezierPath方案,但是因?yàn)橐舨〝?shù)據(jù)量特別大(每秒40個(gè)音頻數(shù)據(jù)),導(dǎo)致多個(gè)圖形頻繁刷新的時(shí)候發(fā)熱十分嚴(yán)重,而且也出現(xiàn)了卡頓的情況。
為了優(yōu)化,最后又加入了分屏繪制的邏輯:


QQ20200819-152747@2x.png

藍(lán)色區(qū)域表示屏幕區(qū)域,紅色表示繪制的區(qū)域,黑色線條表示臨界邊,
整體邏輯:
0、轉(zhuǎn)換坐標(biāo)為窗體坐標(biāo)。
1、判斷是否有上次繪制的位置,沒(méi)有則直接繪制。
2、繪制完成后保存當(dāng)前位置為繪制位置,計(jì)算出黑色臨臨界區(qū)域。
3、滑動(dòng)視圖的過(guò)程中判斷滑動(dòng)位置是否超出了黑線區(qū)域,超出則重新進(jìn)行繪制。
4、重復(fù)2、3。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容