当前位置: 首页 > news >正文

鸿蒙Next如何自定义标签页

前言

项目需求是展示标签,标签的个数不定,一行展示不行就自行换行。但是,使用鸿蒙原生的 Grid 后发现特别的难看。然后就想着自定义控件。找了官方文档,发现2个重要的实现方法,但是,官方的demo中讲的很少,需要自己去看去思考。

效果图如下:
在这里插入图片描述
注意点:

  1. 需要计算整体布局的宽高,这和 Android 差不多。
  2. 注意 margin 的计算

具体的代码如下:

/*** 自定义标签页面*/
@Component struct CustomTagView {screenWidth: number = 0aboutToAppear(): void {let dis = display.getDefaultDisplaySync();let width = dis.widthlet height = dis.height// width 是单位是 px, 转换为 vp ,因为 onMeasureSize 和 onPlaceChildren 得到的 width margin 以及 padding 都是 vplet w = px2vp(width)let h = px2vp(height)this.screenWidth = wconsole.log("TagView aboutToAppear width = " + width + " , height = " + height + ", w = " + w + ", h = " + h)}@Builder childBuilder() {}@BuilderParam buildTagView: () => void = this.childBuilderresult: SizeResult = {width: 0,height: 0}// 第一步:计算各子组件的实际大小以及设置布局本身的大小onMeasureSize(selfLayoutInfo: GeometryInfo, children: Array<Measurable>, constraint: ConstraintSizeOptions) {let parentWidth = selfLayoutInfo.widthlet parentHeight = selfLayoutInfo.heightconsole.log("TagView onMeasureSize parentWidth = " + parentWidth + " , parentHeight = " + parentHeight)let startPosX = 0let columNumber = 1let childHeight = 0children.forEach((child) => {// 得到子控件的实际大小let result: MeasureResult = child.measure({minHeight: constraint.minHeight,minWidth: constraint.minWidth,maxWidth: constraint.maxWidth,maxHeight: constraint.maxHeight})let padding = child.getPadding()let border = child.getBorderWidth()console.log("TagView onMeasureSize = child width = " + result.width + "  ,  height = " + result.height+ " , padding = [" + padding.start + ", " + padding.end + ", " + padding.top + ", " + padding.bottom +"], border = ["+ border.start + ", " + border.end + ", " + border.top + ", " + border.bottom + "]")/// 计算 布局所需的高度, 宽度默认为屏幕的宽度childHeight = result.height + child.getMargin().topstartPosX += result.width + child.getMargin().startif (startPosX > parentWidth) {columNumber++startPosX = result.width + child.getMargin().start}})// 父布局的宽和高,即承载 child 布局的宽和高,这里指的就是 TagView 的宽和高this.result.width = this.screenWidth;this.result.height = childHeight * columNumber + 10 // 加10是为了底部多点空间return this.result;}// 第二步:放置各子组件的位置onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Array<Layoutable>, constraint: ConstraintSizeOptions) {let startPosX = 0let startPosY = 0let posY = 0let parentWidth = selfLayoutInfo.widthconsole.log("TagView onPlaceChildren parentWidth = " + selfLayoutInfo.width + " , parentHeight = " + selfLayoutInfo.height)children.forEach((child) => {startPosX += child.getMargin().start// 如果一行的控件的长度大于屏幕宽度则换行if (startPosX + child.measureResult.width > parentWidth) {startPosY += child.measureResult.height + child.getMargin().topstartPosX = child.getMargin().start}posY = startPosYconsole.log("TagView child width = " + child.measureResult.width + "  ,  height = " + child.measureResult.height +" , margin left = " + child.getMargin().start)child.layout({ x: startPosX, y: posY })startPosX += child.measureResult.width})}build() {this.buildTagView()}
}
调用的方法也很简单,下面是个调用的demo
@Entry
@Component
struct TestPage {@State name: string = 'hello'// @Provide 参数 key 必须是 string@Provide('provideName') pName: string = "哈哈"@Provide('count') count: number = 4textWidth: number = 0aboutToAppear(): void {let width = MeasureText.measureText({ textContent: '返厂无忧券1', fontSize: '13vp' });let w = px2vp(width)console.log("TestPage aboutToAppear >>>> width = " + width + " , w = " + w)// 如果有换行,那么长度等于最长的一行let textSize = MeasureText.measureTextSize({ textContent: '返厂无忧券1\n返厂无忧券1234', fontSize: '13vp' })let w2 = textSize.widthlet h2 = textSize.heightthis.textWidth = px2vp(w2 as number)console.log("TestPage aboutToAppear >>>> w2 = " + w2 + " , h2 = " + h2)}// @Build 参数按值传递,状态变量改变不会引起 @Build 方法内的 UI 刷新// 但是,Text(" ----- " + this.name) 中的 UI 会刷新@BuildernameView(name: string) {Text(name)}//  @Build 参数按引用传递的话,状态变量(@State name) 改变,@Build 方法内的 UI 会刷新@BuildernameView2(tmp: Tmp) {Text('V2 : name = ' + tmp.params)}// 通过builder的方式传递多个组件,作为自定义组件的一级子组件(即不包含容器组件,如Column)@BuilderTagViewChildren() {ForEach(['你好,哪吒2', '大圣归来啊', '我是名字超长的但是很厉害', 'hello world', '你真的好厉害啊哈哈', '没空看', '非凡','再来一个很长的名字啊', 'OK'],(data: string, index: number) => { // 暂不支持lazyForEach的写法Text(data).fontSize(13).fontColor(Color.Red).margin({ left: 3, top: 4 })// .width(100)// .height(100).borderWidth(1).border({ radius: 4 }).padding(5).textAlign(TextAlign.Center)// .offset({ x: 10, y: 20 })})}build() {Column() {Column() {CustomTagView({ buildTagView: this.TagViewChildren })}.backgroundColor(Color.Pink).margin({ top: 2 })}.width('100%').height('100%').alignItems(HorizontalAlign.Start)}
}

好了,具体的可以参考下 demo 啦,有疑问的可以一起交流。

http://www.lryc.cn/news/543340.html

相关文章:

  • 知识拓展:Python 接口实现方式对比:Protocol vs @implementer
  • 开源程序wordpress在海外品牌推广中的重要作用
  • 【Python爬虫(89)】爬虫“反水”:助力数字版权保护的逆向之旅
  • k8s面试题总结(五)
  • 文章精读篇——用于遥感小样本语义分割的可学习Prompt
  • Spring Boot2.0之十 使用自定义注解、Json序列化器实现自动转换字典类型字段
  • 从电子管到量子计算:计算机技术的未来趋势
  • 将CUBE或3DL LUT转换为PNG图像
  • python文件的基本操作,文件读写
  • 华为认证考试证书下载步骤(纸质+电子版)
  • 正式页面开发-登录注册页面
  • nss刷题5(misc)
  • 深入Linux序列:进程的终止与等待
  • 蓝桥杯之日期问题2
  • 【STL】7.STL常用算法(1)
  • uniapp 本地数据库多端适配实例(根据运行环境自动选择适配器)
  • 百度觉醒,李彦宏渴望光荣
  • 【算法工程】大模型局限性新发现之解决能连github但无法clone项目的问题
  • SOME/IP-SD -- 协议英文原文讲解3
  • 软件测试八股文,软件测试常见面试合集【附答案】
  • 数据结构秘籍(一)线性数据结构
  • TFChat:腾讯大模型知识引擎(DeepSeek R1)+飞书机器人实现AI智能助手
  • 使用消息队列怎样防止消息重复?
  • MySQL安装多版本与版本切换
  • Docker02 - 深入理解Docker
  • 检查SSH安全配置-sshd服务端未认证连接最大并发量配置
  • HarmonyOS Design 介绍
  • C++中的多重继承
  • Java基础第14天-坦克大战【1】
  • Java线程池入门04