WXLabel 文字的排版与设计

众多的APP中,大多数项目都需要针对不同的文字内容或者图片,进行不同的设计,像字体的大小,行距,对齐方式等。当然在社交类项目中,会使用到表情符号等图片,穿插与文字之中。像这类功能,通过CoreText内置框架大部分就能够解决,但是像文字超链接,文字的选取这类功能,CoreText就无法完成。这时候就需要在CoreText的基础上,进行进一步封装。

在这里,针对这一需求,在CoreText的基础上,集成RegexKitLite,封装了WXLabel这样一个能够对文字精加工的类。

首先,如下图所示,创建WXLabel类,并在其中导入CoreText/CoreText.h 框架、regexKitLite.h文件。


在初始化中,对WXLabel进行一些基础设置。因为在后续中,会设置超链接,对UILabel进行点击,所以需要设置该WXLabel为可点击状态。并且,设置它的默认行间距、超链接文字颜色和当手指经过超链接时,文字的颜色。

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        //开启当前点击的手势
        self.userInteractionEnabled = YES;
        //缓存略字典
        self.dicHeights = [NSMutableDictionary dictionary];
        //行间距
        self.linespace = 10;
        
        //当前文本超链接文字的颜色默认为purpleColor
        self.linkColor = [UIColor purpleColor];
        //当前文本超链接文字手指经过的颜色默认为greenColor
        self.passColor = [UIColor greenColor];
        
        _framesetter = nil;
    }
    return self;
}


我们定义了6个协议方法,来对通过正则表达式筛选出得文字,进行操作。 正则表达式详见:正则表达式

//手指离开当前超链接文本响应的协议方法
- (void)toucheEndWXLabel:(WXLabel *)wxLabel withContext:(NSString *)context;
//手指接触当前超链接文本响应的协议方法
- (void)toucheBenginWXLabel:(WXLabel *)wxLabel withContext:(NSString *)context;

<span style="color:#333399;">/*
    - (NSString *)contentsOfRegexStringWithWXLabel:(WXLabel *)wxLabel
    {
         //需要添加链接字符串的正则表达式:@用户、http://、#话题#
         NSString *regex1 = @"@\\w+";
         NSString *regex2 = @"http(s)?://([A-Za-z0-9._-]+(/)?)*";
         NSString *regex3 = @"#\\w+#";
         NSString *regex = [NSString stringWithFormat:@"(%@)|(%@)|(%@)",regex1,regex2,regex3];
         return regex;
    }
 */</span>
//检索文本的正则表达式的字符串
- (NSString *)contentsOfRegexStringWithWXLabel:(WXLabel *)wxLabel;
//设置当前链接文本的颜色
- (UIColor *)linkColorWithWXLabel:(WXLabel *)wxLabel;
//设置当前文本手指经过的颜色
- (UIColor *)passColorWithWXLabel:(WXLabel *)wxLabel;

/*
    注意:
        默认表达式@"<image url = '[a-zA-Z0-9_\\.@%&\\S]*'>"
        可以通过代理方法修改正则表达式,不过本地图片地址的左右(***一定要用单引号引起来)
 */
//检索文本中图片的正则表达式的字符串
- (NSString *)imagesOfRegexStringWithWXLabel:(WXLabel *)wxLabel;

在- (void)drawRect:(CGRect)rect方法中,调用_createAttributeText方法,对内容中的文字和图片进行属性设置。

- (void)drawRect:(CGRect)rect
{
    [self _createAttributeText];
    
    //然后创建一个CGPath对象,这个Path对象用于表示可绘制区域坐标值、长宽。
    CGRect bouds = CGRectInset(self.bounds, 0.0f, 0.0f);
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, bouds);
    
    //使用上面生成的setter和path生成一个CTFrameRef对象,这个对象包含了这两个对象的信息(字体信息、坐标信息)
    CTFrameRef frame = CTFramesetterCreateFrame(_framesetter, CFRangeMake(0, 0), path, NULL);
    
    //获取当前(View)上下文以便于之后的绘画,这个是一个离屏。
    CGContextRef context = UIGraphicsGetCurrentContext();
    if (context != NULL) {
        CGContextSetTextMatrix(context , CGAffineTransformIdentity);
        //压栈,压入图形状态栈中.每个图形上下文维护一个图形状态栈,并不是所有的当前绘画环境的图形状态的元素都被保存。图形状态中不考虑当前路径,所以不保存
        //保存现在得上下文图形状态。不管后续对context上绘制什么都不会影响真正得屏幕。
        CGContextSaveGState(context);
        //x,y轴方向移动
        CGContextTranslateCTM(context , 0 ,self.height );
        //缩放x,y轴方向缩放,-1.0为反向1.0倍,坐标系转换,沿x轴翻转180度
        CGContextScaleCTM(context, 1.0 ,-1.0);
        //可以使用CTFrameDraw方法绘制了。
        CTFrameDraw(frame,context);
    }
    
    //获取当前行的集合
    self.row = (NSArray *)CTFrameGetLines(frame);
    
    if (self.row.count > 0) {
        //获取最后一行的CGRect
        CGRect lineBounds = CTLineGetImageBounds((CTLineRef)[self.row lastObject], context);
        _lastLineWidth = lineBounds.size.width;        
    }
    
    //---------------------------绘制图片---------------------------
    CFArrayRef lines = CTFrameGetLines(frame);
    CGPoint lineOrigins[CFArrayGetCount(lines)];
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), lineOrigins);
    
    for (int i = 0; i < CFArrayGetCount(lines); i++) {
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        CGFloat lineAscent;
        CGFloat lineDescent;
        CGFloat lineLeading;
        CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
        //NSLog(@"ascent = %f,descent = %f,leading = %f",lineAscent,lineDescent,lineLeading);
        
        //CTLineGetGlyphRuns 返回line中得run得数量
        CFArrayRef runs = CTLineGetGlyphRuns(line);
        //NSLog(@"run count = %ld",CFArrayGetCount(runs));
        for (int j = 0; j < CFArrayGetCount(runs); j++) {
            CGFloat runAscent;
            CGFloat runDescent;
            CGPoint lineOrigin = lineOrigins[i];
            CTRunRef run = CFArrayGetValueAtIndex(runs, j);
            NSDictionary* attributes = (NSDictionary*)CTRunGetAttributes(run);
            CGRect runRect;
            runRect.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0,0), &runAscent, &runDescent, NULL);
            //NSLog(@"width = %f",runRect.size.width);
            
            runRect=CGRectMake(lineOrigin.x + CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL), lineOrigin.y - runDescent, runRect.size.width, runAscent + runDescent);
            
            NSString *imageName = [attributes objectForKey:WXLABEL_IMAGE_NAME];
            //图片渲染逻辑
            if (imageName) {
                UIImage *image = [UIImage imageNamed:imageName];
                if (image) {
                    CGRect imageDrawRect;
                    //#warning 设置图片的大小 (长宽都为字体大小的1.2倍)
                    imageDrawRect.size = CGSizeMake(self.font.pointSize * 1.2, self.font.pointSize * 1.2);
                    imageDrawRect.origin.x = runRect.origin.x + lineOrigin.x;
                    imageDrawRect.origin.y = lineOrigin.y - self.font.pointSize * .2;
                    
                    //绘制图片
                    CGContextDrawImage(context, imageDrawRect, image.CGImage);
                    //                    imageDrawRect.size = CGSizeMake(image.size.height, image.size.height);
                    //                    imageDrawRect.origin.x = runRect.origin.x + lineOrigin.x;
                    //                    imageDrawRect.origin.y = lineOrigin.y - 8;
                    //                    CGContextDrawImage(context, imageDrawRect, image.CGImage);
                    
                }
            }
        }
    }
    
    
    //---------------获取当前文本的高度------------------
    //获取当前的行高
    
//    float lineHeight = self.font.pointSize + self.linespace + 2;
//    self.textHeight = CFArrayGetCount(lines) * lineHeight ;
//    self.textHeight = suggestedSize.height;
    CGRect myframe = self.frame;
    myframe.size.height = self.textHeight;
    self.frame = myframe;
    
    //释放对象:(path是通过CGPathCreateMutable创建的,需要通过CGPathRelease释放)
    CGPathRelease(path);
//    CFRelease(framesetter);
    CFRelease(frame);
    
}

    _createAttributeText方法,对文字和图片进行设置。

- (void)_createAttributeText {
    
    //自定义当前超链接文本颜色
    if ([self.wxLabelDelegate respondsToSelector:@selector(linkColorWithWXLabel:)]) {
        self.linkColor = [self.wxLabelDelegate linkColorWithWXLabel:self];
    }
    
    //自定义当前鼠标经过超链接文本颜色
    if ([self.wxLabelDelegate respondsToSelector:@selector(passColorWithWXLabel:)]) {
        self.passColor = [self.wxLabelDelegate passColorWithWXLabel:self];
    }
    if (self.text == nil) {
        return;
    }
    
    /*
     使用CoreText设置文字属性
     */
    //生成属性字符串对象
    self.attrString = [[NSMutableAttributedString alloc]initWithString:self.text];

    //设置图片属性字符串
    [self replaceImageText];
    
    //------------------------设置字体属性--------------------------
    //    CTFontRef font = CTFontCreateWithName(CFSTR("Georgia"), 15, NULL);
    //设置当前字体
    [_attrString addAttribute:(id)kCTFontAttributeName value:self.font range:NSMakeRange(0, _attrString.length)];
    //设置当前文本的颜色
    [_attrString addAttribute:(id)kCTForegroundColorAttributeName value:self.textColor range:NSMakeRange(0, _attrString.length)];
    
    
    //----------------------设置链接文本的颜色-------------------
    //判断当前链接文本表达式是否实现
    if ([self.wxLabelDelegate respondsToSelector:@selector(contentsOfRegexStringWithWXLabel:)] && [self.wxLabelDelegate contentsOfRegexStringWithWXLabel:self] != nil)
    {
        
        //获取所有的链接文本
        NSArray *contents = [self contentsOfRegexStrArray];
        
        //获取所有文本的的索引集合
        NSArray *ranges = [self rangesOfContents:contents];
        //NSLog(@"ranges %@",ranges);
        for (NSValue *value in ranges) {
            NSRange range = [value rangeValue];
            //设置字体的颜色
            [_attrString addAttribute:(id)kCTForegroundColorAttributeName value:(id)self.linkColor range:range];
            
        }
        
        //设置选中经过字体颜色
        [_attrString addAttribute:(id)kCTForegroundColorAttributeName value:(id)self.passColor range:self.movieStringRange];
        
    }
    
    
    //------------------------设置段落属性(CoreText)-----------------------------
    //指定为对齐属性
    CTTextAlignment alignment = kCTJustifiedTextAlignment;
    CTParagraphStyleSetting alignmentStyle;
    alignmentStyle.spec=kCTParagraphStyleSpecifierFirstLineHeadIndent;//指定为对齐属性(首行缩进)
    alignmentStyle.valueSize=sizeof(alignment);
    alignmentStyle.value=&alignment;
    
    
    //行距
    CTParagraphStyleSetting lineSpaceSetting;
    lineSpaceSetting.spec = kCTParagraphStyleSpecifierLineSpacing;
    lineSpaceSetting.value = &_linespace;
    lineSpaceSetting.valueSize = sizeof(_linespace);
    
    //多行高
    self.mutiHeight = 1.0f;
    CTParagraphStyleSetting Muti;
    Muti.spec = kCTParagraphStyleSpecifierLineHeightMultiple;
    Muti.value = &_mutiHeight;
    Muti.valueSize = sizeof(float);
    
    //换行模式
    CTParagraphStyleSetting lineBreakMode;
    CTLineBreakMode lineBreak = kCTLineBreakByCharWrapping;
    lineBreakMode.spec = kCTParagraphStyleSpecifierLineBreakMode;
    lineBreakMode.value = &lineBreak;
    lineBreakMode.valueSize = sizeof(CTLineBreakMode);
    
    //组合设置
    CTParagraphStyleSetting settings[] = {
        lineSpaceSetting,Muti,alignmentStyle,lineBreakMode
    };
    
    //通过设置项产生段落样式对象
    CTParagraphStyleRef style = CTParagraphStyleCreate(settings, 4);
    
    // build attributes
    NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithObject:(__bridge id)style forKey:(id)kCTParagraphStyleAttributeName ];
    
    // set attributes to attributed string
    [_attrString addAttributes:attributes range:NSMakeRange(0, _attrString.length)];
    
    
    //生成CTFramesetterRef对象
    _framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)_attrString);
}

    在_createAttributeText中,通过imagesOfRegexStrArray方法来获取符合正则表达式规则的图片数组,通过replaceImageText来进行替换图片文本。两个方法实现如下:

//获取所有图片的字符串
- (NSArray *)imagesOfRegexStrArray
{
    //需要添加图片正则表达,默认为@"<image url = '[a-zA-Z0-9_\\.@%&\\S]*'>"
    NSString *regex = @"<image url = '[a-zA-Z0-9_\\.@%&\\S]*'>";
    if ([self.wxLabelDelegate respondsToSelector:@selector(imagesOfRegexStringWithWXLabel:)]) {
        regex = [self.wxLabelDelegate imagesOfRegexStringWithWXLabel:self];
    }
    
    //通过正则表达式查找出匹配的字符串
    NSArray *matchArray = [self.text componentsMatchedByRegex:regex];
    //<image url = 'wxhl.png'>
    return matchArray;
}

//替换图片文本
- (void)replaceImageText
{
    //为图片设置CTRunDelegate,delegate决定留给图片的空间大小
    CTRunDelegateCallbacks imageCallbacks;
    imageCallbacks.version = kCTRunDelegateVersion1;
    imageCallbacks.dealloc = RunDelegateDeallocCallback;
    imageCallbacks.getAscent = RunDelegateGetAscentCallback;
    imageCallbacks.getDescent = RunDelegateGetDescentCallback;
    imageCallbacks.getWidth = RunDelegateGetWidthCallback;
    
    //存放所有图片的索引位置
    NSMutableArray *ranges = [NSMutableArray array];
    // [self imagesOfRegexStrArray] 通过正则表达式,筛选出符合要求的文字,组成数组
    for (NSString *imageUrl in [self imagesOfRegexStrArray]) {
        //componentsSeparatedByString :字符串的分割
        NSArray *imageUrls = [imageUrl componentsSeparatedByString:@"'"];
        NSString *imgName = imageUrls[1];
        CTRunDelegateRef runDelegate = CTRunDelegateCreate(&imageCallbacks, (__bridge void *)(imgName));
        NSMutableAttributedString *imageAttributedString = [[NSMutableAttributedString alloc] initWithString:@"   "];//空格用于给图片留位置
        [imageAttributedString addAttribute:(NSString *)kCTRunDelegateAttributeName value:(__bridge id)runDelegate range:NSMakeRange(1, 1)];
        CFRelease(runDelegate);
        //设置空格的属性
        [imageAttributedString addAttribute:WXLABEL_IMAGE_NAME value:imgName range:NSMakeRange(1, 1)];
        
        //获取上一次图片检索的位置
        NSValue *lastValue = [ranges lastObject];
        int location = [lastValue rangeValue].location + ([lastValue rangeValue].length == 0 ? 0 : 1);
        //获取当前字符串在文本中的位置
        NSRange range = [[self.attrString string] rangeOfString:imageUrl  options:NSCaseInsensitiveSearch range:NSMakeRange(location, self.attrString.length - location)];
        //NSLog(@"lenght:%d",self.attrString.length);
        //把图片的字符串替换为(空格的属性字符串)
        [self.attrString replaceCharactersInRange:range withAttributedString:imageAttributedString];
        //NSLog(@"lenght:%d",self.attrString.length);
        NSValue *value = [NSValue valueWithRange:range];
        //添加到数组中
        [ranges addObject:value];
    }
}

通过contentsOfRegexStrArray方法,返回符合规定正则表达式的文字数组,并且通过 - (NSArray *)rangesOfContents:(NSArray *)contents 方法,来获取该文字索引位置:

#pragma mark - 检索当前链接文本
//返回所有的链接字符串数组
- (NSArray *)contentsOfRegexStrArray
{
    //需要添加链接字符串正则表达:@用户、http://、#话题#
    NSString *regex = [self.wxLabelDelegate contentsOfRegexStringWithWXLabel:self];
    
    //通过正则表达式查找出匹配的字符串
    NSArray *matchArray = [[self.attrString string] componentsMatchedByRegex:regex];
    //@用户 ---> <a href='user://用户'>@用户</a>
    //http:// ---> <a href='http://wwww.iphonetrain.com'>http://wwww.iphonetrain.com</a>
    //#话题# -----> <a href='topic://话题'>#话题#</a>
    return matchArray;
}

//获取所有链接文字的位置
- (NSArray *)rangesOfContents:(NSArray *)contents
{
    if (_ranges == nil) {
        _ranges = [[NSMutableArray alloc]init];
    }
    [_ranges removeAllObjects];
    
    for (NSString *content in contents) {
        NSValue *lastValue = [_ranges lastObject];
        int location = [lastValue rangeValue].location + [lastValue rangeValue].length;
        //获取当前字符串在文本中的位置
        NSRange range = [[self.attrString string] rangeOfString:content options:NSCaseInsensitiveSearch range:NSMakeRange(location, self.attrString.length - location)];
        NSValue *value = [NSValue valueWithRange:range];
        //添加到数组中
        [_ranges addObject:value];
    }
    
    return _ranges;
}

实现CTRunDelegate的delegate方法:

#pragma mark - CTRunDelegate delegate
void RunDelegateDeallocCallback(void *refCon) {
    
}
//设置空白区域的高度
CGFloat RunDelegateGetAscentCallback(void *refCon) {
    //NSString *imageName = (__bridge NSString *)refCon;
    return 0;//[UIImage imageNamed:imageName].size.height / 4;
}

CGFloat RunDelegateGetDescentCallback(void *refCon) {
    return 0;
}
//设置空白区域的宽度
CGFloat RunDelegateGetWidthCallback(void *refCon){
    //    NSString *imageName = (__bridge NSString *)refCon;
    //    return [UIImage imageNamed:imageName].size.width;
    return 23;
}

对选定文字的操作:

#pragma mark - touch Action

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.movieStringRange = NSMakeRange(0, 0);
    UITouch *touch = [touches anyObject];
    CGPoint point = [touch locationInView:self];
    //获取当前选中字符的范围
    NSRange range = [self touchInLabelText:point];
    if (range.length == 0) {
        [super touchesEnded:touches withEvent:event];
    }else{
        //判断当前代理方法是否实现
        if ([self.wxLabelDelegate respondsToSelector:@selector(toucheEndWXLabel:withContext:)]) {
            //获取当前点击字符串
            NSString *context = [[self.attrString string] substringWithRange:range];
            //调用点击开始代理方法
            [self.wxLabelDelegate toucheEndWXLabel:self withContext:context];
        }
    }
    
    //
    //    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //
    //
    //
    //        dispatch_async(dispatch_get_main_queue(), ^{
    //
    //        });
    //    });
    
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.movieStringRange = NSMakeRange(0, 0);
}
//手指接触视图
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint point = [touch locationInView:self];
    //获取当前选中字符的范围
    NSRange range = [self touchInLabelText:point];
    self.movieStringRange = range;
    if (range.length == 0) {
        [super touchesBegan:touches withEvent:event];
    }else
    {
        //判断当前代理方法是否实现
        if ([self.wxLabelDelegate respondsToSelector:@selector(toucheBenginWXLabel:withContext:)]) {
            //获取当前点击字符串
            NSString *context = [[self.attrString string] substringWithRange:range];
            //调用点击开始代理方法
            [self.wxLabelDelegate toucheBenginWXLabel:self withContext:context];
        }
    }
    
}


#pragma mark - 检索当前点击的是否是链接文本
//检查当前点击的是否是连接文本,如果是返回文本的位置
- (NSRange)touchInLabelText:(CGPoint)point
{
    //获取当前的行高
    float lineHeight = self.font.pointSize + self.linespace;
    
    int indexLine = point.y / lineHeight;
    //NSLog(@"indexLine:%d",indexLine);
    
    //如果当前行数大于最大行数
    if (indexLine >= _row.count) {
        return NSMakeRange(0, 0);
    }
    //如果当前行是最后一行and点击位置的横坐标大于当前行文本最大的位置
    if (indexLine == _row.count - 1 && point.x > _lastLineWidth) {
        return NSMakeRange(0, 0);
    }
    
    //如果点击在当前行文字的上方空白位置
    //    if (point.y <= indexLine *lineHeight + (asc+des+lead) * (_mutiHeight - 1.0f)) {
    //        return NSMakeRange(0, 0);
    //    }
    
    
    //获取当前行
    CTLineRef selectLine = CFArrayGetValueAtIndex((__bridge CFArrayRef)_row, indexLine);
    CFIndex selectCharIndex = CTLineGetStringIndexForPosition(selectLine, point);
    
    
    //获取当前行结束字符位置
    CFIndex endIndex = CTLineGetStringIndexForPosition(selectLine, CGPointMake(self.frame.size.width-1, 1));
    
    
    //获取整段文字中charIndex位置的字符相对line的原点的x值
    CGFloat beginset;
    do {
        //获取当前选中字符距离起点位置
        CTLineGetOffsetForStringIndex(selectLine,selectCharIndex
                                      ,&beginset);
        //判断当前字符的开始位置是否小于点击位置
        if (point.x >= beginset) {
            //判断当前字符是否为最后一个字符
            if (selectCharIndex == endIndex) {
                break;
            }
            //判断当前字符的结束位置是否大于点击位置
            CGFloat endset;
            CTLineGetOffsetForStringIndex(selectLine,selectCharIndex + 1,&endset);
            if (point.x <= endset) {
                break;
            }else
            {
                selectCharIndex++;
            }
        }else
        {
            selectCharIndex--;
        }
        
    } while (YES);
    
    //判断当前点击的位置是否在链接文本位置
    for (NSValue *value in _ranges) {
        NSRange range = [value rangeValue];
        if (range.location <= selectCharIndex && selectCharIndex + 1 <= range.location + range.length) {
            return range;
        }
    }
    
    
    return NSMakeRange(0, 0);
}

#pragma mark - 当前手指触摸文本
//复写当前选中的链接文本的索引
- (void)setMovieStringRange:(NSRange)movieStringRange
{
    if (_movieStringRange.location != movieStringRange.location || _movieStringRange.length != movieStringRange.length) {
        _movieStringRange = movieStringRange;
        [self setNeedsDisplay];
    }
}

另外,定义一个求文本内容高度的方法,通过这个方法,可以方便求出带有图片内容的文本高度:


#pragma mark - 计算文本高度
#define kHeightDic @"kHeightDic"
//计算文本内容的高度
+ (float)getTextHeight:(float)fontSize
                 width:(float)width
                  text:(NSString *)text
             linespace:(CGFloat)linespace
{
    
    if (linespace == 0) {
        linespace = 10;
    }
    
    WXLabel *wxLabel = [[WXLabel alloc] initWithFrame:CGRectMake(0, 0, width, 400)];
    wxLabel.linespace = linespace;
    wxLabel.font = [UIFont systemFontOfSize:fontSize];
    wxLabel.text = text;
//    [wxLabel drawRect:CGRectMake(0, 0, width , 400)];
    
    return wxLabel.textHeight;
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值