HarmonyOS 应用开发《掌上英语》第18篇:Grid 布局实战——从功能 5 宫格到练习 2×2 模式

📅 2026/7/17 0:02:48 👤 编程新知 🏷️ 技术资讯
HarmonyOS 应用开发《掌上英语》第18篇:Grid 布局实战——从功能 5 宫格到练习 2×2 模式 Grid 布局实战——从功能 5 宫格到练习 2×2 模式引言Grid 布局是 ArkTS 中最强大的布局容器之一它通过行列模板系统实现灵活的多列多行布局。在本英语学习 App 中Grid 布局贯穿了首页的多个核心模块功能栏 5 宫格、练习模式 2×2 卡片、以及响应式学习进度区。本文将通过这些实战案例深入剖析 Grid 的配置技巧和最佳实践。一、Grid 布局核心概念ArkTS 的 Grid 组件采用模板驱动的方式定义行列结构核心属性包括columnsTemplate定义列模板如1fr 1fr 1fr表示三等分rowsTemplate定义行模板如1fr 1fr表示二等分不指定则自动换行columnsGap/rowsGap列间距 / 行间距子组件通过GridItem包裹自动按顺序填充fr 单位的理解frfraction是 Grid 布局中的弹性单位表示可用空间的比例分配1fr 1fr 1fr 1fr 1fr → 等分为 5 列每列占 1/5 1fr 2fr → 第一列占 1/3第二列占 2/3 100vp 1fr 1fr → 第一列固定 100vp后两列平分剩余空间二、功能栏 5 宫格——固定列数 Grid2.1 业务需求首页需要一个展示 5 个核心功能的快捷入口搜单词、易错词、生词本、笔记、学习报告。采用固定 5 列布局每个图标图标大小一致。2.2 代码实现// 数据定义Localfunctions:CourseCoreBar[][newCourseCoreBar($r(app.media.ic_practice_camera),搜单词),newCourseCoreBar($r(app.media.ic__practice_wrong_question),易错词),newCourseCoreBar($r(app.media.ic_practice_star),生词本),newCourseCoreBar($r(app.media.ic_practice_notes),笔记),newCourseCoreBar($r(app.media.ic_chart_report),学习报告)];// 功能栏 BuilderBuilderfunctionBarBuilder(){Grid(){ForEach(this.functions,(item:CourseCoreBar){GridItem(){CourseCoreBuilder(item);};},(item:CourseCoreBar,index:number)item.descriptionindex);}.columnsTemplate(1fr 1fr 1fr 1fr 1fr)// 固定 5 列.columnsGap($r(app.float.vp_4)).width(100%).padding($r(app.float.vp_8)).borderRadius($r(app.float.vp_16)).backgroundColor($r(sys.color.background_primary));}// 全局 Builder每个功能项BuilderexportfunctionCourseCoreBuilder(mainFunctionItem:CourseCoreBar){Column(){Image(mainFunctionItem.showIcon).height($r(app.float.vp_24)).width($r(app.float.vp_24)).margin({bottom:$r(app.float.vp_2)});Text(mainFunctionItem.description).fontSize($r(sys.float.Caption_M));}.width(100%).height(72).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).stateStyles({pressed:{.scale({x:0.97,y:0.97})}}).onClick((){// 路由跳转});}2.3 关键设计要点columnsTemplate(1fr 1fr 1fr 1fr 1fr)五个1fr表示 5 等分每列宽度相同不指定rowsTemplateGrid 会自动根据子项数量换行5 个刚好一行GridItem宽度自适应默认填满所在列的宽度columnsGap设置 4vp 的列间距避免图标过于紧凑key生成使用item.description index确保每个 GridItem 有唯一标识三、练习模式 2×2——固定列数 LazyForEach3.1 业务需求练习模式展示 4 个大卡片单词记忆、听力训练、阅读训练、语法练习。采用 2×2 的宫格布局使用LazyForEach实现数据懒加载。3.2 数据模型exportclassPracticeView{imageUri:ResourceStr;name:string;describe:string;constructor(imageUri:ResourceStr,name:string,describe:string){this.imageUriimageUri;this.namename;this.describedescribe;}}exportconstPRACTICE_LIST_DATA:PracticeView[][newPracticeView($r(app.media.ic_sequence),单词记忆,每日单词打卡),newPracticeView($r(app.media.ic_practice_simulations),听力训练,沉浸式听力练习),newPracticeView($r(app.media.ic_practice_test_paper),阅读训练,英文原著阅读),newPracticeView($r(app.media.ic_wrong_question),语法练习,语法专项突破),];3.3 代码实现BuilderpracticeModeBuilder(){Text(练习模式).fontSize($r(sys.float.Subtitle_L)).fontWeight(FontWeight.Medium).fontColor($r(sys.color.font_primary)).width(100%).textAlign(TextAlign.Start).margin({bottom:$r(app.float.vp_12)});Grid(){LazyForEach(this.dataSource,(practiceItem:PracticeView){GridItem(){ReusableFlowItem({item:practiceItem});}.onClick((){// 路由跳转逻辑if(practiceItem.name单词记忆){RouterModule.push({url:RouterMap.ANSWER_QUESTIONS_PAGE,param:1});}elseif(practiceItem.name听力训练){RouterModule.push({url:RouterMap.Mock_PAGE,param:1});}elseif(practiceItem.name阅读训练){RouterModule.push({url:RouterMap.Mock_PAGE,param:2});}else{RouterModule.push({url:RouterMap.ANSWER_QUESTIONS_PAGE,param:5});}});},(practiceItem:PracticeView,index:number)practiceItem.nameindex);}.padding($r(app.float.vp_12)).backgroundColor($r(sys.color.background_primary)).columnsTemplate(1fr 1fr)// 固定 2 列.rowsGap($r(app.float.vp_12)).columnsGap($r(app.float.vp_12)).borderRadius($r(app.float.vp_16));}3.4 ReusableFlowItem 子组件每个 GridItem 内部使用ReusableFlowItem自定义组件来渲染卡片内容ComponentV2struct ReusableFlowItem{Paramitem:PracticeViewnewPracticeView($r(app.media.ic_home),顺序练习,1、3256);build(){Row(){Column(){Text(this.item.name).textOverflow({overflow:TextOverflow.Ellipsis}).maxLines(1).fontWeight(FontWeight.Bold).fontSize($r(sys.float.Body_S)).fontColor($r(sys.color.font_primary));Text(this.item.describe).textOverflow({overflow:TextOverflow.Ellipsis}).maxLines(1).fontSize($r(sys.float.Caption_M)).fontWeight(FontWeight.Regular).fontColor($r(sys.color.font_secondary)).margin({top:$r(app.float.vp_2)});}.justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Start).layoutWeight(1);Image(this.item.imageUri).interpolation(ImageInterpolation.High).objectFit(ImageFit.Fill).width(40).height(40).clip(true).margin({left:$r(app.float.vp_4),right:$r(app.float.vp_4)});}.stateStyles({pressed:{.scale({x:0.97,y:0.97})}}).backgroundColor($r(sys.color.background_secondary)).justifyContent(FlexAlign.SpaceBetween).height(100).width(100%).padding({left:$r(app.float.vp_12),right:$r(app.float.vp_16)}).borderRadius($r(app.float.vp_12));}}3.5 LazyForEach vs ForEach特性ForEachLazyForEach数据量范围小规模 50 项大规模 50 项渲染策略一次性渲染所有项按需渲染可见项数据源要求数组实现 IDataSource 接口性能小数据量时更简单大数据量时更高效本项目中练习模式虽然只有 4 项但使用LazyForEachIDataSource的架构方便后续扩展更多练习模式。四、学习进度 Grid——响应式列数控制4.1 业务需求学习进度区域需要根据设备宽度自适应列数手机单列、平板双列、桌面三列。4.2 响应式 Grid 实现BuildersubjectPracticeBuilder(){Text(学习进度).fontSize(18).fontWeight(FontWeight.Bold).fontColor($r(sys.color.font_primary)).width(100%).textAlign(TextAlign.Start).margin($r(app.float.vp_12));if(!this.logoUser.isLogin){// 未登录状态// ...}else{Grid(){ForEach(this.coursePractice,(item:CourseQuestions){GridItem(){CoursePracticeBuilder(item);};},(item:CourseQuestions,index:number)item.subjectNameindex);}.columnsTemplate(1fr .repeat(newBreakpointTypenumber({sm:1,// 手机单列md:2,// 平板双列lg:3,// 桌面三列xl:3// 大屏三列}).getValue(this.homeVM.breakPointModel.currentBreakpoint))).columnsGap($r(app.float.vp_6)).rowsGap($r(app.float.vp_6)).borderRadius($r(app.float.vp_16));}}4.3 动态列模板构建技巧核心技巧在于1fr .repeat(n)的字符串构建模式// 假设当前断点为 md平板constcolumnsnewBreakpointTypenumber({sm:1,md:2,lg:3,xl:3}).getValue(this.homeVM.breakPointModel.currentBreakpoint);// columns 2consttemplate1fr .repeat(columns);// template 1fr 1fr → 双列布局这种方式比手写条件判断更简洁// ❌ 不推荐条件判断冗长.columnsTemplate(this.breakpointsm?1fr:this.breakpointmd?1fr 1fr:1fr 1fr 1fr)// ✅ 推荐BreakpointType 封装.columnsTemplate(1fr .repeat(breakpointType.getValue(currentBreakpoint)))五、Grid 布局对比总结场景列数策略rowsTemplate数据遍历间距功能栏 5 宫格固定 5 列不指定自动换行ForEach4vp练习模式 2×2固定 2 列不指定自动换行LazyForEach12vp学习进度响应式 1-3 列不指定自动换行ForEach6vp六、最佳实践6.1 GridItem 高度一致当不指定rowsTemplate时Grid 会自动让同一行的 GridItem 高度相同。如果希望每个卡片高度不同应显式设置rowsTemplate。6.2 卡片点击反馈使用stateStyles添加按压缩放效果.stateStyles({pressed:{.scale({x:0.97,y:0.97})}})6.3 唯一 key 的重要性Grid 中每个 GridItem 必须有唯一的 key用于框架的差异更新// ❌ 不推荐使用固定值(item)item// ✅ 推荐使用唯一标识(item:CourseCoreBar,index:number)item.descriptionindex总结Grid 布局是 ArkTS 中最灵活的多列布局方案。通过本文的三个实战案例我们掌握了固定列数布局功能栏 5 宫格和练习模式 2×2适合已知数据量的场景响应式列数布局结合BreakpointType实现不同断点下的自适应数据遍历选择ForEach适合小数据量LazyForEach适合大数据量和懒加载GridItem 封装将卡片内容抽为独立组件提升可维护性掌握这些 Grid 布局技巧就能应对绝大多数多列 UI 场景。