ArkUI geometryTransition:卡片换位置,也别瞬移

举报
蓝瘦的蜕变 发表于 2026/11/10 19:47:17 2026/11/10
【摘要】 今天我们要用 geometryTransition 做一个小场景:同一张卡片从列表里的缩略形态,展开成详情页的头图。 准备 页面文件是 entry/src/main/ets/pages/arkUI/motion/ArkUIGeometryTransitionDemo.ets。 我们只讲同一个元素在两种布局里的位置和尺寸

ArkUI geometryTransition:卡片换位置,也别瞬移

今天我们要用 geometryTransition 做一个小场景:同一张卡片从列表里的缩略形态,展开成详情页的头图。

准备

页面文件是 entry/src/main/ets/pages/arkUI/motion/ArkUIGeometryTransitionDemo.ets。

我们只讲同一个元素在两种布局里的位置和尺寸变化,不做列表联动,也不做真实详情页。

第一步:给同一个元素相同的 id

.geometryTransition('motion-hero-card')

收起布局里有一块蓝色小卡片,展开布局里也有一块蓝色大封面。它们不是同时存在,但都写了同一个 geometryTransition id。

第二步:切换布局时用 animateTo 包住状态

this.getUIContext().animateTo({ duration: 520, curve: Curve.EaseInOut }, () => {
  this.expanded = !this.expanded;
})

这一步让布局切换有动画上下文。否则你只是换了一段 UI,用户看到的仍然可能是硬切。

运行后看什么

点“展开成详情形态”,小卡片会变成顶部封面。再点“回到列表形态”,它会回到列表里的位置。

如果你后面要做“列表卡片点开详情”的效果,可以先用这一页把几何转场跑通,再考虑业务数据和页面结构。

完整示例代码

@Entry
@Component
struct ArkUIGeometryTransitionDemo {
  @State expanded: boolean = false;

  private toggleLayout(): void {
    this.getUIContext().animateTo({ duration: 520, curve: Curve.EaseInOut }, () => {
      this.expanded = !this.expanded;
    })
  }

  @Builder
  sectionTitle(title: string, desc: string) {
    Column({ space: 6 }) {
      Text(title)
        .fontSize(19)
        .fontWeight(FontWeight.Bold)
        .fontColor('#111827')
      Text(desc)
        .fontSize(13)
        .fontColor('#6B7280')
        .lineHeight(20)
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }

  build() {
    Scroll() {
      Column({ space: 22 }) {
        Column({ space: 8 }) {
          Text('ArkUI geometryTransition')
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
            .fontColor('#111827')
          Text('卡片换位置,也别瞬移')
            .fontSize(16)
            .fontColor('#4B5563')
          Text('这一页只做一个元素在两种布局里的变形。我们给它同一个 geometryTransition id,让 ArkUI 帮它接住位置和尺寸变化。')
            .fontSize(14)
            .fontColor('#6B7280')
            .lineHeight(22)
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)

        this.sectionTitle('同一个元素,两个布局位置', '收起时它像列表里的小卡片,展开后变成详情头图。重点是 id 保持一致。')
        Column({ space: 16 }) {
          Button(this.expanded ? '回到列表形态' : '展开成详情形态')
            .height(38)
            .fontSize(14)
            .fontColor('#FFFFFF')
            .backgroundColor('#111827')
            .borderRadius(19)
            .padding({ left: 18, right: 18 })
            .onClick(() => {
              this.toggleLayout();
            })

          if (this.expanded) {
            Column({ space: 14 }) {
              Column({ space: 8 }) {
                Text('详情封面')
                  .fontSize(22)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#FFFFFF')
                Text('同一张卡片从列表位置变成顶部封面,过程不会突然跳。')
                  .fontSize(13)
                  .fontColor('#DBEAFE')
                  .lineHeight(20)
              }
              .width('100%')
              .height(172)
              .padding(20)
              .borderRadius(22)
              .backgroundColor('#2563EB')
              .geometryTransition('motion-hero-card')
              .alignItems(HorizontalAlign.Start)
              .justifyContent(FlexAlign.End)

              Text('下面可以继续放详情内容。这里先不展开业务,只看几何转场本身。')
                .fontSize(14)
                .fontColor('#475569')
                .lineHeight(22)
            }
            .width('100%')
            .alignItems(HorizontalAlign.Start)
          } else {
            Row({ space: 14 }) {
              Column()
                .width(76)
                .height(76)
                .borderRadius(18)
                .backgroundColor('#2563EB')
                .geometryTransition('motion-hero-card')

              Column({ space: 6 }) {
                Text('列表里的小卡片')
                  .fontSize(18)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#111827')
                Text('点按钮后,它会变成详情页头部区域。')
                  .fontSize(13)
                  .fontColor('#64748B')
                  .lineHeight(20)
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
            }
            .width('100%')
            .padding(14)
            .borderRadius(18)
            .backgroundColor('#F8FAFC')
            .border({ width: 1, color: '#E5E7EB' })
          }
        }
        .width('100%')
        .alignItems(HorizontalAlign.Start)
      }
      .width('100%')
      .padding(24)
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。