当前位置: 首页 > news >正文

鸿蒙 HarmonyOS 6|ArkUI(02):线性布局到网格与滚动,五大容器实战

做 ArkUI 页面,容器就是地基。Row、Column、Stack、Grid、Scroll(配合 Scroller)这五个家伙经常出镜:顶部工具条、信息流、卡片墙、海报叠层、工具面板,基本都能靠它们撑起来。

UI 容器并不神秘,它们负责的就是四个老问题:内容沿哪个方向排、怎么对齐、尺寸怎么占位、滚动谁来接手。

Row 让子项横着排,Column 让子项竖着排;“主轴”和“交叉轴”的词看似陌生,其实就是“沿着排列方向”和“垂直于排列方向”。

对齐属性也分这两类:主轴上的分布交给 justify(不同版本命名略有差异),交叉轴交给 alignItems;如果某个子项要单独“破队形”,用 alignSelf。还有一个常被忽略的点是权重:layoutWeight 会把主轴剩余空间按比例分掉,谁权重大谁多吃一点,这在做均分布局时很顺手。

尺寸与约束则是“第二层安全带”:width/height/size 决定占位,padding/margin 管内外边距,最小/最大值的约束防止组件在不同屏幕尺寸下“炸形”。写页面时,一般先把容器定方向,再根据内容补对齐,最后用尺寸与约束把边界收拢,这样结构会更稳。如果你遇到“对齐不起作用”的错觉,十有八九是把方向想反了,或者属性写在了不该管事的那层。

一、Row 与 Column:先把骨架立起来

先做个最常见的工具条:左边是标题区,右边是按钮组。外层用 Row 把两块横着摆好;左边再用 Column 放标题和副标题,层级就自然了;右边的按钮组自己用 Row 管间距。为了让标题区吃掉多余空间,给它一个 layoutWeight(1);为了避免按钮挤成一团,给右侧一个最小宽度。

// Index.ets
@Entry
@Component
export struct Index {build() {Column() {// 工具条:左侧标题 + 右侧按钮组Row({ space: 12 }) {// 左侧标题列Column({ space: 2 }) {Text('项目面板').fontSize(18).fontWeight(FontWeight.Medium)Text('最近更新 · 3 个任务进行中').fontSize(12).opacity(0.7)}.layoutWeight(1) // 左侧占据剩余空间// 右侧按钮组Row({ space: 8 }) {Button('新建')Button('筛选')Button('更多')}}.width('100%').padding({ left: 16, right: 16, top: 12, bottom: 12 })// 占位内容Column({ space: 8 }) {Text('这里是内容区域示意').opacity(0.6)Text('Row/Column 的间距与权重已就位').opacity(0.6)}.padding(16)}.width('100%').height('100%')}
}

这里有两个小提醒。

第一,分清“谁是主轴谁是交叉轴”,才能判断把对齐写在 Row/Column 的哪一个层;

第二,别忘了检查子项是否单独设置了 align(或 alignSelf),它会覆盖父级对齐。相关规则可以在通用定位与对齐的英文条目里查到。

二、Stack:叠放关系一次写对,就不必算坐标

碰到“底图、遮罩、角标按钮”这种叠层,Stack 很省力。

它默认后面的子项盖在前面的上面,alignContent 决定整组内容对齐到哪里(九宫格那套)。所以只要保证顺序是“先底图,再遮罩,最后按钮”,基本不需要手算绝对定位。

// Index.ets
@Entry
@Component
export struct Index {build() {Column({ space: 12 }) {// 海报卡:底图 + 底部半透明遮罩 + 右下角按钮Stack({ alignContent: Alignment.BottomEnd }) {Image($r('app.media.foreground')).objectFit(ImageFit.Cover).width('100%').height(200).borderRadius(12)// 底部遮罩,可随时加标题/副标题Column().height(64).width('100%').backgroundColor('#66000000').borderRadius({ bottomLeft: 12, bottomRight: 12 })Button('立即查看').margin({ right: 12, bottom: 12 })}.padding({ left: 12, right: 12, top: 12 })// 占位内容Text('Stack 叠层示意,alignContent=BottomEnd').opacity(0.65).padding(12)}.width('100%').height('100%')}
}

为什么不把遮罩“烤进”图片?因为叠一层 Column 之后,你随时能在遮罩上加标题、副标题或操作区;视觉样式、交互热区和可访问性都更好维护。

三、Grid:规则网格的秩序感

需要规则的多列卡片时,用 Grid 最省事:列、行、间距都能直接在组件上声明。

columnsTemplate/rowsTemplate 负责网格轨道,columnsGap/rowsGap 负责间距。

两列卡片常见写法就是 columnsTemplate(‘1fr 1fr’),你也可以在断点处切模板,控制三列或四列的布局。

// Index.ets // 1) 明确声明条目类型(类或接口都行;这里用类,符合官方示例)
class CardItem {title: string = ''desc: string = ''
}@Entry
@Component
export struct Index {// 2) 数组显式标注为 CardItem[],元素按 CardItem 结构书写private items: CardItem[] = [{ title: '卡片 A', desc: '描述 A' },{ title: '卡片 B', desc: '描述 B' },{ title: '卡片 C', desc: '描述 C' },{ title: '卡片 D', desc: '描述 D' },]build() {Column() {Text('两列卡片').fontSize(18).fontWeight(FontWeight.Medium).padding(12)Grid() {// 3) ForEach 显式标注回调入参类型,避免 any/unknown 推断ForEach(this.items,(item: CardItem) => {GridItem() {Column({ space: 6 }) {Text(item.title).fontSize(16).fontWeight(FontWeight.Medium)Text(item.desc).fontSize(12).opacity(0.6)}.padding(12).backgroundColor('#FFFF00').borderRadius(12)}},(item: CardItem) => item.title // key,确保唯一即可)}.columnsTemplate('1fr 1fr') // 两列均分(fr 轨道语法受支持).columnsGap(12).rowsGap(12).padding(12)}.width('100%').height('100%')}
}

网格和滚动的关系,很多人会搞混。

长列表场景更适合用 List,尤其是需要懒加载、按跨轴分栏(lanes)的时候; List 的 lanes 和 alignListItem,用来做“多列列表”十分合适。你也可以配合 ScrollBar 来提升滚动可见性。

四、Scroll 与 Scroller:信息流要跑顺,事件要挂对

当内容不是规则网格、而是流式信息时,用 Scroll 承载滚动区域,再用 Scroller 做编程式控制,比如回到顶部、定位到特定偏移。

// Index.ets
function makeRange(count: number, start: number = 1): number[] {const arr: number[] = []for (let i: number = 0; i < count; i++) {arr.push(start + i)}return arr
}@Entry
@Component
export struct Index {@State data: number[] = makeRange(20, 1)            // 初始 1..20private feedScroller: Scroller = new Scroller()@State private loading: boolean = falseprivate loadMore(): void {if (this.loading) {return}this.loading = true// ArkTS 支持的计时调用(示例用途)setTimeout((): void => {const base: number = this.data.lengthconst more: number[] = makeRange(10, base + 1)  // 追加 10 条this.data = this.data.concat(more)this.loading = false}, 500)}build() {Column() {// 顶部操作区Row({ space: 8 }) {Button('回到顶部').onClick((): void => {this.feedScroller.scrollTo({ xOffset: 0, yOffset: 0 })})Button('刷新').onClick((): void => {this.data = makeRange(20, 1)this.feedScroller.scrollTo({ xOffset: 0, yOffset: 0 })})}.padding({ left: 12, right: 12, top: 12, bottom: 6 })// 滚动主体(事件挂在实际滚动容器 Scroll 上)Scroll(this.feedScroller) {Column({ space: 10 }) {ForEach(this.data,(n: number): void => {Column({ space: 6 }) {Text(`条目 #${n}`).fontSize(16).fontWeight(FontWeight.Medium)Text('示例内容').opacity(0.65)}.padding(12).backgroundColor('#FFFF00').borderRadius(10)},(n: number): string => `item-${n}` // key:显式返回 string)if (this.loading) {Row() {LoadingProgress().width(20).height(20)Text('正在加载更多…').margin({ left: 8 })}.justifyContent(FlexAlign.Center).padding(12)}}.padding({ left: 12, right: 12, bottom: 12 })}.onScroll((): void => {}).onScrollEdge((edge: Edge): void => {if (edge === Edge.Bottom) {this.loadMore()}})}.width('100%').height('100%')}
}

如果有父子两层都能滚的场景,不要让它们互相抢手。英文 API 建议的做法是:父容器用 onScrollFrameBegin 拦一下,根据实际情况用 scrollBy 分配滚动量;子容器把 edgeEffect 关成 None,避免边缘回弹把事件“吃掉”。文档里的例子写得很直白,可以直接套用。

五、工具面板三连:不一定非得上 Grid,Row 也能排得很干净

三块功能卡横着排,Row 加上 layoutWeight 就能均分横向空间;若要在大屏或折叠屏上更灵活地换列,再切换到 Grid 三列也来得及。

// Index.ets
@Entry
@Component
export struct Index {@Builder card(title: string, desc: string) {Column({ space: 6 }) {Text(title).fontSize(16).fontWeight(FontWeight.Medium)Text(desc).fontSize(12).opacity(0.65)Button('进入').margin({ top: 8 })}.padding(12).backgroundColor('#FFFF00').borderRadius(12)}build() {Column() {Text('工具面板三连').fontSize(18).fontWeight(FontWeight.Medium).padding(12)Row({ space: 12 }) {// 关键改动:给每块加一个真实组件容器(Column),再挂 layoutWeightColumn() { this.card('收藏', '管理常用') }.layoutWeight(1)Column() { this.card('下载', '离线可用') }.layoutWeight(1)Column() { this.card('分享', '一键分发') }.layoutWeight(1)}.padding(12)}.width('100%').height('100%')}
}

七、常见坑与排查顺序

第一类是“对齐好像不生效”。先确认 Row/Column 的方向判断是否正确;再看对齐写在了哪一层;最后排查子项是否用 align/alignSelf 覆盖了父级。

第二类是“触底没触发”。请确认事件挂在了真正滚动的容器上(List、Grid、Scroll 等),不要挂在外层 Column 或 Stack。

第三类是“嵌套滚动打架”。父层用 onScrollFrameBegin,配合 scrollBy 分配滚动量;子层把 edgeEffect 设为 None,这个组合是官方推荐方案。

第四类是“均分不均”。layoutWeight 是“按比例分剩余空间”,不是“写了就平均”。要参与分配的子项最好都明确给 weight,避免出现某个子项没吃到空间的错觉。

总结

容器这套工具更像“摆凳子”的手艺:先把方向摆对,再把对齐放稳,尺寸与约束收好边,叠层交给 Stack,规则卡片交给 Grid,信息流交给 Scroll/Scroller。写完页面,如果你能一句话回答“谁在滚、事件挂哪一层、对齐是按哪个轴”,这个页面基本就稳了。

剩下的动画、状态同步、导航拆分,都是在这个稳定的骨架上顺势加法。

http://www.dtcms.com/a/490362.html

相关文章:

  • 投资中国基金启动 1160 亿元试运行 确权为赎回变现核心前提,夯实封转开业务根基
  • SSL/TLS证书:保障网站安全的关键
  • Python SQLAlchemy:告别原生 SQL,用 ORM 优雅操作数据库
  • 鸿蒙Harmony实战开发教学(No.5)-TextInput组件基础到进阶篇
  • 【Qt】8.信号和槽_自定义信号和槽​
  • WPF——动画
  • 医院做网站怎么做wordpress还能用
  • YOLO系列目标检测算法全面解析
  • 目标检测全解析:从基础概念到深度学习实战技术
  • 基于深度学习计算机视觉的风格迁移技术原理与经典实现解析
  • Redis Key设计与Value存储
  • Pytest+requests进行接口自动化测试8.0(Allure进阶 + 文件上传接口 + 单接口多用例)
  • Kubernetes全景解读:从云原生基石到卓越实践
  • 【JUnit实战3_02】第二章:探索 JUnit 的核心功能(一)
  • 计算机视觉(opencv)——实时颜色检测
  • 宣传网站怎么做网站制作洋网络
  • 网站排名优化化快排优化网站服务器搭建的步骤
  • 本地用docling实现pdf转markdown操作笔记
  • iOS 26 APP 性能测试实战攻略:多工具组合辅助方案
  • 《Linux运维总结:基于X86_64+ARM64架构CPU使用docker-compose一键离线部署consul 1.21.5容器版集群》
  • wordpress 购物东莞网站优化方法有哪些
  • 接线盒工程量-图形识别高效运算
  • 后厨手套穿戴检测保障食品安全 手套佩戴检测 未戴手套检测 未佩戴手套实时报警 高危行业手套佩戴实时监控
  • 原位PL光谱测试教学(实操版)
  • 技术报告:高仿真虚构内容对主流大模型的现实感幻觉测试
  • 大模型提示词简介
  • R语言术语(2)
  • 广州网站建设推广谷歌官网首页
  • 【Python】基于Tkinter库实现文件夹拖拽与选择功能
  • Spring Boot 官方文档精解:构建与依赖管理