美文网首页
iOS 图形绘制方案

iOS 图形绘制方案

作者: yitez | 来源:发表于2020-08-19 15:37 被阅读0次

    先说下背景,项目里需要绘制音乐和视频的波形图,由于产品上的设计,波形图的长度基本都可以达到屏幕长度的几十倍。并且图形并不是折线图而是柱状图,还要跟随音乐音量变化,所以图形肯定是无法直接拉伸挤压的,所以当时为了性能和内存方面的考虑,尝试了很多方案。

    UIImage:
    使用UIGraphicsGetImageFromCurrentImageContext方法将绘制的图形生成图片。
    这种方式适用于图片不长并且图片不变的情况。

    优点:可在子线程绘制,方便缓存。
    缺点:占用内存大,绘制不够高效。
    PS:注意此方法有个隐患,因为系统会对设置给UIImageView的图片进行缓存,如果一直调用,即使是完全相同的图片,也会产生内存占用。
    示例:

    - (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的基类,重写drawInContext方法进行进行绘制。
    优点:量级轻(实在想不到优点)。
    缺点:主线程绘制,绘制不够高效 。
    示例:

    //子类重写drawInContext方法
    - (void)drawInContext:(CGContextRef)ctx {
        UIGraphicsPushContext(ctx);
        [self drawSomthing];
        UIGraphicsPopContext();
    }
    

    CATiledLayer:
    layer的子类,专门用于绘制大图的方案,系统底层已进行过优化,子线程绘制,并且不会绘制屏幕外的内容。可将大图分割成若干个更小的单元进行绘制,可通过tileSize设置单个绘制单元的大小。
    优点:子线程绘制,性能极好。
    缺点:绘制较缓慢,能控制的变量较少。
    示例:

    与CALayer用法一致,可设置额外属性

        CGSize tileSize = CGSizeMake(w, h);
        layer.tileSize = tileSize;   //设置单次绘制的单元
    

    YYAsyncLayer:
    知名的异步绘制的第三方控件,是CALayer的子类,内部创建了队列进行管理,能将绘制的操作转换为异步操作,并且引入了RunLoop机制进行管理,只在RunLoop空闲的时候才进行刷新操作。
    优点:子线程绘制,性能很高,不阻塞用户操作。
    缺点:因为只在空闲时执行,图形刷新不及时。
    示例:

    + (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;
              //绘制的代码写在这里
        };
        
        task.didDisplay = ^(CALayer *layer, BOOL finished) {
            if (finished) {
                // finished
            } else {
                // cancelled
            }
        };
        
        return task;
    }
    
    

    CAShapeLayer + UIBezierPath:
    layer的子类,CAshapeLayer能在GPU上渲染,性能很高,绘制速度很快,而且曲线绘制既能选择在主线程绘制,也能选择在子线程绘制。
    优点:无论子线程还是主线程都可绘制,且性能很高。
    缺点:曲线路径量大的话,还是影响性能。
    示例:

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

    最终还是选择了CAShapeLayer + UIBezierPath方案,但是因为音波数据量特别大(每秒40个音频数据),导致多个图形频繁刷新的时候发热十分严重,而且也出现了卡顿的情况。
    为了优化,最后又加入了分屏绘制的逻辑:


    QQ20200819-152747@2x.png

    蓝色区域表示屏幕区域,红色表示绘制的区域,黑色线条表示临界边,
    整体逻辑:
    0、转换坐标为窗体坐标。
    1、判断是否有上次绘制的位置,没有则直接绘制。
    2、绘制完成后保存当前位置为绘制位置,计算出黑色临临界区域。
    3、滑动视图的过程中判断滑动位置是否超出了黑线区域,超出则重新进行绘制。
    4、重复2、3。

    相关文章

      网友评论

          本文标题:iOS 图形绘制方案

          本文链接:https://www.haomeiwen.com/subject/dwkrjktx.html