前言

Popup 最大的特点之一,就是它和触发组件有明确的空间关系。它不是简单地“出现在屏幕上”,而是可以出现在组件上方、下方、左侧或右侧。这个位置关系决定了用户是否能快速理解“这块浮层是在解释谁、服务谁”。

这个案例通过四个方向按钮,把 Placement 的常见取值一次讲清楚,特别适合做 Popup 位置能力的入门训练。
一张手绘笔记风的信息图,主题是 HarmonyOS7 浮层入门:PopupStarterGuide。

页面效果

案例将四个按钮排成一个十字结构:

  • 顶部
  • 左侧
  • 右侧
  • 底部

点击每个按钮,都会在对应方向弹出相同内容的 Popup,从而直观看到位置差异。

完整代码

import { DEMO_CARD_COLOR } from './types'

@Entry
@Component
struct PopupPlacementAtlas {
  @State isShow: boolean = true
  @State showTop: boolean = false
  @State showBottom: boolean = false
  @State showLeft: boolean = false
  @State showRight: boolean = false

  build() {
    Column() {
      if (this.isShow) {
        Column() {
          Text('Popup 位置演示')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .margin({ bottom: 20 })

          Row() {
            Text('').layoutWeight(1)
            Button('顶部')
              .width(100)
              .height(44)
              .backgroundColor('#FF6B6B')
              .borderRadius(10)
              .fontColor('#FFFFFF')
              .fontSize(14)
              .bindPopup(this.showTop, {
                builder: this.positionPopup(),
                placement: Placement.Top,
                enableArrow: true,
                onStateChange: (e) => {
                  if (!e.isVisible) {
                    this.showTop = false
                  }
                }
              })
              .onClick(() => {
                this.showTop = !this.showTop
              })
            Text('').layoutWeight(1)
          }
          .width('100%')
          .margin({ bottom: 12 })

          Row() {
            Button('左')
              .width(70)
              .height(44)
              .backgroundColor('#4D96FF')
              .borderRadius(10)
              .fontColor('#FFFFFF')
              .fontSize(14)
              .bindPopup(this.showLeft, {
                builder: this.positionPopup(),
                placement: Placement.Left,
                enableArrow: true,
                onStateChange: (e) => {
                  if (!e.isVisible) {
                    this.showLeft = false
                  }
                }
              })
              .onClick(() => {
                this.showLeft = !this.showLeft
              })

            Text('').layoutWeight(1)

            Button('右')
              .width(70)
              .height(44)
              .backgroundColor('#6BCB77')
              .borderRadius(10)
              .fontColor('#FFFFFF')
              .fontSize(14)
              .bindPopup(this.showRight, {
                builder: this.positionPopup(),
                placement: Placement.Right,
                enableArrow: true,
                onStateChange: (e) => {
                  if (!e.isVisible) {
                    this.showRight = false
                  }
                }
              })
              .onClick(() => {
                this.showRight = !this.showRight
              })
          }
          .width('100%')
          .margin({ bottom: 12 })

          Row() {
            Text('').layoutWeight(1)
            Button('底部')
              .width(100)
              .height(44)
              .backgroundColor('#FFA500')
              .borderRadius(10)
              .fontColor('#FFFFFF')
              .fontSize(14)
              .bindPopup(this.showBottom, {
                builder: this.positionPopup(),
                placement: Placement.Bottom,
                enableArrow: true,
                onStateChange: (e) => {
                  if (!e.isVisible) {
                    this.showBottom = false
                  }
                }
              })
              .onClick(() => {
                this.showBottom = !this.showBottom
              })
            Text('').layoutWeight(1)
          }
          .width('100%')
        }
        .width('100%')
        .padding(24)
        .backgroundColor(DEMO_CARD_COLOR)
        .borderRadius(12)
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F6FA')
    .padding(16)
  }

  @Builder
  positionPopup() {
    Column() {
      Text('Popup 内容')
        .fontSize(14)
        .fontColor('#333333')
    }
    .padding(12)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
    .shadow({ radius: 8, color: '#00000010' })
  }
}

关键代码讲解

1. Placement 决定浮层的方向关系

案例里分别使用了四种位置:

  • Placement.Top
  • Placement.Left
  • Placement.Right
  • Placement.Bottom

这不是简单的视觉摆放,而是在告诉系统:Popup 应该相对于触发组件从哪个方向展开

2. 十字布局很适合教学和调试

这个案例把按钮刻意排成了十字结构,目的很明确:让你在一个页面里同时观察四个方向的行为。平时业务开发虽然不一定这么排,但在调试浮层位置时,这种布局很有效。

3. 同一份内容可以复用到不同位置

四个按钮都复用了 positionPopup()。这说明位置和内容本身是可以分开的:

  • 内容关注展示什么
  • 位置关注从哪里弹出

当你以后做帮助提示、标签说明、状态解释时,这种解耦方式会非常省事。

如何选择方向

一般可以这样判断:

  • 上方空间充足,用 Top
  • 下方承接内容更自然,用 Bottom
  • 列表项右侧有更多操作,用 Right
  • 左右结构中需要说明前置内容,用 Left

别只凭视觉喜好选方向,应该优先考虑空间和阅读路径。

总结

这个案例把 Popup 的方向能力拆得很直白。真正要掌握的是:位置不是装饰,而是上下文关系的一部分。当浮层方向和用户视线运动一致时,交互理解成本会低很多。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐