鸿蒙-试一下属性字符串:除了Span之外,如何在同一个Text组件中展示不同样式的文字

文章目录

    • 前言
    • 简介
    • 有哪些类型
    • 拉出来溜溜
      • Text + Span
      • StyledString
      • 其他
      • CustomSpan
        • 先看一下构造函数
        • onMeasure(measureInfo: CustomSpanMeasureInfo): CustomSpanMetrics
        • onDraw(context: DrawContext, drawInfo: CustomSpanDrawInfo)
    • 遗留问题

前言

在开发中,经常会遇到一段文字中需要设置不同的字体样式和点击事件,最常见的就是在"我已仔细阅读并同意《隐私政策》和《用户协议》"这种情况,需要将书名号中的文字高亮,并且在点击的时候需要跳转到不同页面。一般我们可以使用Text+Span来实现,但我们还有另外一种方法:属性字符串

简介

方便灵活应用文本样式的对象,可通过TextController中的setStyledString方法与Text组件绑定,可通过RichEditorStyledStringController中的setStyledString方法与RichEditor组件绑定。
但需要注意以下几点:

  • 当组件样式和属性字符串中的样式冲突时,冲突部分以属性字符串设置的样式为准,未冲突部分则生效组件的样式。
  • 当属性字符串和Text子组件冲突时,属性字符串优先级高,即当Text组件中绑定了属性字符串,忽略Text组件下包含Span等子组件的情况。
  • 不支持@State修饰。
  • 建议将StyledString定义为成员变量,从而避免应用退后台后被销毁。
  • 目前不支持在worker线程中使用。

最重要的一点,文档上没提到的:在aboutToAppear生命周期中调用textController.setStyledString()是没有效果的的。
着也是为什么文档中的示例将该方法的调用放在onPageShow方法的原因。
当然也可以在组件的onAppear方法中调用

有哪些类型

一般情况下,MutableStyledString使用的多一些。该类继承自StyledString,其构造方法如下

 constructor(value: string | ImageAttachment | CustomSpan , styles?: Array<StyleOptions>)

一般情况下我们是这么使用的

//创建无样式属性的字符串,然后调用该对象的 appendStyledString insertStyledString 等方法设置各种属性
let mutableStyledString:MutableStyledString = new MutableStyledString("字符串")//直接添加各种样式属性
let mutableStyledString:MutableStyledString = new MutableStyledString("字符串",[{start:2,length:2,styledKey:StyledStringKey.DECORATION,styledValue:new DecorationStyle({color:Color.Red,type:TextDecorationType.LineThrough,style:TextDecorationStyle.WAVY})}])

这里的styledKey和styledValue是需要一一对应的,当这两个值不匹配时不生效。
比如:

StyledStringKey.FONT <-> TextStyle
StyledStringKey.DECORATION <-> DecorationStyle
StyledStringKey.BASELINE_OFFSET <-> BaselineOffsetStyle
StyledStringKey.LETTER_SPACING <-> LetterSpacingStyle
StyledStringKey.TEXT_SHADOW <-> TextShadowStyle
StyledStringKey.LINE_HEIGHT <-> LineHeightStyle
StyledStringKey.BACKGROUND_COLOR <-> BackgroundColorStyle
StyledStringKey.URL <-> UrlStyle
StyledStringKey.GESTURE <-> GestureStyle
StyledStringKey.PARAGRAPH_STYLE <-> ParagraphStyle
StyledStringKey.USER_DATA <-> extends UserDataSpan

还有两个比较特殊的:StyledStringKey.CUSTOM_SPANStyledStringKey.IMAGE,这两个用的比较少。

拉出来溜溜

来看下如何实现一开始说的那个例子

Text + Span

Text(){Span("我已仔细阅读并同意").fontColor("#333333").fontSize(16)Span("《用户协议》").fontColor("#39d175").fontSize(16).onClick((_)=>{promptAction.showToast({message:"打开用户协议页面"})})Span("和").fontColor("#333333").fontSize(16)Span("《隐私协议》").fontColor("#39d175").fontSize(16).onClick((_)=>{promptAction.showToast({message:"打开隐私协议页面"})})
}

StyledString

      Text(undefined,{controller:this.protocolTextController}).onAppear(()=>{let protocolStyledString : MutableStyledString = new MutableStyledString("我已仔细阅读并同意《用户协议》和《隐私协议》",[{start: 9,length: 6,styledKey: StyledStringKey.FONT,styledValue: new TextStyle({fontColor:"#39d175",fontSize:LengthMetrics.fp(16),})},{start: 9,length: 6,styledKey: StyledStringKey.GESTURE,styledValue: new GestureStyle({onClick:(event:ClickEvent)=>{promptAction.showToast({message:"打开用户协议页面"})},onLongPress:(event:GestureEvent)=>{}})},{start: 16,length: 6,styledKey: StyledStringKey.FONT,styledValue: new TextStyle({fontColor:"#39d175",fontSize:LengthMetrics.fp(16),})},{start: 16,length: 6,styledKey: StyledStringKey.GESTURE,styledValue: new GestureStyle({onClick:(event:ClickEvent)=>{promptAction.showToast({message:"打开隐私协议页面"})},onLongPress:(event:GestureEvent)=>{}})}])this.protocolTextController.setStyledString(protocolStyledString)})

当然这么比较起来还是Text+Span比较简洁。但当遇到Span不支持的属性的时候,还是得用StyledString,比如设置背景色、下划线、删除线、偏移、字间距等等

其他

整个全乎的看下效果

    this.mutableStyledString = new MutableStyledString("豫章故郡,洪都新府。星分翼轸,地接衡庐。襟三江而带五湖,控蛮荆而引瓯越。物华天宝,龙光射牛斗之墟;人杰地灵,徐孺下陈蕃之榻。", [{start: 0,length: 6,styledKey: StyledStringKey.FONT,styledValue:  new TextStyle({ fontColor: Color.Blue })}, {start: 7,length: 6,styledKey: StyledStringKey.DECORATION,styledValue: new DecorationStyle({color: Color.Red,type: TextDecorationType.LineThrough,style: TextDecorationStyle.WAVY})}, {start: 14,length: 6,styledKey: StyledStringKey.BASELINE_OFFSET,styledValue: new BaselineOffsetStyle(new LengthMetrics(6, LengthUnit.VP))}, {start: 21,length: 6,styledKey: StyledStringKey.LETTER_SPACING,styledValue: new LetterSpacingStyle(new LengthMetrics(6, LengthUnit.VP))}, {start: 28,length: 6,styledKey: StyledStringKey.TEXT_SHADOW,styledValue: new TextShadowStyle({radius: 5,type: ShadowType.COLOR,color: Color.Yellow,offsetX: 10,offsetY: -10})}, {start: 35,length: 6,styledKey: StyledStringKey.LINE_HEIGHT,styledValue: new LineHeightStyle(LengthMetrics.fp(20))}// , {//   start: 42,//   length: 6,//   styledKey: StyledStringKey.BACKGROUND_COLOR,//   styledValue: new BackgroundColorStyle({//     color: Color.Pink,//     radius: 6//   })// }// , {//   start: 49,//   length: 6,//   styledKey: StyledStringKey.URL,//   styledValue: new UrlStyle("https://www.example.com")// }, {start: 56,length: 6,styledKey: StyledStringKey.PARAGRAPH_STYLE,styledValue:new ParagraphStyle({ textAlign: TextAlign.End, maxLines: 1, wordBreak: WordBreak.BREAK_ALL, overflow: TextOverflow.Ellipsis})}]);

注意:BackgroundColorStyleUrlStyle是api14开始支持的

在这里插入图片描述

CustomSpan

我们需要继承CustomSpan并重写onMeasure(measureInfo: CustomSpanMeasureInfo): CustomSpanMetrics来完成测量,重写onDraw(context: DrawContext, options: CustomSpanDrawInfo)来完成绘制,这和自定义组件的自定义布局中重写onMeasureSizeonPlaceChildren差不多。

先看一下构造函数

CustomSpan对象只有一个无参构造函数,但一般情况下我们需要在构造函数中传入我们需要的参数,大多数情况我们需要传入要绘制的内容,这里简单的以绘制字符串为例。还需要一个UIContext的上下文对象,用于获取各种工具。
另外我们还需要根据需求,定义一些变量,来保存我们需要使用的参数。这里我们需要保存字体大小

class MyCustomSpan extends CustomSpan {constructor(text: string, uiContext: UIContext) {super();this.text = text;this.uiContext = uiContext}text: stringuiContext: UIContextfontSizeFp:number =0}
onMeasure(measureInfo: CustomSpanMeasureInfo): CustomSpanMetrics

这个方法中,我们可以获取到文字大小,需要返回一个CustomSpanMetrics对象,表示自定义绘制Span的尺寸。

  onMeasure(measureInfo: CustomSpanMeasureInfo): CustomSpanMetrics {// measureInfo.fontSize单位为fpthis.fontSizeFp = measureInfo.fontSize// 传入的fontSize单位为fp,返回文本所占布局宽度和高度单位均为px。let size =this.uiContext.getMeasureUtils().measureTextSize({ textContent: this.text, fontSize: measureInfo.fontSize })//customSpanMetrics的width和height 单位为vpthis.customSpanMetrics = { width: px2vp(size.width as number), height: px2vp(size.height as number) };return this.customSpanMetrics}

这样我们就获取到了Span的尺寸信息

onDraw(context: DrawContext, drawInfo: CustomSpanDrawInfo)

DrawContext的实例对象context中的size属性保存的画布的宽高,canvas属性保存了画布对象;这里需要注意的是:获取的画布是Text组件的画布,绘制时不会超出Text组件的范围。这里就先认为是属性的,戳进去看源码就是定义的get方法:get canvas(): drawing.Canvas;
CustomSpanDrawInfo的实例对象drawInfo则保存了自定义绘制Span的绘制信息。比如属性x自定义绘制Span相对于挂载组件的偏移
lineTop自定义绘制Span相对于Text组件的上边距lineBottom自定义绘制Span相对于Text组件的下边距baseline自定义绘制Span的所在行的基线偏移量,它们的单位是都px

  onDraw(context: DrawContext, drawInfo: CustomSpanDrawInfo): void {console.error(`onDraw drawInfo x:${drawInfo.x}  lineTop:${drawInfo.lineTop}  lineBottom:${drawInfo.lineBottom}  baseline:${drawInfo.baseline}`)console.error(`onDraw context ${vp2px(context.size.width)}   ${vp2px(context.size.height)}`)let canvas = context.canvas;const font = new drawing.Font();font.setSize(vp2px(this.fontSizeFp));const brush = new drawing.Brush();brush.setColor({alpha: 255,red: 0,green: 74,blue: 175});canvas.attachBrush(brush)const textBlob = drawing.TextBlob.makeFromString(this.text, font, drawing.TextEncoding.TEXT_ENCODING_UTF8);canvas.drawTextBlob(textBlob, drawInfo.x, drawInfo.baseline);canvas.detachBrush()}

这样我们就完成了一个简单的自绘制的Span。

遗留问题

但是这里有个很大的问题:当绘制的文字多的时候,文字并不会换行。因为我们测量出来文字是按一行计算的,高度也是一行文字的高度。

想要计算需要几行,就需要知道Text组件的宽度。这里可以从构造函数中传进来。

那么问题就变成了如何获取Text组件的宽度?可以从onAreaChange回调中获取,但这个函数并不可靠,有时候一步小心使用属性字符串时返回的宽度就是0。

另外一个问题就是,我们如何知道组件的宽度可以放下几个字?假如一行可以放下5.4个字,那实际结果肯定是一行只绘制5个字。

我们可以根据这个方法来计算需要多大的高度。

还有一个问题就是在onDraw方法中drawInfo.baseLine属性,目前来看就是最后一行文字的baseLine,如果有多行文字,还需要我们自己计算每一行的baseLine


哈哈,遗留的问题有时间再说吧,这个自定义绘制Span用的机会应该不大。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/902963.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nginx 安装与配置全流程指南(2025 最新版)

一、环境准备与依赖安装 1.1 系统要求 操作系统&#xff1a;支持主流 Linux 发行版&#xff08;Ubuntu 20.04/CentOS 7/Debian 10&#xff09;硬件配置&#xff1a;内存 ≥512MB&#xff0c;磁盘 ≥10GB 可用空间&#xff08;建议使用 SSD&#xff09;网络要求&#xff1a;开…

【LeetCode 热题 100】滑动窗口最大值 / 最小覆盖子串 / 轮转数组 / 缺失的第一个正数

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;LeetCode 热题 100 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 子串和为 K 的子数组滑动窗口最大值最小覆盖子串 普通数组最大子数组和合并区间轮转数组除自身以外数组的乘积缺失的…

golang的cgo的一点小心得

最后有个项目需要涉及到cgo&#xff0c;在这块以前用的不多&#xff0c; 这次略微用得深入了一点&#xff0c;记下来几点以备以后使用 本质上cgo去用的时候就是遵守一些ABI而已&#xff0c;总体而言&#xff0c;尽量避免复杂结构的来回传递。1 对于变长参数&#xff0c;只有…

异构网络环境下的切换策略研究

移动互联网应用快速崛起,现有的无线接入技术有,无线局域网(Wireless Local Area NetWork,WLAN),移动蜂窝网络(4G,5G),无线广域网(Wireless Wide Area Network,WWAL)以及卫星通信网络等。多接入技术方便用户通信,还符合多业务场景。这种多无线接入技术共存的网络环…

人工智能赋能美妆零售数字化转型:基于开源AI大模型的S2B2C商城系统构建

摘要 在消费升级背景下&#xff0c;美妆行业正经历从传统卖场向智能体验空间的转型。本文以"未来商店"为研究对象&#xff0c;探讨开源AI大模型与S2B2C商城系统的协同效应&#xff0c;揭示人工智能技术如何重构"人-货-场"关系。通过实证研究发现&#xff…

计算机视觉中的正则化:从理论到实践的全面解析

&#x1f31f; 计算机视觉中的正则化&#xff1a;从理论到实践的全面解析&#x1f31f; 大家好&#xff01;今天要和大家分享的是在计算机视觉&#xff08;CV&#xff09;领域中非常重要的一个概念——正则化&#xff08;Regularization&#xff09;。无论你是刚开始接触深度学…

Linux字符设备驱动开发的详细步骤

1. 确定主设备号​​ ​​手动指定​​&#xff1a;明确设备号时&#xff0c;使用register_chrdev_region()静态申请&#xff08;需确保未被占用&#xff09;。​​动态分配​​&#xff1a;通过alloc_chrdev_region()由内核自动分配主设备号&#xff08;更灵活&#xff0c;推…

软件工程效率优化:一个分层解耦与熵减驱动的系统框架

软件工程效率优化&#xff1a;一个分层解耦与熵减驱动的系统框架** 摘要 (Abstract) 本报告构建了一个全面、深入、分层的软件工程效率优化框架&#xff0c;旨在超越简单的技术罗列&#xff0c;从根本的价值驱动和熵减原理出发&#xff0c;系统性地探讨提升效率的策略与实践。…

【Docker游戏】使用Docker部署vue-XiuXianGame文字修仙小游戏

【Docker游戏】使用Docker部署vue-XiuXianGame文字修仙小游戏 一、vue-XiuXianGame介绍1.1 vue-XiuXianGame简介1.2 主要特点 二、本次实践规划2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、拉…

用 LangChain 手搓 RAG 系统:从原理到实战

一、RAG 系统简介 在当今信息爆炸的时代&#xff0c;如何高效地从海量数据中获取有价值的信息并生成准确、自然的回答&#xff0c;成为了人工智能领域的重要课题。检索增强生成&#xff08;Retrieval-Augmented Generation&#xff0c;RAG&#xff09;系统应运而生&#xff0c;…

SpringBoot集成LiteFlow实现轻量级工作流引擎

LiteFlow 是一款专注于逻辑驱动流程编排的轻量级框架&#xff0c;它以组件化方式快速构建和执行业务流程&#xff0c;有效解耦复杂业务逻辑。通过支持热加载规则配置&#xff0c;开发者能够即时调整流程步骤&#xff0c;将复杂的业务如价格计算、下单流程等拆分为独立且可复用的…

38 python random

在实际中,我们常常会用到随机的概念,比如 模拟抽奖活动(如:月度优秀员工抽奖)生成测试数据(如:随机考勤时间、随机销售额)打乱数据顺序(如:随机分配任务到人)Python 的random模块就像你的 "随机事件生成器",帮你轻松创建各种随机数据 一、基础操作:从随…

附赠二张图,阐述我对大模型的生态发展、技术架构认识。

文章精炼&#xff0c;用两张图说明大模型发展业态方向&#xff0c;以及大模型主体技术架构。&#xff08;目前还需要进一步验证我的Thought && ideas&#xff0c;等待机会吧.........&#xff09; 图一&#xff1a;探究大模型三个层次应用方向&#xff0c;浅层次入门简…

2025上海车展 | 移远通信全栈车载智能解决方案重磅亮相,重构“全域智能”出行新范式

2025年4月23日至5月2日&#xff0c;第二十一届上海国际汽车工业展览会在国家会展中心&#xff08;上海&#xff09;盛大启幕。作为车载智能解决方案领域的领军企业&#xff0c;移远通信以“全域智能 驭见未来”为主题&#xff0c;携丰富的车载解决方案及客户终端惊艳亮相8.2馆8…

告别 “幻觉” 回答:RAG 中知识库与生成模型的 7 种对齐策略

一、引言 大语言模型&#xff08;LLM&#xff09;在文本生成领域展现出惊人能力&#xff0c;但 “幻觉” 问题&#xff08;生成虚构或偏离事实的内容&#xff09;始终是落地应用的核心挑战。检索增强生成&#xff08;RAG&#xff09;通过将外部知识库与 LLM 结合&#xff0c;形…

项目笔记2:post请求是什么,还有什么请求

在 HTTP&#xff08;超文本传输协议&#xff09;中&#xff0c;请求方法用于向服务器表明客户端想要执行的操作。POST 请求是其中一种常见的请求方法&#xff0c;此外还有 GET、PUT、DELETE 等多种请求方法&#xff0c;下面为你详细介绍&#xff1a; POST 请求 定义&#xff…

中间系统-邻居建立,数据库同步

ISIS邻居状态&#xff1a; 1、Down&#xff1a;接口一旦启用ISIS协议之后就是Down状态 2、Init&#xff1a;收到了邻居的Hello报文后&#xff0c;发现了邻居。 3、up&#xff1a;收到了邻居的Hello报文&#xff0c;并且在邻居的hello报文中发现了自己。 ISIS邻居建立的条件&…

玩转Docker | Docker部署LMS轻量级音乐工具

玩转Docker | Docker部署LMS轻量级音乐工具 前言一、LMS介绍LMS简介主要特点二、系统要求环境要求环境检查Docker版本检查检查操作系统版本三、部署LMS服务下载镜像创建容器创建容器检查容器状态检查服务端口安全设置四、访问LMS服务访问LMS首页注册账号五、基本使用上传音乐文…

AR行业应用案例与NXP架构的结合

1. 工业巡检AR头盔 场景示例&#xff1a;宁德核电基地使用AR智能头盔进行设备巡检&#xff0c;通过实时数据叠加和远程指导&#xff0c;将工作效率提升35%。头盔需处理传感器数据、图像渲染和低延迟通信1。 NXP架构支持&#xff1a; 协处理器角色&#xff1a;NXP i.MX RT系列M…

【Harmony OS】组件

目录 组件概述 组件常用属性 系统内置组件 Text TextArea 多行文本输入框组件 TextInput 文本输入框 Button Image 图片组件&#xff0c;支持本地图片和网络图片 Radio 单选框 Checkbox 复选框 Blank 空白填充组件 Divider 分隔符 PatternLock 图案密码锁组件 Prog…