前言

3D 拆解分析 App 经历了三版 UI:第一版"能跑",第二版"好看",第三版"好用"。这一版的改动集中在布局层面——没有加新功能,没有换颜色,只是调整了间距、对齐方式和信息密度。

但效果很明显:首页从"松散"变成了"紧凑",同样的内容在屏幕上不需要滚动就能看完;场景卡片从"抽象网格"变成了"直觉设备",用户一眼就知道该选哪个。

首页:信息密度的提升

从宽松到紧凑

旧版的间距:

  • 标题区 margin-bottom: 22
  • Hero Card margin-bottom: 28
  • 场景卡片区 margin-bottom: 28
  • 三步拆解 margin-bottom: 14

新版的间距:

  • 标题区 margin-bottom: 20
  • Hero Card margin-bottom: 22
  • 场景卡片区 margin-bottom: 24
  • 三步拆解 margin-bottom: 10
    在这里插入图片描述

每个区域的间距都缩小了 2-8px。单看一个区域差别不大,但四个区域加起来节省了约 16px——差不多是半个卡片的高度。

CTA 按钮的布局变化

这是最大的改动。旧版的 CTA 按钮固定在屏幕底部(用 Stack({ alignContent: Alignment.Bottom })),新版把按钮放进了 Scroll 内容区:

// 旧版:固定在底部
Stack({ alignContent: Alignment.Bottom }) {
  Scroll() { Column() { ... } }
  Row() { Button('拍照开始分析') }  // 固定底部
}

// 新版:在 Scroll 内容区
Scroll() {
  Column() {
    // ... 其他内容
    Button() {
      Row() {
        Text('📸 ').fontSize(16)
        Text('拍照开始分析').fontSize(15).fontWeight(FontWeight.Bold).fontColor(Color.White)
      }
    }
    .width('100%').height(48).backgroundColor(A).borderRadius(24)
    .shadow({ radius: 10, color: A + '35', offsetY: 4 })
    .onClick(() => { this.push(this.cols(), this.rows(), '') })
    .margin({ top: 26, bottom: 24 })
  }
}

在这里插入图片描述

为什么这样改?

固定底部按钮在长内容页面有个问题:用户滚动时按钮始终可见,但按钮下面的内容(如果有的话)会被遮挡。把按钮放进 Scroll 后,它跟随内容流,用户滚动到哪它就在哪。

对于我们的场景——首页内容不多,按钮在最后一行——放进 Scroll 和固定底部的效果几乎一样。但代码更简单,不需要额外的 Stack 层。

场景卡片:从网格到设备

旧版的场景卡片信息是"3×3 网格 · 9 部件",新版改成"3×3 · 9部件"——去掉了"网格"两个字,更紧凑。

场景名称也缩短了:

  • “平板/相机” → “相机”
  • “大件设备” → “大件”

这些改动看起来微不足道,但在 2×2 的卡片网格里,每个字的宽度都很宝贵。缩短名称后,卡片不需要换行,布局更整齐。

场景卡片的对齐改进

旧版的单选按钮用 position 定位:

if (this.preset === idx) {
  Text('✓').fontSize(12).fontWeight(FontWeight.Bold).fontColor(Color.White)
    .position({ x: 6, y: 3 })
}

新版改用 Stack 居中:

Stack({ alignContent: Alignment.Center }) {
  Row() {}.width(22).height(22).borderRadius(11)
    .backgroundColor(this.preset === idx ? A : '#E8E5F0')
    .border({ width: 2, color: this.preset === idx ? A : '#DDD' })
  if (this.preset === idx) {
    Text('✓').fontSize(12).fontWeight(FontWeight.Bold).fontColor(Color.White)
  }
}

在这里插入图片描述

position 是绝对定位,需要手动计算偏移量(x: 6, y: 3)。Stack + Alignment.Center 是相对定位,自动居中。

好处:不需要硬编码偏移量,字号变了也不用重新调。

Flow 步骤:等宽分布

旧版的 Flow 步骤用 SymbolGlyph 箭头连接,每个步骤宽度固定 90px:

Row() {
  this.Flow('1', '拍照上传', ...)
  Column() { SymbolGlyph($r('sys.symbol.chevron_right')) }
  this.Flow('2', '智能切分', ...)
  Column() { SymbolGlyph($r('sys.symbol.chevron_right')) }
  this.Flow('3', '3D 爆炸', ...)
}
.width('100%').justifyContent(FlexAlign.Center)

新版用 Text(' › ') 连接,每个步骤用 layoutWeight(1) 等宽:

Row() {
  this.Flow('📸', '拍照', '拍摄物品\n清晰照片')
  Text(' › ').fontSize(14).fontColor('#DDD')
  this.Flow('🧩', '切分', '网格划分\n识别部件')
  Text(' › ').fontSize(14).fontColor('#DDD')
  this.Flow('💥', '爆炸', '部件飞散\n一目了然')
}
.width('100%').justifyContent(FlexAlign.SpaceEvenly)

在这里插入图片描述

FlexAlign.SpaceEvenly 让三个步骤均匀分布在 Row 中,两侧留白和中间间距相等。比固定宽度更灵活——屏幕变窄时步骤自动缩小,不会溢出。

Flow 步骤的简化

旧版的 Flow 有编号(1, 2, 3)和描述文字,新版去掉了编号,简化了描述:

旧版:

1  📸  拍照上传
   拍摄物品的
   清晰正面照片

新版:

📸
拍照
拍摄物品
清晰照片

去掉了编号——箭头 本身已经暗示了顺序,编号是冗余信息。简化描述后,Flow 卡片更紧凑,不需要固定宽度。

分析工作台:细节优化

复用上一版的设计

分析页面的布局基本延续了上一版的设计——头部、工作区、滑块、网格选择、部件清单。主要改动是代码层面的简化。

代码简化

旧版的变量名比较长:

@State imageUri: string = ''
@State itemName: string = ''
@State gridCols: number = 3
@State gridRows: number = 3
@State partCount: number = 0
@State intensity: number = 0
@State isPlaying: boolean = false
@State showPartList: boolean = false

新版缩短了变量名:

@State uri: string = ''
@State name: string = ''
@State cols: number = 3
@State rows: number = 3
@State count: number = 0
@State val: number = 0
@State on: boolean = false
@State list: boolean = false

对于组件内部的 @State 变量,短名称不会影响可读性——因为作用域小,上下文清晰。但在跨组件传递的 Props 或公共接口中,长名称更合适。

部件清单的深度条

部件清单的布局没有大改,但深度条的位置和样式有微调:

Row() {
  Column() {
    Text(c.idx.toString()).fontSize(11).fontWeight(FontWeight.Bold).fontColor(A)
  }
  .width(24).height(24).borderRadius(12).backgroundColor(AL)
  .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)

  Row() {}.width(c.d * 42).height(5).borderRadius(3)
    .backgroundColor(A + '50').margin({ left: 10, right: 10 })

  Column() {
    Text(c.n).fontSize(13).fontWeight(FontWeight.Medium).fontColor(T1)
    Text(c.sub).fontSize(10).fontColor(T3)
  }.alignItems(HorizontalAlign.Start).layoutWeight(1)
}

深度条的最大宽度从 c.d * 40 调整到 c.d * 42——多了 2px,在 100% 深度时差异不大,但在低深度时更明显。深度条的颜色从 A + '60' 降低到 A + '50',更柔和。

布局设计的原则

1. 间距要一致

整个页面的间距遵循一个规律:

  • 区块之间:20-28px
  • 元素之间:8-14px
  • 内边距:12-18px

不遵循规律的间距会让页面看起来"不整齐"。比如标题和卡片之间 22px,卡片和卡片之间 28px,用户会觉得"这两个卡片之间的距离怎么比标题和卡片之间还大"。

2. 对齐要明确

每个 RowColumn 都有明确的对齐方式:

// 标题区:左侧对齐
Row() {
  Column() { ... }.alignItems(HorizontalAlign.Start).layoutWeight(1)
  Stack() { ... }  // 右侧图标
}

// 场景卡片:左侧对齐
Row() {
  Text(icon).fontSize(20)
  Blank()  // 推到两侧
  Stack() { ... }  // 右侧单选
}

layoutWeight(1) + Blank() 是 ArkUI 中实现"两端对齐"的标准模式。

3. 信息密度要适中

旧版的信息密度偏低——每个区域之间留白很多,用户需要滚动才能看到所有内容。新版提高了密度,但没有过度压缩。

经验法则:一个屏幕应该能展示完整的信息层级——标题 + 主操作 + 核心选项 + 辅助信息。如果需要滚动才能看到核心选项,说明密度太低;如果信息挤在一起看不清,说明密度太高。

踩坑记录

1. Column 的 space 参数

Column({ space: 8 }) {
  Row({ space: 8 }) { ... }
  Row({ space: 8 }) { ... }
}

Column({ space: 8 }) 会让子组件之间保持 8px 间距。这比手动给每个子组件加 .margin({ bottom: 8 }) 更简洁,而且最后一个子组件不会有多余的底部间距。

2. FlexAlign.SpaceEvenly vs FlexAlign.Center

// SpaceEvenly:均匀分布,两侧有留白
.justifyContent(FlexAlign.SpaceEvenly)

// Center:居中排列,两侧无留白
.justifyContent(FlexAlign.Center)

对于 Flow 步骤,SpaceEvenlyCenter 更好——因为步骤之间需要间距,Center 会让步骤挤在一起。

3. Stack 的 alignContent vs alignItems

Stack({ alignContent: Alignment.Center }) {
  // 子组件在 Stack 中居中
}

alignContent 控制子组件在 Stack 主轴方向的对齐。Alignment.Center 让子组件在水平和垂直方向都居中。

如果 Stack 只有一个子组件,alignContent 和不设置效果一样。如果有多个子组件,它们会重叠在居中位置。

4. Scroll 内的按钮高度

Button() { ... }
  .width('100%').height(48)
  .margin({ top: 26, bottom: 24 })

按钮放在 Scroll 内时,底部需要足够的 margin(24px),否则滚动到底部时按钮会贴着屏幕边缘,视觉上很挤。

写在最后

布局重设计的核心是"做减法"——去掉冗余的编号、缩短文字、压缩间距、简化对齐方式。每一处改动都很小,但组合在一起让整个页面更紧凑、更高效。

移动端屏幕寸土寸金,每一 px 的间距、每一个字的长度都值得仔细考量。不是越宽松越好,也不是越紧凑越好——关键是找到那个"信息完整且不拥挤"的平衡点。

Logo

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

更多推荐