参考文档:https://gitee.com/openharmony/docs/blob/5654c2b940ab3e2f4f0baf435e630c4ef3536428/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-component-id.md

通过getInspectorByKey(id: string): string可以获取指定组件信息,那么组件信息中,有一个最有用的信息就是$rect。

$rect

他提供了组件绘制区域的起始点坐标(左上角)结束点坐标(右下角)。

注意:这个坐标是相对于显示屏幕左上角坐标(0,0)的。 如果一个组件绘制在滚动容器内,当容器发生滚动时,这个组件的坐标则会随着滚动而发生变化。 PS:这个设计也很有点难理解,但是也不是不能用。

根据这两个信息,我们可以获取这样的信息:

  • 组件的高宽
  • 组件相对于屏幕边缘的位置

格式化$rect

$rect返回的是一个字符串

"[起始点x坐标, 起始点y坐标][结束点x坐标, 结束点y坐标]"

我们要使用这两个坐标首先要对这个字符串进行处理

// 记录格式化结果
let rect = null

let rectStr = data['$rect']

//使用正则表达式来处理
rectStr = rectStr.replace(/\s/g, '')
var patt = /\[\d+\.\d+,\d+\.\d+\]+/g;
let arr = rectStr.match(patt)

let startPoint = JSON.parse(arr[0])
let endPoint = JSON.parse(arr[1])

// 结果
rect = {
     s: startPoint,
     e: endPoint
}

// 打印结果看看
console.log(JSON.stringify(rect))
//[phone][Console   DEBUG]  04/25 15:19:55 10596  app Log: {"s":[0,0],"e":[1080,140.96]}

有了组件位置信息,我们就可以来实现一个很常见的能力——让滚动容器滚动到指定位置。 这里我写了一个简单的示例,根据选择的数字跳转到数字对应的一行。 录制_2022_04_25_15_28_39_921.gif

示例代码:

注意:由于滚动会导致每个组件的绘制坐标变化,所以需要一开始onAppear就记录下所有组件的绘制位置。 坐标信息的单位是px(像素),如果不指定滚动位移单位,则需要px2vp方法,将坐标信息转换成为vp(屏幕密度像素)

@Entry
@Component
export struct MainPage {
  @State index: number = 0
  scroller: Scroller = new Scroller()
  arrLength = 50
  arr = new Array(this.arrLength)
  _topBarHeight: number = 0


  getRect(id) {
    let rect = null
    try {
      // @ts-ignore
      let ins = getInspectorByKey(id)

      let data = JSON.parse(ins)
      let rectStr = data['$rect']
      rectStr = rectStr.replace(/\s/g, '')
      var patt = /\[\d+\.\d+,\d+\.\d+\]+/g;
      let arr = rectStr.match(patt)

      let startPoint = JSON.parse(arr[0])
      let endPoint = JSON.parse(arr[1])

      rect = {
        s: startPoint,
        e: endPoint
      }

      console.log(JSON.stringify(rect))
    } catch (e) {

    }

    return rect
  }

  build() {
    Flex({
      direction: FlexDirection.Column
    }) {
      // 绘制顶部操作栏
      Row() {
        Counter() {
          Text(this.index.toString()).fontSize(20)
        }
        .width(150)
        .onInc(() => {
          if (this.index == this.arrLength - 1) return
          this.index++
        })
        .onDec(() => {
          if (this.index == 0) return
          this.index--
        })

        Button('jump')
          .backgroundColor(Color.White)
          .width(100)
          .height(40)
          .fontColor(Color.Black)
          .margin({
            left: 10
          })
          .onClick(() => {
            // @ts-ignore
            let insObj = this.arr[this.index]
            if (!insObj) return

            let posY = insObj['s'][1]
            this.scroller.scrollTo({
              xOffset: 0,
			// 滚动位移 = 指定组件位置 - 顶部操作栏高度
              yOffset: px2vp(posY) - px2vp(this._topBarHeight)
            })
          })
      }
      .padding({
        left: 10,
        right: 10
      })
      .backgroundColor(Color.Gray)
      .width('100%')
      .height(50)
      .id('topbar')
      .onAppear(() => {
		// 记录顶部操作栏高度
        let rect = this.getRect('topbar')
        if (!rect) return
        this._topBarHeight = rect['e'][1] - rect['s'][1]
      })

      // 绘制滚动列表
      Row() {
        Scroll(this.scroller) {
          Column() {
            ForEach(this.arr, (v, i) => {
              Flex({
                justifyContent: FlexAlign.Center,
                alignItems: ItemAlign.Center
              }) {
                Text(i.toString()).fontSize(30)
              }
              .height(50)
              .width('100%')
              .id('line' + i)
              .backgroundColor(i % 2 == 0? Color.White : '#ddd')
              .onAppear(() => {
				// 记录所有组件初始绘制信息
                let rect = this.getRect('line' + i)
                if (!rect) return
                this.arr[i] = rect
              })
            })
          }
        }
        .width('100%')
        .height('100%')
      }
      .width('100%')
      .flexGrow(1)
    }
  }
}