【open harmony/harmonyos】ArkUI 玻璃拟态弹层实战:打造轻量级节点详情面板

前言 ✨

在 HarmonyOS / OpenHarmony 应用中,弹层是非常常见的交互形式。

比如:

  • 新增内容
  • 查看详情
  • 编辑信息
  • 二次确认
  • 快捷操作

如果弹层只是普通白底卡片,当然能用,但在沉浸式界面中会显得比较割裂。我的项目 星图 Xingtu 是一个全屏 3D 知识星图应用,背景是深色空间和发光节点,所以弹层也不能做得太“传统”。

这篇文章会结合项目中的节点详情弹层、创建节点弹层、词图生成弹层,分享如何使用 ArkUI 实现玻璃拟态风格的轻量级浮层。🧊

一、为什么选择玻璃拟态弹层

星图应用的主界面是一个全屏空间场景:

  • 背景是深色渐变
  • 节点有发光效果
  • 底部是悬浮导航栏
  • 顶部有悬浮操作区

如果这时候弹出一个完全不透明的大卡片,会遮挡场景,也会破坏沉浸感。

所以项目里的弹层采用了这种设计:

  • 半透明背景
  • 背景模糊
  • 细描边
  • 大圆角
  • 轻阴影
  • 内容简洁

它不是跳转页面,而是覆盖在星图上方,让用户始终感觉自己还在同一个空间里。🌌

二、弹层组件拆分

项目中主要有三个弹层:

  • XingtuNodeSheet:节点详情弹层
  • XingtuCreateNodeSheet:新增节点弹层
  • XingtuGenerateWordMapSheet:词图生成弹层

它们的职责不同,但视觉风格保持一致:

  • 节点详情:展示标题、备注、标签、连接、删除、关闭
  • 新增节点:输入名称、备注、标签
  • 词图生成:输入主题和关键词,生成星图

这种拆分方式比较清晰。每个弹层只关注自己的业务,不把所有逻辑塞进一个巨大组件里。

三、节点详情弹层结构

先看节点详情弹层 XingtuNodeSheet

@Component
export struct XingtuNodeSheet {
  node: XingtuNode | null = null;
  linking: boolean = false;
  onClose: () => void = () => {};
  onLink: (nodeId: string) => void = () => {};
  onDelete: (nodeId: string) => void = () => {};

  build() {
    if (!this.node) {
      Column() {}
    } else {
      Column({ space: 10 }) {
        Text(this.node.title)
          .fontSize(24)
          .fontWeight(FontWeight.Bold)

        Text(this.node.note.length > 0 ? this.node.note : '暂无备注')
        Text(this.node.tags.length > 0 ? this.node.tags.join(' / ') : '暂无标签')

        Row({ space: 12 }) {
          Button(this.linking ? '连线中' : '连接')
          Button('删除')
          Button('关闭')
        }
      }
    }
  }
}

这里有一个小细节:当 node 为空时,组件返回一个空 Column,不展示内容。

这样外层可以始终挂载这个组件,只通过选中节点来控制它是否显示。

四、玻璃拟态样式核心 🧊

节点详情弹层的高级感主要来自下面这组样式:

.padding({ left: 18, right: 18, top: 18, bottom: 18 })
.borderRadius(28)
.backgroundColor(XingtuTheme.materialGlass)
.backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THIN)
.border({ width: 1, color: XingtuTheme.materialStroke })
.shadow({
  radius: 28,
  color: XingtuTheme.softShadow,
  offsetX: 0,
  offsetY: 12
})

几个关键点:

  • backgroundColor 使用半透明颜色
  • backgroundBlurStyle 增加背景模糊
  • border 用低透明描边强化边界
  • shadow 让浮层从背景中浮起来
  • borderRadius(28) 让弹层更柔和

这几个属性组合起来,就能做出比较自然的玻璃拟态效果。

五、统一主题色管理 🎨

为了避免样式散落在各个组件里,项目把颜色统一放进 XingtuTheme

export class XingtuTheme {
  static readonly materialGlass: string = '#29FFFFFF';
  static readonly materialStroke: string = '#24FFFFFF';
  static readonly surfaceElevated: string = '#2EFFFFFF';
  static readonly textPrimary: string = '#F8FAFC';
  static readonly textSecondary: string = '#A7B3C7';
  static readonly primaryAction: string = '#F8FAFC';
  static readonly primaryActionText: string = '#0B1220';
  static readonly destructive: string = '#FF453A';
  static readonly softShadow: string = '#24000000';
}

这样有几个好处:

  • 多个弹层风格统一
  • 后续调整主题更方便
  • 颜色含义更清楚
  • 不容易出现“同一个白色写了五种透明度”的问题

做 UI 文章时,这类主题色封装也很值得展示,因为它能体现项目不是随手堆样式,而是有设计规范的。

六、新增节点弹层

新增节点弹层 XingtuCreateNodeSheet 负责收集用户输入。

@State title: string = '';
@State note: string = '';
@State tagsText: string = '';

private createNode(): void {
  const parsedTags: string[] = this.tagsText
    .split(',')
    .map((item: string) => item.trim())
    .filter((item: string) => item.length > 0);

  this.onCreate(this.title.trim(), this.note.trim(), parsedTags);

  this.title = '';
  this.note = '';
  this.tagsText = '';
}

这里把标签输入设计成逗号分隔,提交时再转换成数组。

表单提交后会清空本地状态,这样下次打开弹层时不会残留旧内容。

七、输入框样式处理

为了和整体视觉一致,输入框也使用了半透明背景和圆角:

TextInput({ text: this.title, placeholder: '名称' })
  .height(46)
  .borderRadius(14)
  .backgroundColor(XingtuTheme.surfaceElevated)
  .fontColor(XingtuTheme.textPrimary)
  .onChange((value: string) => this.title = value)

这里的重点不是复杂,而是统一:

  • 输入框高度一致
  • 圆角一致
  • 背景色一致
  • 字体颜色一致

当一个弹层里有多个输入项时,统一的规格会明显提升页面质感。

八、操作按钮分层

弹层中通常会有多个按钮,比如:

  • 取消
  • 创建
  • 删除
  • 连接
  • 关闭

项目中使用不同颜色区分按钮优先级:

Button('创建')
  .layoutWeight(1)
  .height(44)
  .backgroundColor(XingtuTheme.primaryAction)
  .fontColor(XingtuTheme.primaryActionText)

Button('删除')
  .layoutWeight(1)
  .height(44)
  .backgroundColor(XingtuTheme.surfaceElevated)
  .fontColor(XingtuTheme.destructive)

主操作使用亮色背景,危险操作使用红色文字,普通操作使用半透明背景。

这样用户不用仔细阅读,也能快速判断哪个操作最重要、哪个操作需要谨慎。

九、弹层在主界面中的挂载

这些弹层最终都挂载在 XingtuAppShellStack 中。

Stack({ alignContent: Alignment.Bottom }) {
  this.buildHdsTabs()
  this.buildPrimaryAddButton()

  XingtuNodeSheet({
    node: this.selectedNode(),
    linking: this.store.linkingSourceId !== null,
    onClose: () => {
      this.store.selectNode(null);
      this.store.linkingSourceId = null;
      this.revision += 1;
    }
  })
  .margin({ left: 16, right: 16, bottom: this.overlayBottomInset() })

  XingtuCreateNodeSheet({ ... })
  XingtuGenerateWordMapSheet({ ... })
}

这里用 overlayBottomInset() 控制弹层距离底部导航的间距,避免弹层和悬浮 Tab 重叠。

private overlayBottomInset(): number {
  return this.isImmersiveGraphSurface() ? 96 : 112;
}

这个细节在沉浸式界面里很重要:浮层多了以后,最容易出问题的不是样式,而是层级和避让。

十、体验总结 🌟

通过这套设计,项目中的弹层具备了几个特点:

  • 不跳转页面,保持沉浸感
  • 玻璃拟态风格,和星图背景融合
  • 输入、详情、生成弹层风格统一
  • 主按钮、普通按钮、危险按钮层级清晰
  • 弹层和底部悬浮导航保持安全距离

对于 HarmonyOS / OpenHarmony 应用来说,弹层不仅是功能容器,也可以成为提升视觉质感的重要部分。

如果你的应用本身是沉浸式、深色、高级感路线,可以尝试用 backgroundBlurStyle、半透明背景、细描边和轻阴影组合出这种玻璃拟态浮层。✨

img

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐