如何获取目标组件相对位置信息
==============

#### 问题现象

如何获取当前组件相对位置信息有以下场景：

*   如何获取目标组件相对父元素位置信息？
*   如何获取目标组件相对屏幕顶部的信息？

#### 背景知识

*   [onAreaChange](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-universal-component-area-change-event#onareachange)：组件区域变化时触发该回调。仅会响应由布局变化所导致的组件大小、位置发生变化时的回调。
*   [createComponentObserver](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-uicontext-uiinspector#createcomponentobserver)：注册组件布局和组件绘制送显完成回调通知。
*   [getRectangleById](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-uicontext-componentutils#getrectanglebyid)：获取组件大小、位置、平移、缩放、旋转及仿射矩阵属性信息。

#### 解决方案

*   方案一：通过onAreaChange中返回参数获取目标组件相对父元素和页面左上角的坐标位置。
    
    | 
    名称
    
     | 
    
    类型
    
     | 
    
    说明
    
     |
    | :-- | :-- | :-- |
    | 
    
    position
    
     | 
    
    [Position](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-types#position)
    
     | 
    
    目标元素左上角在以父元素为基准的组件坐标系中的位置。
    
     |
    | 
    
    globalPosition
    
     | 
    
    Position
    
     | 
    
    目标元素左上角在当前窗口坐标系中的位置。
    
     |
    
    import { window } from '@kit.ArkUI';
    
    interface T {
      x: string,
      y: string
    }
    
    @Entry
    @Component
    struct AreaExample {
      @State sizeValue: string = '';
      @State pos: T | undefined = undefined;
      @State statusBarHeight: number = 0;
    
      aboutToAppear(): void {
        let type1 = window.AvoidAreaType.TYPE\_SYSTEM;
        window.getLastWindow(this.getUIContext().getHostContext())
          .then((data) => {
            _// 获取系统默认区域，一般包括状态栏、导航栏_
            let avoidArea1 = data.getWindowAvoidArea(type1);
            _// 顶部状态栏高度_
            this.statusBarHeight = avoidArea1.topRect.height;
          });
      }
    
      build() {
        Column() {
          Row() {
            Text('目标组件')
              .fontSize(20)
              .fontWeight(FontWeight.Bold)
              .onAreaChange((oldValue: Area, newValue: Area) => {
                this.sizeValue = \`${Number(newValue.globalPosition.y).toFixed()}vp。\`;
                this.pos = {
                  x: Number(newValue.position.x).toFixed(),
                  y: Number(newValue.position.y).toFixed()
                };
                console.info(oldValue.width.toString());
              });
          }
          .width('calc(100% - 32vp)')
          .height(200)
          .backgroundColor('#f1f3f5')
          .margin({
            left: 16,
            right: 16
          })
          .alignItems(VerticalAlign.Center)
          .justifyContent(FlexAlign.Center);
    
          Column() {
            Text(\`通过getLastWindow获取顶部状态栏高度：${this.getUIContext().px2vp(this.statusBarHeight).toFixed()}vp。\`)
              .width('100%');
            Divider().height(22).color('#182431').opacity(0.6);
            Text(\`通过onAreaChange获取目标组件距离屏幕顶部的距离：${this.sizeValue}\`).width('100%');
            Text(\`通过onAreaChange获取目标组件距离父元素的位置：${JSON.stringify(this.pos)}。\`).width('100%');
          }
          .margin({ top: 30, left: 16, right: 16});
        }
        .width('100%').height('100%');
      }
    }
    
    示例实现效果图如下：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/8b/v3/DmIIuh3pRIOGkOboc8Ej9Q/zh-cn_image_0000002628392494.png?HW-CC-KV=V1&HW-CC-Date=20260709T133906Z&HW-CC-Expire=86400&HW-CC-Sign=F3351FFCBA06258357AE373E3E27F21EF3380AF0298BD9453EFA2BD37DF0D2FB "点击放大")
    

*   方案二：通过inspector.createComponentObserver绑定指定组件，返回对应的监听句柄，结合getRectangleById中返回参数获取目标组件相对父元素和相对于屏幕的信息。
    
    | 
    名称
    
     | 
    
    类型
    
     | 
    
    说明
    
     |
    | :-- | :-- | :-- |
    | 
    
    localOffset
    
     | 
    
    [Offset](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-arkui-componentutils#offset)
    
     | 
    
    组件相对于父组件信息。
    
     |
    | 
    
    screenOffset
    
     | 
    
    Offset
    
     | 
    
    组件相对于屏幕信息。
    
     |
    
    @Entry
    @Component
    struct AreaExample2 {
      @State localOffset: string = '';
      @State screenOffset: string = '';
    
      aboutToAppear(): void {
        this.init();
      }
    
      _// 初始化方法：获取初始位置_
      init() {
        _// 绑定指定组件，返回对应的监听句柄_
        let observer = this.getUIContext().getUIInspector().createComponentObserver('target');
        _// 通过句柄向对应的查询条件注册回调，当组件布局完成时会触发该回调。type:必须填写字符串'layout'或'draw'。layout:组件布局完成。draw:组件绘制送显完成。_
        observer.on('layout', () => {
          let node = this.getUIContext().getComponentUtils().getRectangleById('target');
          if (node) {
            this.localOffset = JSON.stringify(node.localOffset);
            this.screenOffset = JSON.stringify(node.screenOffset);
          }
          _// 通过句柄向对应的查询条件取消注册回调，当组件布局完成时不再触发指定的回调。_
          observer.off('layout');
        });
      }
    
      build() {
        Column() {
          Row() {
            Text('目标组件')
              .fontSize(20)
              .fontWeight(FontWeight.Bold)
              .key('target');
          }
          .width('calc(100% - 32vp)')
          .height(200)
          .backgroundColor('#f1f3f5')
          .margin({
            left: 16,
            right: 16
          })
          .alignItems(VerticalAlign.Center)
          .justifyContent(FlexAlign.Center);
    
          Column() {
            Text(\`通过getRectangleById获取目标组件相对于屏幕信息：${this.screenOffset}。\`).width('100%');
            Text(\`通过getRectangleById获取目标组件距离父元素的位置：${this.localOffset}。\`).width('100%');
          }
          .margin({ top: 30, left: 16, right: 16});
        }
        .width('100%').height('100%');
      }
    }
    
    示例实现效果图如下：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/e9/v3/hRA3aYk3SlioonzUtfQ-1w/zh-cn_image_0000002658911711.png?HW-CC-KV=V1&HW-CC-Date=20260709T133906Z&HW-CC-Expire=86400&HW-CC-Sign=0EECD317E5F32D5038DF668244B5B47DDFA037C1F8D1649E3BBDC88EB1E2BE83 "点击放大")
    

#### 总结

| 
方案

 | 

区别

 |
| :-- | :-- |
| 

方案一

 | 

onAreaChange组件区域变化时触发该回调。仅会响应由布局变化所导致的组件大小、位置发生变化时的回调。由绘制变化所导致的渲染属性变化不会响应回调，如translate、offset。若组件自身位置由绘制变化决定也不会响应回调，如bindSheet。

 |
| 

方案二

 | 

createComponentObserver注册组件布局和组件绘制送显完成回调通知。getRectangleById可获取组件大小、位置、平移、缩放、旋转及仿射矩阵属性信息。

 |