news 2026/10/23 22:03:56

【ArkUI 练中学】第4课:高级布局与列表性能优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【ArkUI 练中学】第4课:高级布局与列表性能优化

本节目标

· 掌握 Flex 弹性布局的核心属性 flexBasis、flexGrow、flexShrink 及 FlexWrap 换行布局的用法
· 掌握 Grid 网格布局的 columnsTemplate / rowsTemplate 行列设置方法,理解 fr 单位与 GridItem 的配合
· 掌握 Stack 层叠布局的 alignContent 对齐方式与 zIndex Z 序控制
· 掌握 List 列表布局的基础用法,理解 ListItem 与 ListItemGroup 的组织方式
· 理解长列表性能问题,掌握 LazyForEach 懒加载、cachedCount 预加载、@Reusable 组件复用三大优化手段
· 能根据实际场景选择合适的布局容器,并针对长列表进行性能优化

一、Flex 弹性布局

1.1 Flex 的定位与核心属性

Flex 布局提供灵活的组件排列和对齐能力,可以动态分配容器内的子组件空间,使元素根据可用空间自动扩展或收缩。适用于响应式 UI 布局、动态内容布局、复杂布局实现等场景,能解决传统布局在多设备适配困难、内容变化导致布局错位、复杂对齐需求难以实现等问题。

Flex 布局的核心在于三个属性,它们共同决定了子组件在主轴方向上的尺寸分配行为:

flexBasis:设置组件的基准尺寸,作为布局的初始参考值,优先级高于 width / height。默认值为 ‘auto’,表示组件在主轴方向上的基准尺寸为组件原本的大小。仅作为 Flex、Column、Row 和 DynamicLayout 容器的子组件时支持设置此属性。

flexGrow:定义组件在父容器有剩余空间时的扩展比例,剩余空间按各组件 flexGrow 比例分配。当父容器宽度大于所有子组件总宽度时,超出部分按比例分配给设置了 flexGrow 的子组件。

flexShrink:定义组件在父容器空间不足时的压缩比例,超出的尺寸按各组件 flexShrink 比例分摊。当父容器宽度小于所有子组件总宽度时,超出部分按比例从各子组件中扣除。

三者关系可以概括为:flexBasis 设定基准尺寸,flexGrow 控制扩展行为,flexShrink 控制压缩行为,可单独使用或组合使用。

Flex({direction:FlexDirection.Row}){Text('固定宽度').width(80).height(50).backgroundColor('#e1dede')Text('弹性扩展').flexGrow(1)// 占满剩余空间.height(50).backgroundColor('#c1cbac')Text('按比例扩展').flexGrow(2)// 占剩余空间的2份.flexBasis(60)// 基准尺寸60vp.height(50).backgroundColor('#d2cab3')}.width('100%')

1.2 FlexWrap 换行布局

Flex 布局支持通过 FlexWrap 属性控制子组件是否换行。FlexWrap.NoWrap 为默认值,不换行;FlexWrap.Wrap 允许换行,从左到右从上到下排列;FlexWrap.WrapReverse 允许换行,但从下到上排列。

FlexWrap 是实现流式布局的关键属性——当标签数量不固定时,无需提前计算一行放几个,设置 FlexWrap.Wrap 即可自动换行。

Flex({wrap:FlexWrap.Wrap,space:{main:LengthMetrics.px(10),cross:LengthMetrics.px(10)}}){ForEach(this.tags,(tag:string)=>{Text(tag).padding({left:12,right:12,top:6,bottom:6}).backgroundColor('#f0f0f0').borderRadius(16)})}.width('100%')

注意:当 Flex 组件参数 wrap 设置为 FlexWrap.Wrap 或 FlexWrap.WrapReverse 时,主轴长度 auto 的自适应布局会失效,默认撑满父容器。如果需要自适应内容宽度,可以根据内容是否超过一行动态切换换行规则。

二、Grid 网格布局

2.1 Grid 的定位与结构

网格布局由“行”和“列”分割的单元格组成,通过指定“项目”所在的单元格可以做出各种各样的布局。网格布局具有较强的页面均分能力和子组件占比控制能力,是一种重要的自适应布局,其使用场景有九宫格图片展示、日历、计算器等。

ArkUI 提供了 Grid 容器组件和子组件 GridItem 用于构建网格布局。Grid 用于设置网格布局相关参数,GridItem 定义子组件相关特征。Grid 的子组件必须是 GridItem 组件。

2.2 行列数量与占比设置

Grid 组件提供了 rowsTemplate 和 columnsTemplate 属性用于设置网格布局的行列数量与尺寸占比。属性值是一个由多个空格和 ‘数字+fr’ 间隔拼接的字符串,fr 的个数即网格布局的行或列数,fr 前面的数值大小用于计算该行或列在网格布局宽度上的占比,最终决定该行或列的宽度。

例如,columnsTemplate(‘1fr 1fr 1fr’) 表示将水平方向划分为 3 等列,每列宽度相等。columnsTemplate(‘1fr 2fr’) 表示两列,第二列宽度是第一列的 2 倍。

根据行列数量与占比属性的设置,Grid 可以分为三种布局情况:

· 行、列数量与占比同时设置:Grid 只展示固定行列数的元素,其余元素不展示,且 Grid 不可滚动(推荐使用该种布局方式)
· 只设置行或列数量与占比中的一个:元素按照设置的方向进行排布,超出的元素可通过滚动方式展示
· 行列数量与占比都不设置:元素在布局方向上排布,其行列数由布局方向、单个网格的宽高等多个属性共同决定

Grid(){ForEach(this.items,(item:string,index:number)=>{GridItem(){Text(item).fontSize(16).fontColor(Color.White)}.backgroundColor(this.colors[index%this.colors.length]).borderRadius(8).rowStart(index*2)// 从第 index*2 行开始.rowEnd(index*2+1)// 到第 index*2+1 行结束})}.columnsTemplate('1fr 1fr 1fr')// 3列等宽.rowsTemplate('1fr 1fr')// 2行等高.columnsGap(10)// 列间距.rowsGap(10)// 行间距.width('100%').height(300)

2.3 子组件横跨行列

GridItem 支持通过 rowStart / rowEnd / columnStart / columnEnd 属性设置子组件所占的行列数,实现合并单元格的效果。此外,还可以直接使用 rowStart(0) 配合 rowEnd 来跨行,或 columnStart / columnEnd 来跨列。

三、Stack 层叠布局

3.1 Stack 的定位与基本用法

层叠布局(StackLayout)用于在屏幕上预留一块区域来显示组件中的元素,提供元素可以重叠的布局。容器中的子元素依次入栈,后一个子元素覆盖前一个子元素,子元素可以叠加,也可以设置位置。层叠布局具有较强的页面层叠、位置定位能力,其使用场景有广告、卡片层叠效果等。

Stack 容器内的子元素默认进行居中堆叠,子元素被约束在 Stack 下进行自己的样式定义以及排列。

Stack({alignContent:Alignment.BottomEnd}){Image($r('app.media.background')).width('100%').height(200)Text('限时优惠').fontSize(14).fontColor(Color.White).backgroundColor(Color.Red).padding({left:8,right:8,top:4,bottom:4}).margin(10)}.width('100%').height(200)

3.2 对齐方式与 Z 序控制

Stack 组件通过 alignContent 参数实现位置的相对移动,支持九种对齐方式(TopStart、Top、TopEnd、Start、Center、End、BottomStart、Bottom、BottomEnd)。当 alignContent 和通用属性 align 同时设置时,后设置的生效。

Stack 容器中兄弟组件的显示层级关系可以通过 Z 序控制的 zIndex 属性改变。zIndex 值越大,显示层级越高,即 zIndex 值大的组件会覆盖在 zIndex 值小的组件上方。默认情况下,后入栈的子组件覆盖先入栈的子组件。在层叠布局中,如果后面子元素尺寸大于前面子元素尺寸,则前面子元素会被完全隐藏。

Stack({alignContent:Alignment.Center}){Text('底层').width('80%').height('80%').backgroundColor('#e1dede').zIndex(1)Text('上层').width('50%').height('50%').backgroundColor('#c1cbac').zIndex(2)// 层级更高,显示在“底层”上方}.width('100%').height(200)

四、List 列表布局

4.1 List 的定位与基本用法

List 是 ArkUI 中的列表容器组件,用于呈现连续、多行或多列的同类数据,例如图片和文本,支持垂直或水平滚动。List 适用于消息列表、商品列表、设置页面等场景,支持单列/多列布局、分组列表、吸顶吸底等能力。

List 的子组件应为 ListItem 或 ListItemGroup。ListItem 表示单个列表项,ListItemGroup 用于分组。

List({space:10}){ForEach(this.messages,(msg:Message)=>{ListItem(){Row(){Image(msg.avatar).width(40).height(40).borderRadius(20)Column(){Text(msg.name).fontSize(16).fontWeight(FontWeight.Bold)Text(msg.content).fontSize(14).fontColor(Color.Gray)}.alignItems(HorizontalAlign.Start).layoutWeight(1)}.width('100%').padding(12)}},(msg:Message)=>msg.id.toString())}.width('100%').padding(16).divider({strokeWidth:1,color:'#e0e0e0'})// 分割线

4.2 分组列表

当列表数据需要按类别分组时,可以使用 ListItemGroup 将同类数据组织在一起,并为其设置分组标题(header)。

List(){ForEach(this.groups,(group:Group)=>{ListItemGroup({header:this.groupHeader(group.title)}){ForEach(group.items,(item:string)=>{ListItem(){Text(item).fontSize(16).padding(12)}})}})}.sticky(StickyStyle.Header)// 分组标题吸顶

五、长列表性能优化

5.1 为什么需要性能优化?

在处理大量子组件时,如果使用普通的 ForEach 循环渲染,List 会一次性创建所有的子组件,在需要的时候布局和渲染屏幕范围内的节点。这意味着如果列表有上千条数据,即使屏幕上只显示十几条,框架也会创建全部上千个组件节点,导致启动速度慢、内存占用高。

为了解决这个问题,ArkUI 提供了三大优化手段:懒加载、缓存列表项、组件复用。

5.2 LazyForEach 懒加载

LazyForEach 从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了 LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。

与 ForEach 的核心区别在于:ForEach 一次性创建全部子组件,划出屏幕范围的节点不会下树销毁;而 LazyForEach 只会创建屏幕范围内的节点,划出屏幕范围的节点会下树销毁。

使用 LazyForEach 需要先实现 IDataSource 接口,这是懒加载的核心。

// 实现 IDataSource 接口classBasicDataSourceimplementsIDataSource{privatelisteners:DataChangeListener[]=[]privateoriginDataArray:string[]=[]totalCount():number{returnthis.originDataArray.length}getData(index:number):string{returnthis.originDataArray[index]}registerDataChangeListener(listener:DataChangeListener):void{if(this.listeners.indexOf(listener)<0){this.listeners.push(listener)}}unregisterDataChangeListener(listener:DataChangeListener):void{constpos=this.listeners.indexOf(listener)if(pos>=0){this.listeners.splice(pos,1)}}// 数据操作方法...pushData(data:string):void{this.originDataArray.push(data)this.listeners.forEach(listener=>{listener.onDataAdd(this.originDataArray.length-1)})}}
// 在 List 中使用 LazyForEachList(){LazyForEach(this.dataSource,(item:string,index:number)=>{ListItem(){Text(item).fontSize(16).padding(12)}},(item:string)=>item)// key 必须唯一且稳定}.cachedCount(5)// 预加载屏幕外上下各5项

5.3 cachedCount 预加载

List 支持通过 cachedCount 设置预加载的数量。默认会预加载显示区域上下各一屏子组件(最大预加载 16 行子组件)。预加载是指除了加载显示区域内可见的子组件外,还支持在空闲时隙提前加载部分显示区域外不可见的子组件,使用预加载可以减少滚动丢帧,提升流畅性。cachedCount 只在 LazyForEach 中生效,即 cachedCount 只能与 LazyForEach 一起使用。

cachedCount 的增加会增大 UI 的 CPU 和内存开销,使用时需要根据实际情况调整。一般建议:中等长度列表设置 10 左右,长列表快速滚动场景可适当增大到 20。

5.4 @Reusable 组件复用

为了降低反复创建销毁自定义组件带来的性能开销,可以使用 @Reusable 装饰 @Component 装饰的自定义组件,实现组件复用。标记为 @Reusable 的组件从组件树上被移除时,组件和其对应的 JSView 对象会被放入复用缓存中,再次需要时优先从缓存池中取出同结构节点并回调 aboutToReuse 更新数据,省去 build() 重建成本。

@Reusable@Componentstruct MessageItem{@Statemessage:Message=newMessage()aboutToReuse(params:Record<string,Object>):void{this.message=params.messageasMessage}build(){Row(){Image(this.message.avatar).width(40).height(40).borderRadius(20)Column(){Text(this.message.name).fontSize(16).fontWeight(FontWeight.Bold)Text(this.message.content).fontSize(14).fontColor(Color.Gray)}}.padding(12)}}

性能优化总结:LazyForEach 负责按需创建视口内组件节点,@Reusable 负责将滑出屏幕的组件缓存复用而非销毁重建,两者配合可以显著降低内存占用和滑动丢帧率。cachedCount 则在此基础上预加载屏幕外的列表项,让组件提前创建好,滑入时直接渲染。

六、多元化习题

习题 1(判断题)

题目:在 Flex 布局中,flexGrow 用于定义组件在父容器空间不足时的压缩比例。

答案:错误

解读:flexGrow 定义的是组件在父容器有剩余空间时的扩展比例,剩余空间按各组件 flexGrow 比例分配。flexShrink 才是用于定义父容器空间不足时的压缩比例。

习题 2(单选题)

题目:以下 Grid 的 columnsTemplate 设置中,表示将水平方向划分为 3 等列的是( )

A. columnsTemplate(‘3fr’)
B. columnsTemplate(‘1fr 1fr 1fr’)
C. columnsTemplate(‘1 1 1’)
D. columnsTemplate(‘3’)

答案:B

解读:columnsTemplate 属性值是由多个空格和 ‘数字+fr’ 间隔拼接的字符串,fr 的个数即列数,fr 前面的数值大小用于计算该列在网格布局宽度上的占比。‘1fr 1fr 1fr’ 表示 3 列等宽,每列占比相同。

习题 3(多选题)

题目:关于 Stack 层叠布局,以下说法正确的有(多选):

A. Stack 容器中的子元素依次入栈,后一个子元素覆盖前一个子元素
B. alignContent 支持九种对齐方式
C. zIndex 值越小,显示层级越高
D. Stack 适用于广告、卡片层叠效果等场景

答案:A、B、D

解读:Stack 容器中的子元素依次入栈,后入栈的组件覆盖先入栈的组件,选项 A 正确。alignContent 支持 TopStart、Top、TopEnd、Start、Center、End、BottomStart、Bottom、BottomEnd 共九种对齐方式,选项 B 正确。zIndex 值越大,显示层级越高,选项 C 的描述正好相反,错误。Stack 适用于广告、卡片层叠效果等场景,选项 D 正确。

习题 4(代码填空题)

题目:请补全以下代码,使 Grid 实现 4 列等宽的网格布局,每列占比相同。

Grid(){ForEach(this.items,(item:string)=>{GridItem(){Text(item).fontSize(16)}})}.______________('1fr 1fr 1fr 1fr').columnsGap(10).rowsGap(10)

答案:columnsTemplate

解读:columnsTemplate 用于设置 Grid 的列数和每列尺寸占比。‘1fr 1fr 1fr 1fr’ 表示 4 列等宽。rowsTemplate 用于设置行数和每行尺寸占比,但题目要求的是列设置。

习题 5(代码改错题)

题目:以下代码存在性能问题,请指出问题并修正。

List(){ForEach(this.largeDataSource,(item:string)=>{ListItem(){Text(item).fontSize(16).padding(12)}})}.width('100%')

答案:该代码使用 ForEach 渲染长列表,会一次性创建所有子组件,导致启动速度慢、内存占用高。应改为使用 LazyForEach 按需创建组件:

List(){LazyForEach(this.dataSource,(item:string)=>{ListItem(){Text(item).fontSize(16).padding(12)}},(item:string)=>item)}.cachedCount(10).width('100%')

解读:当 List 和 ForEach 结合时,会一次性创建所有的子组件,在需要的时候布局和渲染屏幕范围内的节点。对于长列表场景,应使用 LazyForEach 替代 ForEach,框架会根据滚动容器可视区域按需创建组件,组件滑出可视区域外时进行销毁回收,从而降低内存占用并提升启动速度。

习题 6(简答题)

题目:简述 LazyForEach、cachedCount、@Reusable 三者在长列表性能优化中的各自职责与配合关系。

答案:LazyForEach 负责按需迭代数据,框架根据滚动容器可视区域创建组件,组件滑出可视区域外时销毁回收,避免一次性创建大量组件节点。cachedCount 负责预加载,在空闲时隙提前创建屏幕外的组件,减少滚动时的丢帧,提升流畅性,只在配合 LazyForEach 时生效。@Reusable 负责组件复用,将滑出屏幕的组件实例放入复用缓存池而非销毁,再次需要时优先从缓存池中取出同结构节点并回调 aboutToReuse 更新数据,省去 build() 重建成本。三者配合可以显著降低内存占用和滑动丢帧率。

解读:LazyForEach 解决的是“一次性创建大量组件”的问题,cachedCount 解决的是“滚动时临时创建组件导致卡顿”的问题,@Reusable 解决的是“组件频繁创建销毁”的问题。三者各司其职,共同构成了 ArkUI 长列表性能优化的完整方案。

七、本节知识点总结

Flex 弹性布局
通过 flexBasis 设定基准尺寸、flexGrow 控制扩展、flexShrink 控制压缩,三者可单独或组合使用。FlexWrap.Wrap 实现自动换行流式布局。

Grid 网格布局
通过 columnsTemplate 和 rowsTemplate 设置行列数量与尺寸占比,fr 单位决定各行列占比。Grid 子组件必须是 GridItem。子组件可通过 rowStart / rowEnd / columnStart / columnEnd 横跨行列。

Stack 层叠布局
子元素依次入栈,后入栈者覆盖先入栈者。alignContent 支持九种对齐方式,zIndex 控制显示层级,值越大层级越高。适用于广告、卡片层叠、悬浮按钮等场景。

List 列表布局
适用于连续多行同类数据的展示,支持垂直/水平滚动、分组列表、吸顶吸底。子组件为 ListItem 或 ListItemGroup。

长列表性能优化
LazyForEach 实现按需创建和销毁,需实现 IDataSource 接口;cachedCount 预加载屏幕外列表项,仅在 LazyForEach 中生效;@Reusable 实现组件复用,配合 aboutToReuse 回调更新数据。三者配合可显著降低内存占用和滑动丢帧率。

布局选型建议
需要一维线性排列时使用 Row/Column;需要弹性伸缩和换行时使用 Flex;需要二维网格时使用 Grid;需要元素重叠时使用 Stack;需要长列表滚动时使用 List 并配合 LazyForEach 优化。

下节预告
第5课将深入讲解 ArkUI 中的路由导航与页面跳转,包括 router 路由的基本用法、Navigation 组件的页面栈管理、参数传递与返回值处理,以及页面转场动画的实现方式。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 6:49:42

三维模型制作01——第一周(10.9)

三维模型制作010 参考资料和备课指南1 今日整体课程安排2 认识3dsmax工作界面2.1 标题栏2.1.1 保存方式2.1.2 讲解键盘2.2 菜单栏2.3 主工具栏2.4 视图区2.5 命令面板区2.6 视图控制区2.7 状态与提示信息栏2.8 动画控制区3 将UI界面复原4 细节注意4.1 单位设置4.2 viewcube&…

作者头像 李华
网站建设 2026/10/18 0:12:27

单臂路由实验详解:VLAN间通信的配置原理与排错实战

单臂路由这个实验&#xff0c;算是VLAN间通信里最经典也最容易让人绕晕的一个了。很多人在模拟器里照着教程敲命令&#xff0c;明明每一步都做对了&#xff0c;两个PC就是ping不通。我刚开始学的时候也卡在这上面好几天&#xff0c;后来发现坑几乎都出在交换机接口的角色划分和…

作者头像 李华
网站建设 2026/10/22 23:18:50

基于Java Web的人才招聘系统设计与实现文档撰写指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 23:34:55

分治-快速排序

1.颜色划分 75. 颜色分类 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/sort-colors/description/ 分治就是分而治之&#xff0c;把一个非常大的问题&#xff0c;转化为很多相似的子问题。子问题基础上继续划分&#xff0c;直到划分到这个子问题可…

作者头像 李华
网站建设 2026/10/22 9:27:29

Lojban工具链组合实践:从gerna解析到语义标注的livla数据流

简介&#xff1a;livla 是一套面向 Lojban 语言学习者与开发者的工具组合&#xff0c;围绕解析器、词典软件与检索界面展开&#xff0c;适合希望深入理解这门人工语言语法结构、并动手搭建本地服务的中高级用户。资源包共收录 2000 个文件&#xff0c;整体约 44.38MB&#xff0…

作者头像 李华