前言
项目需求是展示标签,标签的个数不定,一行展示不行就自行换行。但是,使用鸿蒙原生的 Grid
后发现特别的难看。然后就想着自定义控件。找了官方文档,发现2个重要的实现方法,但是,官方的demo中讲的很少,需要自己去看去思考。
效果图如下:
注意点:
- 需要计算整体布局的宽高,这和 Android 差不多。
- 注意 margin 的计算
具体的代码如下:
/**
* 自定义标签页面
*/
@Component struct CustomTagView {
screenWidth: number = 0
aboutToAppear(): void {
let dis = display.getDefaultDisplaySync();
let width = dis.width
let height = dis.height
// width 是单位是 px, 转换为 vp ,因为 onMeasureSize 和 onPlaceChildren 得到的 width margin 以及 padding 都是 vp
let w = px2vp(width)
let h = px2vp(height)
this.screenWidth = w
console.log("TagView aboutToAppear width = " + width + " , height = " + height + ", w = " + w + ", h = " + h)
}
@Builder childBuilder() {}
@BuilderParam buildTagView: () => void = this.childBuilder
result: SizeResult = {
width: 0,
height: 0
}
// 第一步:计算各子组件的实际大小以及设置布局本身的大小
onMeasureSize(selfLayoutInfo: GeometryInfo, children: Array<Measurable>, constraint: ConstraintSizeOptions) {
let parentWidth = selfLayoutInfo.width
let parentHeight = selfLayoutInfo.height
console.log("TagView onMeasureSize parentWidth = " + parentWidth + " , parentHeight = " + parentHeight)
let startPosX = 0
let columNumber = 1
let childHeight = 0
children.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().top
startPosX += result.width + child.getMargin().start
if (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 = 0
let startPosY = 0
let posY = 0
let parentWidth = selfLayoutInfo.width
console.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().top
startPosX = child.getMargin().start
}
posY = startPosY
console.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 = 4
textWidth: number = 0
aboutToAppear(): 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.width
let h2 = textSize.height
this.textWidth = px2vp(w2 as number)
console.log("TestPage aboutToAppear >>>> w2 = " + w2 + " , h2 = " + h2)
}
// @Build 参数按值传递,状态变量改变不会引起 @Build 方法内的 UI 刷新
// 但是,Text(" ----- " + this.name) 中的 UI 会刷新
@Builder
nameView(name: string) {
Text(name)
}
// @Build 参数按引用传递的话,状态变量(@State name) 改变,@Build 方法内的 UI 会刷新
@Builder
nameView2(tmp: Tmp) {
Text('V2 : name = ' + tmp.params)
}
// 通过builder的方式传递多个组件,作为自定义组件的一级子组件(即不包含容器组件,如Column)
@Builder
TagViewChildren() {
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 啦,有疑问的可以一起交流。