1.效果样式
最近在做一个需求,效果图见下图红色框内,标题前放置 标签。
注意:
1.标签包含文本 / 图文 / 单图
2.标签的数量动态(1个或2个)
3.标签长度不限制,可能两个标签占满屏幕宽度,即使占满屏幕宽度但是只在一行显示

image.png
2.效果解析
- 问题1.应采用什么布局实现?
- 问题2.如何控制标题在超出一行,换行时居左?
- 问题3.如何控制两个标签占满屏幕宽度时样式?
方案确认
解答1:结合设计,目前排除Colum和Row,暂时无法实现,目前采用Stack层叠布局(也可以使用RelativeContainer相对布局)。
解答2:结合Stack层叠布局 + Text组件的Span进行对为本占位方式(注意需设置为透明),查看组件Text的Api看了下,属性textIndent设置首行文本缩进,大家也可以试一试。
解答3:这里需要计算标签宽度,并检查是否填充父容器的宽度,使用measure.measureText(),如果是图文需要加上图片的宽度,通过constraintSize属性来约束标签的尺寸。
3.代码实现
3.1.单标签(图文) + 标题实现
@Entry
@Component
struct TagTextPage {
@State tag: string = '回复';
@State title: string = '通过设计单独的路由模块和动态加载方法,将路由功能抽取为单独的模块供其他模块使用';
build() {
Column() {
Stack({alignContent:Alignment.TopStart}){
Text(this.tag)
.fontSize(14)
.fontColor(Color.White)
.backgroundColor(Color.Blue)
.padding(2)
.onClick(() => {
console.error('ych' , `点击Tag`)
})
Text(this.title){
//这里注意:如果存在图片,则需要设置ImageSpan并指定width来进行展位,这里使用Span设置width进行展位没有效果。
Span(this.tag)
.fontSize(16 )
.fontColor(Color.Transparent)
//标题文本
Span(`${this.title}`)
}
.letterSpacing(2)
.lineHeight(20)
.fontColor(Color.Black)
.fontSize(16)
.margin({left:4})
}
}
.padding({top:40,left:10,right:10,bottom:10})
.height('100%')
.width('100%')
}
}
-
效果展示:
image.png - 注意:
- 如果存在图片时,需要在标题的Text组件中添加ImageSpan设置width来进行占位,Span设置width无效果。
Text(this.title){ //这里注意:如果存在图片,则需要设置ImageSpan并指定width来进行展位,这里使用Span设置width进行展位没有效果。 ImageSpan("").width(4) Span(this.tag) .fontSize(16) .fontColor(Color.Transparent) //标题文本 Span(`${this.title}`) }
3.2.两个标签(可能一个/两个,可能一个占满屏幕/可能两个占满屏幕) + 标题实现
@Entry
@Component
struct TagTextPage {
//标签宽度
// tag1Title:string = "你好北京"
tag1Title:string = "你好北京你好北京你好北京你好北京你好北京你好北京你好北京你好北京你好北京你好北京你好北京你好北京你好北京你好北京"
@State tag1Width: number = 0
// tag2Title:string = "你好河南"
tag2Title:string = "你好河南你好河南你好河南你好河南你好河南你好河南你好河南你好河南"
@State tag2Width: number = 0
//屏幕宽度(这里也需要考虑边距)
screenWidth: number = 0
@State title: string = '通过设计单独的路由模块和动态加载方法,将路由功能抽取为单独的模块供其他模块使用';
aboutToAppear(): void {
//容器宽度 = 屏幕宽度 - 边距
this.screenWidth = display.getDefaultDisplaySync().width - vp2px(20)
}
calculateTagWidth(textContext: string):number{
return MeasureText.measureText({
textContent: textContext,
fontSize: `${vp2px(14)}px` //这里注意需要为px
})
}
//多标签
build() {
Column() {
Stack({alignContent:Alignment.TopStart}){
if (this.isFullScreenWidth()){
//充满,那么就需要将两个标签放置在一行,文本从第二行展示
Row(){
//标签
Text(this.tag1Title)
.fontSize(14)
.lineHeight(20)
.backgroundColor(Color.Green)
.fontColor(Color.White)
.padding({left:4,right:4})
.maxLines(1)
.textOverflow({overflow: TextOverflow.Ellipsis})
.constraintSize({
maxWidth: `${this.tag1Width}px`
})
Text("").width(4)
Text(this.tag2Title)
.fontSize(14)
.lineHeight(20)
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.padding({left:4,right:4})
.maxLines(1)
.textOverflow({overflow: TextOverflow.Ellipsis})
.constraintSize({
maxWidth: `${this.tag2Width}px`
})
}
.width('100%')
.justifyContent(FlexAlign.Start)
.height(20)
Text(this.title)
.letterSpacing(2)
.lineHeight(20)
.fontColor(Color.Black)
.fontSize(14)
.align(Alignment.TopStart)
.margin({ top: 21 })
}else {
//未充满,那么就和第一种一样,标签和标题放置在一列,文本进行占位缩进
Stack({alignContent:Alignment.TopStart}){
Row(){
//标签
Text(this.tag1Title)
.fontSize(14)
.lineHeight(20)
.backgroundColor(Color.Green)
.fontColor(Color.White)
.padding({left:4,right:4})
.constraintSize({
maxWidth: `${this.tag1Width}px`
})
Text("").width(4)
Text(this.tag2Title)
.fontSize(14)
.lineHeight(20)
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.padding({left:4,right:4})
.constraintSize({
maxWidth: `${this.tag2Width}px`
})
}
.width('100%')
.justifyContent(FlexAlign.Start)
.height(20)
//标题缩进
Text(){
Span(this.tag1Title)
.fontSize(14)
.fontColor(Color.Transparent)
.padding({left:4,right:4})
ImageSpan("").width(4)
Span(this.tag2Title)
.fontSize(14)
.fontColor(Color.Transparent)
.padding({left:4,right:4})
Span(this.title)
}.letterSpacing(2)
.lineHeight(20)
.fontColor(Color.Black)
.fontSize(14)
.margin({left:4})
}
}
}
}.width('100%')
.height('100%')
.padding({top:40,left:10,right:10,bottom:10})
}
// 判断所有标签数据的宽度是否充满屏幕
private isFullScreenWidth():boolean{
//1.计算第一个标签的宽度(这里需要考虑内边距 和 图片)
this.tag1Width = this.calculateTagWidth(this.tag1Title) + vp2px(8)
if (this.tag1Width >= this.screenWidth - vp2px(45) - vp2px(4)) {
//因为需要两个都显示,所以第一个标签的宽度大于屏幕宽度,那么就让第一个标签的宽度等于 = 屏幕宽度 - 两个标签的间距 - 第二个标签默认的宽度(也就是最小的宽度)
this.tag1Width = this.screenWidth - vp2px(45) - vp2px(4)
this.tag2Width = this.screenWidth - this.tag1Width
}else {
//2.计算第二个标签的宽度(这里需要考虑内边距 和 图片)
this.tag2Width = this.calculateTagWidth(this.tag2Title) + vp2px(8)
}
//3.第一个标签的宽度 + 第二个标签的宽度 > 屏幕的宽度 ? true : false
let result = this.tag1Width + this.tag2Width >= this.screenWidth
if (result) {
this.tag2Width = this.screenWidth - this.tag1Width
}
return result
}
}
- 效果展示
-
1.两个标签均为超过屏幕宽度
image.png -
2.两个标签超过屏幕宽度
image.png -
3.第一个标签超过屏幕宽度,则第二个也需要展示,设置一个默认的最小宽度
image.png
-
3.总结
- 确定方案,明确思路,这里的计算需要认真。
- 组件共性可以通过@Builder装饰器 / @Styles装饰器 / @Extend装饰器进行组件/属性样式复用,也可以通过提高代码的阅读行和可维护性。
- @Styles装饰器 / @Extend装饰器有一定的局限性,也可以使用属性修改器AttributeModifier,可以通过Modifier对象动态修改属性。



