欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

YYText源码解读-YYText同步/异步渲染流程(二)

程序员文章站 2023-12-23 12:55:10
...

文章转载自40K CLUB APP

一、涉及到的几个类

  • YYLabel:Label控件,继承UIView
  • YYTextLayout、YYTextContainer、YYTextLine:用于布局计算,它是YYLabel的属性,当我们为YYLabel设置属性时(比如text、textColor等),它会计算出布局信息和完成内容的绘制。
  • YYTextAsyncLayer:自定义的Layer组件

二、流程

1 将YYLabel的默认Layer替换为YYTextAsyncLayer。

从上一篇文章中得知,我们只需重写+layerClass方法即可。

+ (Class)layerClass {
    return [YYTextAsyncLayer class];
}

2 重写YYTextAsyncLayer的display方法

- (void)display {
    super.contents = super.contents;
    // _displaysAsynchronously属性表示是否需要异步绘制,默认是NO
    [self _displayAsync:_displaysAsynchronously];
}

从上一篇文章中得知,display方法用来设置contents属性,而我们可以通过为contents赋一个CGImage的值,将YYLabel的结果,显示出来。这就需要我们通过YYLabel的所有属性(比如text、textColor等)来绘制一个CGImage出来,绘制的动作就是通过下面的方法完成的,绘制可以同步进行(默认),也可以异步进行,提高性能。

- (void)_displayAsync:(BOOL)async {
    // 暂时省略内容,后续后逐行分析
}

要想生存内容图片,我们需要知道布局信息。

布局信息的计算是通过YYLabel的YYTextLayout属性完成的。我们知道Layer的delegate是它的View(即YYTextAsyncLayer的delegate是YYLabel),我们为YYLabel实现一个newAsyncDisplayTask方法,YYTextAsyncLayer通过delegate调用该方法会返回一个YYTextAsyncLayerDisplayTask对象。通过YYTextAsyncLayerDisplayTask对象的block属性,YYTextAsyncLayer将上写文(context)回调给YYLabel的YYTextLayout属性,这时YYTextLayout的布局信息已经计算好,又获得了上下文信息(context),就可以完成绘制了。

3 YYTextLayout的布局计算方法和绘制方法

###(1)布局计算方法

+ (YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range;

###(2)绘制方法

- (void)drawInContext:(nullable CGContextRef)context
                 size:(CGSize)size
                point:(CGPoint)point
                 view:(nullable UIView *)view
                layer:(nullable CALayer *)layer
                debug:(nullable YYTextDebugOption *)debug
               cancel:(nullable BOOL (^)(void))cancel;

4 YYTextLayout绘制好图片,YYTextAsyncLayer通过上下文(context)就能拿到图片,并设置给contents属性,这样整个Label的显示就完成了。

最后看一下YYTextAsyncLayer的生成图片的方法

- (void)_displayAsync:(BOOL)async {
    __strong id<YYTextAsyncLayerDelegate> delegate = (id)self.delegate;
    YYTextAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
    if (!task.display) {
        if (task.willDisplay) task.willDisplay(self);
        self.contents = nil;
        if (task.didDisplay) task.didDisplay(self, YES);
        return;
    }
    
    if (async) {
        if (task.willDisplay) task.willDisplay(self);
        _YYTextSentinel *sentinel = _sentinel;
        int32_t value = sentinel.value;
        BOOL (^isCancelled)() = ^BOOL() {
            return value != sentinel.value;
        };
        CGSize size = self.bounds.size;
        BOOL opaque = self.opaque;
        CGFloat scale = self.contentsScale;
        CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL;
        if (size.width < 1 || size.height < 1) {
            CGImageRef image = (__bridge_retained CGImageRef)(self.contents);
            self.contents = nil;
            if (image) {
                dispatch_async(YYTextAsyncLayerGetReleaseQueue(), ^{
                    CFRelease(image);
                });
            }
            if (task.didDisplay) task.didDisplay(self, YES);
            CGColorRelease(backgroundColor);
            return;
        }
        
        dispatch_async(YYTextAsyncLayerGetDisplayQueue(), ^{
            if (isCancelled()) {
                CGColorRelease(backgroundColor);
                return;
            }
            UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
            CGContextRef context = UIGraphicsGetCurrentContext();
            if (opaque) {
                CGContextSaveGState(context); {
                    if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {
                        CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
                        CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
                        CGContextFillPath(context);
                    }
                    if (backgroundColor) {
                        CGContextSetFillColorWithColor(context, backgroundColor);
                        CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
                        CGContextFillPath(context);
                    }
                } CGContextRestoreGState(context);
                CGColorRelease(backgroundColor);
            }
            task.display(context, size, isCancelled);
            if (isCancelled()) {
                UIGraphicsEndImageContext();
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (task.didDisplay) task.didDisplay(self, NO);
                });
                return;
            }
            UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            if (isCancelled()) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (task.didDisplay) task.didDisplay(self, NO);
                });
                return;
            }
            dispatch_async(dispatch_get_main_queue(), ^{
                if (isCancelled()) {
                    if (task.didDisplay) task.didDisplay(self, NO);
                } else {
                    self.contents = (__bridge id)(image.CGImage);
                    if (task.didDisplay) task.didDisplay(self, YES);
                }
            });
        });
    } else {
        [_sentinel increase];
        if (task.willDisplay) task.willDisplay(self);
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, self.contentsScale);
        CGContextRef context = UIGraphicsGetCurrentContext();
        if (self.opaque) {
            CGSize size = self.bounds.size;
            size.width *= self.contentsScale;
            size.height *= self.contentsScale;
            CGContextSaveGState(context); {
                if (!self.backgroundColor || CGColorGetAlpha(self.backgroundColor) < 1) {
                    CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
                    CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));
                    CGContextFillPath(context);
                }
                if (self.backgroundColor) {
                    CGContextSetFillColorWithColor(context, self.backgroundColor);
                    CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));
                    CGContextFillPath(context);
                }
            } CGContextRestoreGState(context);
        }
        task.display(context, self.bounds.size, ^{return NO;});
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        self.contents = (__bridge id)(image.CGImage);
        if (task.didDisplay) task.didDisplay(self, YES);
    }
}

文章转载自40K CLUB APP

相关标签: 富文本

上一篇:

下一篇: