问题场景
页面的标题要求超长情况下能够折行显示,且后面跟随着一个标签;折行显示时若使用两个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();
}
}