HarmonyOS 实战教程(四):首页布局实现 —— Swiper 轮播、Grid 网格与组件化 —— 以「柚兔自测量表」为例

一、首页整体结构

MeCharts 首页HomeView承载了所有量表入口,整体结构为:顶部导航栏 + 可滚动内容区(轮播图 + 分类网格)。

@Preview@ComponentV2exportstruct HomeView{@LocalglobalInfoModel:GlobalInfoModel=AppStorage.get('GlobalInfoModel')||newGlobalInfoModel()privatepageContext:PageContext=AppStorage.get('pageContext')asPageContext;@LocalimgHeight:number=190@LocalimgWidth:number=264@LocalimgList:Resource[]=newArray(3).fill('').map((item:string,index)=>{return$r(`app.media.banner_0${index+1}`)})privateasdList:AbcModel[]=[newAbcModel($r('app.media.ic_item_abc'),'ABC量表','(自闭症行为评定量表)',1),newAbcModel($r('app.media.ic_item_cars'),'CARS量表','(儿童期孤独症评定量表)',2),newAbcModel($r('app.media.ic_item_move'),'多动症量表','(多动症诊断标准量表)',3),]privatecareerList:AbcModel[]=[newAbcModel($r('app.media.ic_item_mbti'),'MBTI测试','(MBTI职业性格测试完整版)',1),]privateemotionList:AbcModel[]=[newAbcModel($r('app.media.ic_item_depress'),'SDS量表','(抑郁自评量表)',1),newAbcModel($r('app.media.ic_item_anxious'),'SAS量表','(焦虑症自评量表)',2),newAbcModel($r('app.media.ic_item_relationship'),'ECR量表','(亲密关系经历量表)',3),]build(){Column(){this.buildTopBar();this.buildContentView();}.height('100%').width('100%').backgroundColor($r('app.color.color_background'))}}

1.1 数据模型设计

所有量表入口使用统一的AbcModel数据模型:

@ObservedexportclassAbcModel{img:Resource|undefined=undefinedtitle:string=''subTitle:string=''id:number=0score?:string=''type?:number=0constructor(img:Resource,title?:string,subTitle?:string,id?:number,score?:string,type?:number){// 构造函数赋值...}}

@Observed装饰器使该类的实例可被 ArkUI 框架观测,当属性变化时自动触发 UI 刷新。这在历史记录页面中更新分数显示时非常关键。

二、Swiper 轮播组件

2.1 基本使用

首页顶部使用UISwiper(AGConnect 提供的增强轮播组件)展示推荐量表:

UISwiper({imgWidth:this.imgWidth,imgHeight:this.imgHeight,imgList:this.imgList,isLoop:true,isCovered:false,onImageClick:(index:number)=>{if(index===0){this.pageContext.openPage({param:{title:'MBTI测试'}asResultParams,routerName:'MbtiListPage',},true);}elseif(index===1){this.pageContext.openPage({param:{title:'ABC量表'}asResultParams,routerName:'AbcListPage',},true);}elseif(index===2){this.pageContext.openPage({param:{title:'SAS量表',type:FormType.TYPE_SDS}asResultParams,routerName:'MoveListPage',},true);}}})

轮播图的点击事件通过onImageClick回调处理,根据索引跳转到不同量表页面。

2.2 原生 Swiper 的使用方式

如果使用原生 Swiper 组件,基础用法如下:

Swiper(){ForEach(this.imgList,(img:Resource)=>{Image(img).width(this.imgWidth).height(this.imgHeight).borderRadius(12).onClick(()=>{// 点击跳转逻辑})})}.autoPlay(true).interval(3000).indicator(true).loop(true).cachedCount(2)

原生 Swiper 的关键属性:

属性说明
autoPlay是否自动播放
interval自动播放间隔(ms)
indicator是否显示导航指示器
loop是否循环播放
cachedCount预缓存页面数

三、Grid 网格布局

3.1 分类网格实现

首页使用 Grid 布局展示三大分类的量表入口:

@BuilderbuildAbc(){// 分类标题Row({space:10}){Text().width(3).height(15).backgroundColor($r('app.color.color_default'));Text('自闭症相关').fontWeight(FontWeight.Bold).fontSize(16)}.margin({left:12})// 量表网格Grid(){ForEach(this.asdList,(asd:AbcModel,index)=>{GridItem(){Column({space:8}){Row({space:8}){Image(asd.img).width(24);Text(asd.title).fontWeight(FontWeight.Medium);};Text(asd.subTitle).fontSize(12);}.alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center).borderRadius(15).width('100%').height('100%').backgroundColor($r('app.color.color_card'))}.onClick(()=>{// 跳转到对应量表页面})})}.padding(12).columnsTemplate('1fr 1fr')// 两列等宽.rowsTemplate('1fr 1fr')// 两行等高.columnsGap(10).rowsGap(10).width('100%').height(160)}

3.2 Grid 核心属性

columnsTemplate / rowsTemplate是 Grid 最核心的属性,定义了网格的行列结构:

.columnsTemplate('1fr 1fr')// 两列,每列等宽.rowsTemplate('1fr 1fr')// 两行,每行等高

fr是分数单位,1fr 1fr表示两列各占 1/2。更复杂的布局如:

.columnsTemplate('1fr 2fr 1fr')// 三列,中间列是两侧的两倍宽.rowsTemplate('auto auto')// 两行,高度由内容决定

columnsGap / rowsGap控制行列间距:

.columnsGap(10)// 列间距 10vp.rowsGap(10)// 行间距 10vp

3.3 不同分类的布局差异

MeCharts 根据量表数量灵活调整布局:

分类量表数量布局方式高度
自闭症相关3个2列×2行160
职业性格相关1个2列×1行80
情绪相关3个2列×2行160

职业性格分类只有一个量表,使用单行布局;其他分类使用 2×2 网格,空位自然留白。

四、TopBar 通用顶部导航栏

4.1 组件设计

TopBar 是全应用复用率最高的组件,支持标题、返回按钮和右侧自定义内容:

@ComponentV2exportstruct TopBar{privateglobalInfoModel:GlobalInfoModel=AppStorage.get('GlobalInfoModel')||newGlobalInfoModel();@Paramtitle:string=''@ParamshowBackButton:boolean=true@ParambgColor:ResourceColor=$r('app.color.color_default')@ParamtitleColor:ResourceColor=Color.White@ParambarHeight:number=80@ParamstatusBarHeight:number=this.globalInfoModel.statusBarHeight@EventonBack?:()=>void|boolean@BuilderParamrightContent?:()=>voidhandleBack(){if(this.onBack){constresult=this.onBack()if(result===false){return// 返回 false 可阻止默认行为}}}build(){RelativeContainer(){if(this.showBackButton){Image($r('app.media.ic_back')).width(20).height(20).id('backBtn').alignRules({left:{anchor:'__container__',align:HorizontalAlign.Start},center:{anchor:'__container__',align:VerticalAlign.Bottom}}).onClick(()=>{this.handleBack()})}Text(this.title).fontSize(18).fontWeight(FontWeight.Bold).fontColor(this.titleColor).id('title').alignRules({center:{anchor:'__container__',align:VerticalAlign.Bottom},middle:{anchor:'__container__',align:HorizontalAlign.Center}})if(this.rightContent){Row(){this.rightContent()}.id('rightContent').alignRules({right:{anchor:'__container__',align:HorizontalAlign.End},center:{anchor:'__container__',align:VerticalAlign.Bottom}})}}.width('100%').height(this.barHeight).backgroundColor(this.bgColor).padding({top:this.statusBarHeight,bottom:20,left:12,right:12})}}

4.2 RelativeContainer 相对布局

TopBar 使用RelativeContainer实现左右中三栏布局,这是 HarmonyOS 提供的相对定位容器:

  • 返回按钮:锚定容器左侧 + 垂直居中
  • 标题:锚定容器水平居中 + 垂直居中
  • 右侧内容:锚定容器右侧 + 垂直居中

相比 Flex 布局,RelativeContainer 在处理"左右对齐 + 中间居中"的场景时更简洁。

4.3 使用示例

首页使用 TopBar(无返回按钮 + 自定义右侧):

TopBar({title:'自测量表',showBackButton:false,rightContent:()=>{this.buildRightContent()},})

子页面使用 TopBar(有返回按钮):

TopBar({title:this.title,onBack:()=>{this.pageContext.popPage(true)}})

五、@ComponentV2 与 @Component 的选择

MeCharts 中两种组件声明方式并存:

  • @ComponentV2:新版本装饰器,支持@Param@Event@Local@Monitor等,类型安全更好
  • @Component:经典版本装饰器,支持@Prop@Link@State
特性@Component@ComponentV2
状态装饰器@State, @Prop, @Link@Local, @Param, @Event
状态观测基于代理基于拦截器,性能更优
参数传递构造函数传入@Param 声明式
事件回调箭头函数@Event 装饰器
监听变化@Watch@Monitor

新项目推荐优先使用@ComponentV2

六、小结

本篇讲解了首页的 Swiper 轮播、Grid 网格布局和 TopBar 通用组件的设计实现。通过组件化封装和统一数据模型,首页的代码结构清晰、复用性强。下一篇将深入量表答题页面的实现,讲解自定义 Radio 组件与表单交互。