👋 你好,欢迎来到我的博客!我是【菜鸟学鸿蒙】
   我是一名在路上的移动端开发者,正从传统“小码农”转向鸿蒙原生开发的进阶之旅。为了把学习过的知识沉淀下来,也为了和更多同路人互相启发,我决定把探索 HarmonyOS 的过程都记录在这里。
  
  🛠️ 主要方向:ArkTS 语言基础、HarmonyOS 原生应用(Stage 模型、UIAbility/ServiceAbility)、分布式能力与软总线、元服务/卡片、应用签名与上架、性能与内存优化、项目实战,以及 Android → 鸿蒙的迁移踩坑与复盘。
  🧭 内容节奏:从基础到实战——小示例拆解框架认知、专项优化手记、实战项目拆包、面试题思考与复盘,让每篇都有可落地的代码与方法论。
  💡 我相信:写作是把知识内化的过程,分享是让生态更繁荣的方式。
  
   如果你也想拥抱鸿蒙、热爱成长,欢迎关注我,一起交流进步!🚀

1)Flex / Column / Row 原理:别只会“摆组件”,要懂它怎么“算尺寸”🧠

ArkUI 的布局,本质上逃不掉两个动作:

  1. 测量(measure):父容器把“约束”给子组件(你最多多大?最少多大?)
  2. 摆放(layout):父容器拿到子组件的“期望尺寸”,再决定你放哪、留多少间距、谁先谁后

你可以把它理解成:

  • 父容器:房东(决定房间最大/最小、怎么分配)
  • 子组件:租客(在约束内报个“我想要的大小”)
  • 最终:房东拍板(你想要不代表你能要到🤣)

1.1 Row / Column:线性布局的“规则感”很强

  • Row:主轴横向(从左到右排)

  • Column:主轴纵向(从上到下排)

  • 常见调控点:

    • 主轴:对齐/间距(比如均分、两端对齐)
    • 交叉轴:居中、拉伸等
    • 子项分配:layoutWeight()(很关键!)

你会发现 Row/Column 的体验像“排队”:
顺序明确、规则明确,只要别乱加固定宽高,整体非常稳。

1.2 Flex:更像“会变形的队列”,自由但也更容易翻车🙃

Flex 比 Row/Column 多了几件“更社会”的事:

  • wrap:一行放不下就换行
  • grow/shrink:空间富余怎么分?不够怎么挤?
  • basis:你“默认想占多少”

一句话总结:
Row/Column 更适合“结构稳定”的页面骨架;
Flex 更适合“标签流、按钮组、卡片流”这种会变化的内容。

2)自适应布局策略:别上来就写 375 适配,先把“策略层”想清楚😤

我自己做项目时,基本遵循这套顺序(很土,但真的稳):

2.1 第 1 层:单位与约束先选对

  • 优先用 vp(视口相关单位)、百分比、layoutWeight
  • 少用“拍脑袋固定 px”(除非你明确就是固定尺寸组件,比如图标按钮)
  • 文本区域要给弹性空间:别把 Text 卡死,长文案一来就爆

2.2 第 2 层:用“可切换布局”应对尺寸断点(手机/平板/分屏)

最舒服的自适应不是“每个组件都算一遍”,而是:
到某个宽度,直接换布局结构

比如:

  • 窄屏:Column(纵向单列)
  • 宽屏:Row(左右双列 / 主次分栏)

下面给你一个“能直接用”的示例:用 media query 监听窗口宽度,动态切换布局。

示例:窄屏单列 / 宽屏双列(ArkTS + ArkUI)
import mediaquery from '@ohos.mediaquery'

@Entry
@Component
struct ResponsivePage {
  @State private isWide: boolean = false
  private listener?: mediaquery.MediaQueryListener

  aboutToAppear() {
    // 你可以按项目定义断点:600vp、840vp等
    this.listener = mediaquery.matchMediaSync('(min-width: 600vp)')
    this.isWide = this.listener.matches
    this.listener.on('change', (e) => {
      this.isWide = e.matches
    })
  }

  aboutToDisappear() {
    this.listener?.off('change')
  }

  build() {
    Column({ space: 12 }) {
      Text('ArkUI 自适应布局示例')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      if (this.isWide) {
        // 宽屏:左右分栏
        Row({ space: 12 }) {
          this.LeftPanel()
            .layoutWeight(1)
          this.RightPanel()
            .layoutWeight(2)
        }
        .height('80%')
      } else {
        // 窄屏:上下结构
        Column({ space: 12 }) {
          this.LeftPanel()
          this.RightPanel()
        }
      }
    }
    .padding(16)
  }

  @Builder
  LeftPanel() {
    Column({ space: 8 }) {
      Text('左侧:过滤/导航').fontSize(16).fontWeight(FontWeight.Medium)
      Button('按钮 A')
      Button('按钮 B')
    }
    .padding(12)
    .borderRadius(12)
    .backgroundColor(0xF5F5F5)
  }

  @Builder
  RightPanel() {
    Column({ space: 8 }) {
      Text('右侧:内容列表').fontSize(16).fontWeight(FontWeight.Medium)
      List() {
        ForEach([1,2,3,4,5,6,7,8], (i: number) => {
          ListItem() {
            Text(`Item ${i}`).padding(12)
          }
        }, (i: number) => String(i))
      }
    }
    .padding(12)
    .borderRadius(12)
    .backgroundColor(0xFFFFFF)
  }
}

这类“结构切换式响应式”有个巨大的好处:
你不是在对抗每一像素,而是在管理布局形态。(省头发!)

3)多设备尺寸适配:手机、平板、折叠屏、分屏…你得把它当“窗口”而不是“设备”🪟

3.1 核心观念:适配的是“窗口尺寸变化”

同一台设备上:

  • 竖屏/横屏
  • 分屏/浮窗
  • 折叠展开/合上
    都会导致可用宽高变化。

所以建议:

  • 把“宽度断点”作为策略核心(上面 isWide 就是)
  • 关键容器用 layoutWeight 或百分比,让它自动伸缩
  • 列表/滚动区域要明确可用高度(后面会讲坑)

3.2 图片与卡片:给它“可保持比例”的策略

常见做法:

  • 图片用固定比例(比如 16:9)+ 自适应宽度
  • 卡片内边距固定(vp)+ 内容区域弹性(weight)
示例:Flex 做标签流(wrap),宽了多排,窄了自动换行
@Entry
@Component
struct ChipFlow {
  private tags: string[] = ['鸿蒙', 'ArkUI', 'Stage', '性能优化', '分布式', 'IPC', '多端适配', '安全模型', '数据库', '网络']

  build() {
    Column({ space: 12 }) {
      Text('Flex 标签流:自动换行')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)

      Flex({
        direction: FlexDirection.Row,
        wrap: FlexWrap.Wrap,
        justifyContent: FlexAlign.Start,
        alignItems: ItemAlign.Center
      }) {
        ForEach(this.tags, (t: string) => {
          Text(t)
            .padding({ left: 10, right: 10, top: 6, bottom: 6 })
            .margin({ right: 8, bottom: 8 })
            .borderRadius(999)
            .backgroundColor(0xF2F3F5)
        }, (t: string) => t)
      }
    }
    .padding(16)
  }
}

4)常见布局陷阱:这些坑我都踩过,你别再踩一遍了😭

坑 1:List/Scroll 放在 Column 里不设高度 → 直接“挤没了”

表现:页面空白、列表不显示、或者只显示一丢丢。

原因:Column 会让子组件按内容测量,如果滚动容器拿不到合理高度,就会出怪事。

✅ 修法:给它高度或 layoutWeight(1)

Column() {
  Text('标题').fontSize(18)

  // ❌ 常见错误:List 不知道自己该多高
  // List() { ... }

  // ✅ 正确:给列表一个可伸缩的剩余空间
  List() {
    ForEach([1,2,3,4,5], (i: number) => {
      ListItem() { Text(`Row ${i}`).padding(12) }
    }, (i: number) => String(i))
  }
  .layoutWeight(1)
}
.height('100%')
.padding(16)

坑 2:一堆固定宽高叠加 → 多设备直接爆炸

你在 6.1 寸上刚刚好,到了平板上就一坨留白;再到小屏上直接溢出。

✅ 修法:优先用 weight / % / 自适应断点结构切换。

坑 3:Row 里文字过长,把按钮挤没了(特别常见!)

✅ 修法:给 Text 一个可伸缩区域,让按钮保住。

Row({ space: 8 }) {
  Text('这是一段超级长的标题超级长超级长……')
    .layoutWeight(1) // 让它吃剩余空间
  Button('操作')
}
.padding(12)

坑 4:Flex 用了 shrink/grow 但没想清楚“谁该让步”

表现:某些机型下按钮被压扁、文字被裁切、布局“像被踩了一脚”。

✅ 修法:给关键元素明确最小尺寸;把可压缩的交给文本/次要区域。

坑 5:嵌套滚动(Scroll 里放 List / List 里放 Scroll)

表现:滚动手势冲突、回弹诡异、性能不稳定。

✅ 修法:尽量保持“一个方向一个滚动容器”,复杂场景用更合适的容器组合(比如 List 分组、懒加载等),别硬套。

结尾:布局写到最后,拼的不是组件,是“约束思维”😮‍💨

ArkUI 的布局系统其实很讲道理:你给清楚约束,它就给你稳定结果;你全靠固定值硬怼,它就用多设备狠狠怼回来。
所以我常反问自己一句:**“我是在描述界面,还是在赌运气?”**😅

📝 写在最后

如果你觉得这篇文章对你有帮助,或者有任何想法、建议,欢迎在评论区留言交流!你的每一个点赞 👍、收藏 ⭐、关注 ❤️,都是我持续更新的最大动力!

我是一个在代码世界里不断摸索的小码农,愿我们都能在成长的路上越走越远,越学越强!

感谢你的阅读,我们下篇文章再见~👋

✍️ 作者:某个被流“治愈”过的 移动端 老兵
📅 日期:2025-11-05
🧵 本文原创,转载请注明出处。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐