HarmonyOS 实战教程(四):首页布局实现 —— Swiper 轮播、Grid 网格与组件化 —— 以「柚兔自测量表」为例
一、首页整体结构
MeCharts 首页 HomeView 承载了所有量表入口,整体结构为:顶部导航栏 + 可滚动内容区(轮播图 + 分类网格)。
@Preview
@ComponentV2
export struct HomeView {
@Local globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel') || new GlobalInfoModel()
private pageContext: PageContext = AppStorage.get('pageContext') as PageContext;
@Local imgHeight: number = 190
@Local imgWidth: number = 264
@Local imgList: Resource[] = new Array(3).fill('').map((item: string, index) => {
return $r(`app.media.banner_0${index + 1}`)
})
private asdList: AbcModel[] = [
new AbcModel($r('app.media.ic_item_abc'), 'ABC量表', '(自闭症行为评定量表)', 1),
new AbcModel($r('app.media.ic_item_cars'), 'CARS量表', '(儿童期孤独症评定量表)', 2),
new AbcModel($r('app.media.ic_item_move'), '多动症量表', '(多动症诊断标准量表)', 3),
]
private careerList: AbcModel[] = [
new AbcModel($r('app.media.ic_item_mbti'), 'MBTI测试', '(MBTI职业性格测试完整版)', 1),
]
private emotionList: AbcModel[] = [
new AbcModel($r('app.media.ic_item_depress'), 'SDS量表', '(抑郁自评量表)', 1),
new AbcModel($r('app.media.ic_item_anxious'), 'SAS量表', '(焦虑症自评量表)', 2),
new AbcModel($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 数据模型:
@Observed
export class AbcModel {
img: Resource | undefined = undefined
title: string = ''
subTitle: string = ''
id: number = 0
score?: string = ''
type?: number = 0
constructor(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测试' } as ResultParams,
routerName: 'MbtiListPage',
}, true);
} else if (index === 1) {
this.pageContext.openPage({
param: { title: 'ABC量表' } as ResultParams,
routerName: 'AbcListPage',
}, true);
} else if (index === 2) {
this.pageContext.openPage({
param: { title: 'SAS量表', type: FormType.TYPE_SDS } as ResultParams,
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 布局展示三大分类的量表入口:
@Builder
buildAbc() {
// 分类标题
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 是全应用复用率最高的组件,支持标题、返回按钮和右侧自定义内容:
@ComponentV2
export struct TopBar {
private globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel') || new GlobalInfoModel();
@Param title: string = ''
@Param showBackButton: boolean = true
@Param bgColor: ResourceColor = $r('app.color.color_default')
@Param titleColor: ResourceColor = Color.White
@Param barHeight: number = 80
@Param statusBarHeight: number = this.globalInfoModel.statusBarHeight
@Event onBack?: () => void | boolean
@BuilderParam rightContent?: () => void
handleBack() {
if (this.onBack) {
const result = 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 组件与表单交互。
更多推荐


所有评论(0)