From e0e20d6d82161cd218a971bf6aa969146addff0d Mon Sep 17 00:00:00 2001 From: c30077388 Date: Wed, 25 Jun 2025 15:18:35 +0800 Subject: [PATCH] Feat:debug guide by aweme Signed-off-by: c30077388 Change-Id: Ia6af61aea83b5e86b0521bd3901ec645270c6d5a --- .../ui/ui-debug-example/ui-component-size.md | 333 ++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 zh-cn/application-dev/ui/ui-debug-example/ui-component-size.md diff --git a/zh-cn/application-dev/ui/ui-debug-example/ui-component-size.md b/zh-cn/application-dev/ui/ui-debug-example/ui-component-size.md new file mode 100644 index 00000000000..2d94dec4cf2 --- /dev/null +++ b/zh-cn/application-dev/ui/ui-debug-example/ui-component-size.md @@ -0,0 +1,333 @@ +# 组件尺寸错误 + +## 问题现象 + +应用通过componentUtils.getRectangleById接口获取的组件坐标或尺寸异常。 + +## 定位过程 + +在如下实例中,我们通过调用`componentUtils.getRectangleById`接口,动态获取id为“tab-pink”的tabBar组件(下称“tab-pink"组件)的坐标,然后基于这个坐标绘制一个“红点”状标记,标记”tab-pink”组件。 +存在这样一个场景:在某个短暂的时间内,tabBar的宽度被设置为0(如期望通过这个方式将tabBar隐藏)。在这个短暂的时间内,我们调用了getRectangleById尝试获取“tab-pink”组件的坐标。可以预计的是,此时获取的坐标是错误的。此时,获取到的坐标位于Tabs组件的中心位置。 +但是,当隐藏的时间结束,tabBar重新绘制时,开发者会疑惑于获取的tab-pink的坐标是错误的。 + +“replay”按键复现了这一过程:tabBar的宽度被置为0,随后红点显示;约100ms后,tabBar的宽度重置为正常宽度;此时,红点的位置是异常的。 + +```ts +import { ComponentUtils } from '@kit.ArkUI'; + +@Entry +@Component +struct Page { + @State currentIndex: number = 0; + @State msg: string = 'info'; + @State pivotX: number = 0; + @State pivotY: number = 0; + @State pivotShow: boolean = false; + @State tabBarShow: boolean = true; + + private controller : TabsController = new TabsController(); + private uiContext : UIContext | undefined = undefined; + private componentUtils : ComponentUtils | undefined = undefined; + private componentId : string = 'tab-pink'; + private flag : boolean = false; + private baseX : number = 0; + private baseY : number = 0; + + @Builder + tabBuilder(index: number, name: string) { + Column() { + Text(name) + .fontSize(16) + .fontWeight(this.currentIndex === index ? 500 : 400) + .fontColor(this.currentIndex === index ? '#007DFF': '#182431') + .lineHeight(22) + } + .id(`tab-${name}`) + .width('100%') + .height('100%') + .borderStyle(BorderStyle.Solid) + .borderWidth(1) + } + + aboutToAppear(): void { + this.uiContext = this.getUIContext(); + this.componentUtils = this.getUIContext().getComponentUtils(); + } + + getRectInfo(id?: string) : string { + let componentId : string = id??this.componentId; + let info = this.componentUtils?.getRectangleById(componentId); + let infoStr : string = ''; + if (info) { + infoStr = 'Size: ' + JSON.stringify(info.size) + ', WindowOffset: ' + JSON.stringify(info.windowOffset); + } + return infoStr; + } + + getBasePosition() : void { + if (this.flag) { + return; + } + let info = this.componentUtils?.getRectangleById('root-stack'); + if (info) { + this.baseX = info.windowOffset.x; + this.baseY = info.windowOffset.y; + this.msg = `${this.componentId}: ` + this.getRectInfo(this.componentId) + `, pivot: {x: ${this.pivotX}, y: ${this.pivotY}}`; + this.flag = true; + } + } + + onDidBuild(): void { + } + + build() { + Stack() { + Column() { + Text(this.msg) + .fontSize(20) + .border({ width: 5, color: Color.Brown }) + .width('100%') + .height('30%') + .margin({ top: 50 }) + Row() { + Button('Rect') + .onClick(() => { + this.msg = JSON.stringify(this.componentUtils?.getRectangleById('tab-pink')) + }) + .width('33%') + Button('replay') + .onClick(() => { + this.pivotShow = false; + this.tabBarShow = false; + this.pivotShow = true; + setTimeout(() => { + this.tabBarShow = true + }, 100) + }) + .width('33%') + Button('pivot') + .onClick(() => { + this.pivotShow = !this.pivotShow; + }) + .width('33%') + } + .width('100%') + .height('10%') + .justifyContent(FlexAlign.SpaceEvenly) + Tabs({ barPosition: BarPosition.End, index: this.currentIndex, controller: this.controller }) { + TabContent() { + Column() + .width('100%') + .height('100%') + .backgroundColor('#00CB87') + } + .tabBar(this.tabBuilder(0, 'green')) + TabContent() { + Column() + .width('100%') + .height('100%') + .backgroundColor('#007DFF') + } + .tabBar(this.tabBuilder(1, 'blue')) + TabContent() { + Column() + .width('100%') + .height('100%') + .backgroundColor('#FFBF00') + } + .tabBar(this.tabBuilder(2, 'yellow')) + .width('25%') + TabContent() { + Column() + .width('100%') + .height('100%') + .backgroundColor('#E67C92') + } + .tabBar(this.tabBuilder(3, 'pink')) + } + .expandSafeArea([SafeAreaType.CUTOUT, SafeAreaType.SYSTEM, SafeAreaType.KEYBOARD], + [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) + .barWidth(this.tabBarShow ? '100%' : 0) + .width('100%') + .height('40%') + .barHeight(44) + .vertical(false) + .barMode(BarMode.Fixed) + .backgroundColor('#F1F2F3') + .onChange((index: number) => { + this.currentIndex = index; + if (index == 3) { + this.pivotShow = false; + } + }) + .animation({ duration: 100, curve: Curve.Linear }) + } + .id('col') + .width('100%') + .height('100%') + .justifyContent(FlexAlign.SpaceBetween) + if (this.pivotShow) { + Text('X') + .width(18) + .height(18) + .textAlign(TextAlign.Center) + .borderRadius(9) + .fontColor(Color.White) + .backgroundColor(Color.Red) + .position({ x: this.uiContext?.px2vp(this.pivotX), y: this.uiContext?.px2vp(this.pivotY) }) + .onAreaChange(() => { + let info = this.componentUtils?.getRectangleById(this.componentId); + if (info) { + this.getBasePosition(); + this.pivotX = info.windowOffset.x - this.baseX; + this.pivotY = info.windowOffset.y - this.baseY; + this.msg = `${this.componentId}: ` + this.getRectInfo(this.componentId) + `, pivot: {x: ${this.pivotX}, y: ${this.pivotY}}`; + } + }) + } + } + .id('root-stack') + } +} +``` + +## 解决方案 + +我们可以利用`getInspectorTree`接口来定位解决这个问题。 + +1. 构建失效模型 + +这里,我们可以看到“tab-pink”组件位于页面的右下角区域,而发生异常时,获取到的坐标位于页面的底部中央位置。那么基于这个现象,我们可以得到失效模型:当获取的“tab-pink”组件位于页面的中轴处,我们认为发生的问题。 + +```ts + getRectInfo(id?: string) : string { + let componentId : string = id??this.componentId; + let info = this.componentUtils?.getRectangleById(componentId); + let infoStr : string = ''; + if (info) { + infoStr = 'Size: ' + JSON.stringify(info.size) + ', WindowOffset: ' + JSON.stringify(info.windowOffset); + } + + return infoStr; + } +``` + +2. 基于失效模型定位问题 + +增加capi场景的查询方式: +```ts + getRectInfo(id?: string) : string { + let componentId : string = id??this.componentId; + let info = this.componentUtils?.getRectangleById(componentId); + let infoStr : string = ''; + if (info) { + infoStr = 'Size: ' + JSON.stringify(info.size) + ', WindowOffset: ' + JSON.stringify(info.windowOffset); + } + try { + window.getLastWindow(this.getUIContext().getHostContext()).then((data: window.Window) => { + let windowClass = data; + if (info) { + let diff = info.windowOffset.x - windowClass.getWindowProperties().windowRect.width / 2; + if (diff > -10 && diff < 10) { + let tree = getInspectorTree(); + // 存储必要的组件布局信息 + } + } + }).catch((err: BusinessError) => { + console.error(`Failed to obtain the top window. Cause code: ${err.code}, message: ${err.message}`); + }); + } catch (exception) { + console.error(`Failed to obtain the top window. Cause code: ${exception.code}, message: ${exception.message}`); + } + return infoStr; + } +``` +我们可以解析获取到的组件树,其中组件树是以JSON的形式存储的,这里给出了一个实际的运行结果,并且截取了其中的关键信息。 +```json +{ + "$type": "root", + "width": "720.000000", + "height": "1280.000000", + "$resolution": "1.500000", + "$children": [ + { + "$type": "Stack", + "$ID": 8, + "$rect": "[0.00, 72.00],[720.00,1208.00]", + "$attrs": { + "width": "480.00vp", + "height": "757.33vp", + "id": "root-stack" + }, + "$children": [ + { + "$type": "Column", + "$ID": 9, + "$rect": "[0.00, 72.00],[720.00,1208.00]", + "$attrs": { + "width": "100.00%", + "height": "100.00%", + "id": "col" + }, + "$children": [ + { + "$type": "Tabs", + "$ID": 18, + "$rect": "[0.00, 754.00],[720.00,1280.00]", + "$attrs": { + "width": "100.00%", + "height": "40.00%", + }, + "$children": [ + { + "$type": "TabBar", + "$ID": 20, + "$rect": "[360.00, 1124.40],[360.00,1280.40]", + "$attrs": { + "width": "0.00vp", + "height": "56.00vp", + "id": "" + }, + "$children": [ + { + "$type": "Column", + "$ID": 40, + "$rect": "[360.00, 1124.40],[360.00,1208.40]", + "$attrs": { + "width": "0.00vp", + "height": "56.00vp", + "id": "" + }, + "$children": [ + { + "$type": "Column", + "$ID": 41, + "$rect": "[359.00, 1124.40],[361.00,1208.40]", + "$attrs": { + "width": "100.00%", + "height": "100.00%", + "id": "tab-pink" + } + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] +} +``` +我们首先找到需要定位的组件,在这里我们可以通过id字段找到"tab-pink"组件,并逐级向上寻找布局信息。我们需要关注的是组件的"$rect"字段,其格式为”[x, y],[w, h]",分别表示组件的绘制边界相较于窗口的左上角坐标和大小。同时,我们可以关注组件的"width"和"height"字段,它们分别表示组件的宽高属性。 +基于所述字段,我们可以清晰地看出,"tab-pink"组件位于屏幕中央(x坐标约为屏幕窗口宽度的一半,窗口宽度可以从"root"节点的宽度得到),向上观察祖先节点,可以看到其父节点和祖父节点也位于屏幕中央。其中,容易引起关注的是:其祖父节点TabBar的宽度为"0.00vp",由于Tabs组件,其子节点是居中布局的,当子组件宽度为0时,左右子组件的布局边界均位于窗口中央附近。 +自此,我们分析出了问题的原因。 + +通过分析获取的组件树,我们可以发现,组件树中,tabBar的宽度为0,同时可以看到,所有的tabBar坐标均位于tab组件的中间。根据这一信息,我们可以发现在tabBar的宽度为0时,tabBar的布局位于Tabs组件的中央。 + +3. 解决问题 + +基于失效模型,我们可知,在tabBar隐藏时,坐标会获取错误,因此,基于此失效模型,我们可以在tabBar重新显示时,再次获取"tab-pink"组件的坐标,避免错误。 \ No newline at end of file -- Gitee