HarmonyOS鸿蒙Next中如何获取卡片和按钮的中心位置,使卡片的消失动画效果为缩小并移动到按钮位置?

HarmonyOS鸿蒙Next中如何获取卡片和按钮的中心位置,使卡片的消失动画效果为缩小并移动到按钮位置?

@Entry
@Component
struct CardComponent {
  @State @Watch('onAnimationChange') isShow: boolean = false;
  @State widthSize: number = 116;
  @State heightSize: number = 184;
  @State scaleCard: number = 0;
  @State opacityCard: number = 0;
  @State centerX: string = '0%';
  @State centerY: string = '0%';
  @State screenOffset: string = '';
  @State X: number = 0;
  @State Y: number = 0;
  targetBtnX: number = 0;
  targetBtnY: number = 0;
  targetCardX: number = 0;
  targetCardY: number = 0;

  aboutToAppear(): void {
    this.init()
  }

  onAnimationChange() {
    if (this.isShow) {
      this.showAnimation()
    } else {
      this.hideAnimation()
    }
  }

  showAnimation() {
    this.X = 0;
    this.Y = 0;
    this.centerX = '0%'
    this.centerY = '100%'
    // 创建动画
    this.getUIContext()?.animateTo({
      duration: 300,
      curve: Curve.EaseInOut
    }, () => {
      // 放大1倍
      this.scaleCard = 1
      // 透明度
      this.opacityCard = 1
    })
  }

  hideAnimation() {
    this.centerX = '50%'
    this.centerY = '50%'
    // 创建动画
    this.getUIContext()?.animateTo({
      duration: 300,
      curve: Curve.EaseInOut
    }, () => {
      // 缩小
      this.scaleCard = 0
      // 透明度
      this.opacityCard = 0
      // 计算需要移动的偏移量,使卡片中心移动到按钮中心
      this.X = this.targetBtnX - this.targetCardX;
      this.Y = this.targetBtnY - this.targetCardY;
    })
  }

  // 初始化方法:获取初始位置
  init() {
    // 绑定指定组件,返回对应的监听句柄
    let observer = this.getUIContext().getUIInspector().createComponentObserver('targetBtn');
    let observer2 = this.getUIContext().getUIInspector().createComponentObserver('targetCard');

    observer.on('layout', () => {
      let nodeBtn = this.getUIContext().getComponentUtils().getRectangleById('targetBtn');
      if (nodeBtn) {
        // 存储按钮的中心位置
        this.targetBtnX = nodeBtn.screenOffset.x + nodeBtn.size.width / 2;
        this.targetBtnY = nodeBtn.screenOffset.y + nodeBtn.size.height / 2;
        console.log(`targetBtnX: ${this.targetBtnX}, targetBtnY: ${this.targetBtnY}`);
      }
      console.log(`targetCardX: ${this.targetCardX}, targetCardY: ${this.targetCardY}`);
      }
      // 通过句柄向对应的查询条件取消注册回调,当组件布局完成时不再触发指定的回调
      observer.off('layout');
    })
    observer2.on('layout', () => {
      let nodeCard = this.getUIContext().getComponentUtils().getRectangleById('targetCard');
      if (nodeCard) {
        this.screenOffset = JSON.stringify(nodeCard.screenOffset)
        // 存储按钮的卡片中心位置
        this.targetCardX = nodeCard.screenOffset.x + nodeCard.size.width / 2;
        this.targetCardY = nodeCard.screenOffset.y + nodeCard.size.height / 2;
      }
      // 通过句柄向对应的查询条件取消注册回调,当组件布局完成时不再触发指定的回调
      observer2.off('layout');
    })
  }

  build() {
    Column() {
      Column() {}
      .width(this.widthSize)
      .height(this.heightSize)
      .scale({
        x: this.scaleCard,
        y: this.scaleCard,
        centerX: this.centerX,
        centerY: this.centerY
      })
      .visibility(this.opacityCard ? Visibility.Visible : Visibility.None)
      .translate({ x: this.X, y: this.Y })
      .borderRadius(6)
      .backgroundColor(Color.Yellow)
      .justifyContent(FlexAlign.Start)
      .id('targetCard')

      Button('按钮')
        .width(100)
        .height(50)
        .position({ 'bottom': 0, 'right': 0})
        .id('targetBtn')
        .onClick(() => {
          this.isShow = !this.isShow
        })
    }
    .width('100%')
    .height('100%')
  }
}

现在想实现的效果是出现的时候从左下角展开,收起的时候会收到按钮所在的位置。目前通过计算组件位置获取移动距离,但是获得的位置不太对?


更多关于HarmonyOS鸿蒙Next中如何获取卡片和按钮的中心位置,使卡片的消失动画效果为缩小并移动到按钮位置?的实战教程也可以访问 https://www.itying.com/category-93-b0.html

4 回复

楼主好aboutToAppear中直接调用init可能导致布局未完成前获取位置信息,造成坐标计算错误。

getRectangleById返回的是屏幕绝对坐标,而动画平移使用的是组件相对坐标。所以这要改一下。监听layout事件后立即注销,无法响应动态布局变化。

试一哈通过组件ID获取相对位置

getTargetPosition(id: string) {
  const node = this.getUIContext().getComponentUtils().getGeometryById(id);
  if (node) {
    return {
      x: node.position.x + node.size.width / 2,
      y: node.position.y + node.size.height / 2
    }
  }
  return { x: 0, y: 0 };
}

调整动画参数设置

hideAnimation() {
  const btnPos = this.getTargetPosition('targetBtn');
  const cardPos = this.getTargetPosition('targetCard');
  
  this.getUIContext().animateTo({
    duration: 300,
    curve: Curve.EaseInOut
  }, () => {
    this.scaleCard = 0;
    this.opacityCard = 0;
    // 计算相对父容器的偏移量
    this.X = btnPos.x - cardPos.x;
    this.Y = btnPos.y - cardPos.y;
  });
}

优化一下布局监听机制

init() {
  const observer = this.getUIContext().getUIInspector().createComponentObserver('targetBtn');
  observer.on('layout', () => {
    this.targetBtnPos = this.getTargetPosition('targetBtn');
    console.log(`Button中心: ${JSON.stringify(this.targetBtnPos)}`);
  });

  const cardObserver = this.getUIContext().getUIInspector().createComponentObserver('targetCard');
  cardObserver.on('layout', () => {
    this.targetCardPos = this.getTargetPosition('targetCard');
    console.log(`卡片中心: ${JSON.stringify(this.targetCardPos)}`);
  });
}

更多关于HarmonyOS鸿蒙Next中如何获取卡片和按钮的中心位置,使卡片的消失动画效果为缩小并移动到按钮位置?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


通过onAreaChange事件获取组件的布局区域信息,记录中心点坐标

// 按钮位置状态
@State btnCenterX: number = 0
@State btnCenterY: number = 0

// 卡片位置状态
@State cardCenterX: number = 0
@State cardCenterY: number = 0

Button('目标按钮')
  .onAreaChange((oldArea, newArea) => {
    this.btnCenterX = newArea.globalPosition.x + newArea.width / 2
    this.btnCenterY = newArea.globalPosition.y + newArea.height / 2
  })

// 卡片组件同理

可以试试

在HarmonyOS Next中,使用getBoundingClientRect()获取卡片和按钮的Rect对象,通过left/top/width/height计算中心坐标。创建显式动画animateTo,在动画闭包中:

  1. 设置卡片scale从1变为0
  2. 设置卡片translate从当前位置移动到按钮中心坐标
  3. 动画结束时设置卡片visibility为Hidden。关键代码示例:
let cardRect = card.getBoundingClientRect();
let btnRect = button.getBoundingClientRect();
animateTo({duration:500}, ()=>{
  card.scale({x:0, y:0});
  card.translate({
    x: btnRect.left + btnRect.width/2 - cardRect.width/2,
    y: btnRect.top + btnRect.height/2 - cardRect.height/2
  });
})

在HarmonyOS Next中实现卡片动画效果时,获取组件位置需要注意以下几点:

  1. 使用getRectangleById获取的是组件在屏幕中的绝对位置,包含状态栏等系统UI的高度。建议改用getBoundingClientRect获取相对父组件的坐标。

  2. 当前代码在observer回调中立即调用了off(),这会导致只触发一次位置更新。应该保留监听以便在窗口尺寸变化时更新位置。

  3. 动画中心点计算可以简化为:

// 获取按钮中心点
const btnRect = this.getUIContext().getComponentUtils().getBoundingClientRect('targetBtn');
this.targetBtnX = btnRect.left + btnRect.width / 2;
this.targetBtnY = btnRect.top + btnRect.height / 2;

// 获取卡片中心点 
const cardRect = this.getUIContext().getComponentUtils().getBoundingClientRect('targetCard');
this.targetCardX = cardRect.left + cardRect.width / 2;
this.targetCardY = cardRect.top + cardRect.height / 2;
  1. 动画实现时,建议使用transform-origin设置缩放中心点,而不是通过centerX/centerY:
.scale({
  x: this.scaleCard,
  y: this.scaleCard
})
.transformOrigin({ x: '50%', y: '100%' }) // 设置缩放中心为底部中点
  1. 对于收起动画,直接计算两个中心点的差值作为translate的偏移量即可:
this.X = this.targetBtnX - this.targetCardX;
this.Y = this.targetBtnY - this.targetCardY; 

注意检查组件是否已正确设置id,并且确保在组件挂载完成后再获取位置信息。

回到顶部