UI显示异常调试

本章节主要介绍UI显示异常问题的调试方法,并结合案例讲解具体的解决步骤。

定位UI显示异常问题

UI显示异常问题主要是通过分析UI布局信息来定位。当前分析UI布局主要通过getInspectorTree接口获取组件树信息,或者通过getRectangleById接口获取单个节点的信息。

组件树

从API version 9开始,可以使用getInspectorTree接口获取组件树及其属性。

单个节点

从API version 10开始,可以使用getRectangleById接口获取组件的大小、位置、平移、缩放、旋转及仿射矩阵等属性信息。

解决UI显示异常问题

下面通过具体案例,介绍如何解决UI显示异常问题。

通过ComponentUtils.getRectangleById获取的tabBar组件坐标尺寸异常

问题现象

在动态控制tabBar显示或隐藏的场景下,通过ComponentUtils.getRectangleById获取的tabBar组件坐标或尺寸可能与预期不符。例如,当tabBar隐藏时(宽度设为0),获取的坐标位于屏幕中央,恢复显示后,该错误坐标仍被沿用。

可能原因

  • 使用同步接口查询布局信息时,目标节点的宽度临时设置为0,节点布局默认居中显示,导致获取的坐标位于屏幕中央。
  • 调用接口时,如果当前布局尚未完成渲染(例如,组件刚被隐藏或显示,布局计算未结束),查询到的将是未更新的旧布局信息。

解决措施

  • 选择合适的调用时机:在组件完成布局渲染后调用接口。例如,tabBar恢复显示后,使用延迟函数等待布局更新完成,再获取坐标。
  • 监听布局变化事件:利用组件的onAreaChange回调,在布局变化并稳定后,触发坐标获取逻辑。
  • 增加有效性校验:获取坐标后,校验组件尺寸,过滤无效数据。宽度或高度为0的组件被视为无效。

代码示例

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')
  }
}