ArkTs-CustomSpan实现有padding的Span

问题场景

页面的标题要求超长情况下能够折行显示,且后面跟随着一个标签;折行显示时若使用两个Text控件,无法做到文案连续排布的效果,故选择使用Span进行显示。

调研后发现,Span和ContainerSpan均无法设置padding属性,标签的背景显示不正常;需使用CustomSpan进行自定义控件的绘制。

在这里插入图片描述

注意点

CustomSpan官方文档:属性字符串(搜drawTextBlob)。示例给出的效果比较随意,实现时有一些细节需要注意:

1.数值单位

控件本身计算宽度、大小等数值时,使用的是vp、fp;onDraw中实际绘制时,使用的是px,需要转换一下

2.控件位置

Span位置计算时,是相较于父控件的左上角开始计算的;要基于给定的起始点(options)来计算,否则在多行情况下会显示错误

3.文字绘制

文字的绘制位置不是从左上角开始,而是从文字baseline开始,需要对位置进行一下校准

4.控件使用

自定义span作为styledString,通过textController传给Text控件显示(见下方示例);

需要注意数据设置的时机:如果当前位置有@Entry注解,可以在onPageShow中调用;若是自定义Component,可在onDidBuild中调用,同时需检查不要引起循环build

完整代码如下
Component中使用
@Component
export struct TitleView {
    ...
    textController: TextController = new TextController();

    onDidBuild(): void {
        this.onTextChanged();
    }

    onTextChanged() {
        // 处理数据
        // 1.需要通过textController来设置
        // 2.设置的时机:如果有@Entry注解,可以在onPageShow中调用;
        // 若是自定义Component,可在onDidBuild中调用,同时需检查不要引起循环build
        let style = new MutableStyledString(this.name);
        if (this.label && this.label.length > 0) {
            // 此处为自定义span,跟在了标题后面
            let span = new XxxSpan();
            span.setPadding(4, 4, 2, 2);
            span.setBoarderRadius(2);
            style.appendStyledString(new StyledString(span));
        }
        this.textController.setStyledString(labelStyle);
    }

    build() {
        // 设置textController
        Text(undefined, { controller: this.textController })
        ...
    }
}
自定义Span控件(因业务原因删去部分信息)
/**
 * 自定义Span —— 可设置padding和背景
 */
export class XxxSpan extends CustomSpan {
    // 传入的数据
    width: number = 0;
    height: number = 0;
    fontSize: number = 11; // 文字大小
    paddingLeft: number = 0;
    paddingRight: number = 0;
    paddingTop: number = 0;
    paddingBottom: number = 0;
    boarderRadius: number = 0; // 背景圆角
    // 绘制中需要的数据
    marginStart: number = 20; // 与前方文字的间距(单位:px)
    textBottomGap: number = 5; // 文字中线与底部的间距,调整位置(单位:px)

    constructor() {
        super();
    }

    /**
     * 设置文字大小、文字、文字颜色、背景色
     */
    setFontValue(fontSize: number) {
        this.fontSize = fontSize;

        try {
            let sizeOption = measure.measureTextSize({
                textContent: this.fontValue.text,
                fontSize: this.fontSize
            });
            this.width = px2vp(Number(sizeOption.width));
            this.height = px2vp(Number(sizeOption.height));
        } catch (error) {
            BMLog.error(TAG, `measureTextSize error: ${error}`);
        }
    }

    /**
     * 设置padding
     */
    setPadding(paddingLeft: number, paddingRight: number, paddingTop: number, paddingBottom: number) {
        this.paddingLeft = paddingLeft;
        this.paddingRight = paddingRight;
        this.paddingTop = paddingTop;
        this.paddingBottom = paddingBottom;
    }

    /**
     * 设置背景圆角
     */
    setBoarderRadius(boarderRadius: number) {
        this.boarderRadius = boarderRadius;
    }

    reset() {
        this.width = 0;
        this.height = 0;
        this.fontSize = 11;
        this.paddingLeft = 0;
        this.paddingRight = 0;
        this.paddingTop = 0;
        this.paddingBottom = 0;
        this.boarderRadius = 0;
    }

    onMeasure(measureInfo: CustomSpanMeasureInfo): CustomSpanMetrics {
        return {
            width: this.width + this.paddingLeft + this.paddingRight,
            height: this.height + this.paddingTop + this.paddingBottom
        };
    }

    onDraw(context: DrawContext, options: CustomSpanDrawInfo) {
        let canvas = context.canvas;
        const brush = new drawing.Brush();

        // 画背景
        let backgroundColor = this.hexToRgba(this.fontValue.backgroundColor);
        let verticalMid = (options.lineBottom - options.lineTop) / 2;
        brush.setColor({
            alpha: backgroundColor.alpha,
            red: backgroundColor.red,
            green: backgroundColor.green,
            blue: backgroundColor.blue
        });
        canvas.attachBrush(brush);
        canvas.drawRoundRect(new drawing.RoundRect({
            left: options.x + this.marginStart,
            top: options.lineTop + verticalMid - vp2px(this.height / 2 + this.paddingTop),
            right: options.x + this.marginStart + vp2px(this.width + this.paddingLeft + this.paddingRight),
            bottom: options.lineTop + verticalMid + vp2px(this.height / 2 + this.paddingBottom)
        }, this.boarderRadius, this.boarderRadius));

        // 画文字
        const font = new drawing.Font();
        font.setSize(fp2px(this.fontSize));
        const textBlob = drawing.TextBlob.makeFromString(this.fontValue.text, font,
            drawing.TextEncoding.TEXT_ENCODING_UTF8);
        const fontColor = this.hexToRgba(this.fontValue.textColor);
        brush.setColor({
            alpha: fontColor.alpha,
            red: fontColor.red,
            green: fontColor.green,
            blue: fontColor.blue
        });
        canvas.attachBrush(brush);
        canvas.drawTextBlob(textBlob, options.x + this.marginStart + vp2px(this.paddingLeft),
            options.lineTop + verticalMid + vp2px(this.height / 2) - this.textBottomGap);
        canvas.detachBrush();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值