鸿蒙原生应用实战(二):首页与诗词库页面开发——多元布局与交互实现
鸿蒙原生应用实战(二):首页与诗词库页面开发——多元布局与交互实现
前言
在上一章中,我们完成了项目初始化和架构设计。本章将正式进入编码阶段,集中开发应用的两个核心页面:
- 首页(Index.ets)—— 信息聚合入口
- 诗词库(PoemListPage.ets)—— 搜索与筛选
这两个页面涉及了大量 ArkTS 布局技巧、组件复用和数据绑定模式,是鸿蒙开发的核心实战内容。
一、首页开发(Index.ets)
1.1 页面布局总览
首页从上到下分为五个区域:
┌──────────────────────┐ │ 标题栏 + 用户头像 │ ← Row + Column 组合 ├──────────────────────┤ │ 每日诗词推荐卡片 │ ← 渐变背景 + 引用样式 ├──────────────────────┤ │ 6 大分类入口 (Grid) │ ← 2 行 3 列网格 ├──────────────────────┤ │ 热门排行列表 │ ← 带序号和点赞数 ├──────────────────────┤ │ 为你推荐列表 │ ← 与排行相同结构 ├──────────────────────┤ │ 底部导航栏 │ ← 4 个 Tab └──────────────────────┘1.2 数据结构定义
在 ArkTS 的严格模式下,所有对象字面量必须有显式类型声明:
// 诗词条目接口interfacePoemItem{id:number;title:string;author:string;dynasty:string;content:string[];// 诗句数组type:string;// 五言绝句 / 词 / 乐府 ...likes:number;}// 每日推荐数据constdailyPoem:DailyPoem={title:'定风波',author:'苏轼',dynasty:'宋',excerpt:'竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。'};// 热门排行数据consttopPoems:PoemItem[]=[{id:1,title:'静夜思',author:'李白',dynasty:'唐',content:['床前明月光','疑是地上霜','举头望明月','低头思故乡'],type:'五言绝句',likes:9852},// ... 更多诗词];1.3 渐变背景卡片(每日诗词)
首页最醒目的"每日一首"卡片使用了渐变背景效果。在 ArkTS 中,可以通过background属性实现:
Column(){Text('每日一首').fontSize(12).fontColor('rgba(255,255,255,0.7)').width('100%')Text(dailyPoem.title).fontSize(22).fontWeight(FontWeight.Bold).fontColor(Color.White).width('100%').padding({top:8})Text('—— '+dailyPoem.dynasty+'·'+dailyPoem.author).fontSize(13).fontColor('rgba(255,255,255,0.8)').width('100%')// 居中展示经典名句Text(dailyPoem.excerpt).fontSize(17).fontColor(Color.White).lineHeight(28).textAlign(TextAlign.Center).padding({top:16,bottom:8})}.width('100%').padding(20)// 渐变色背景——紫色系渐变.background('linear-gradient(135deg, #667eea, #764ba2)').borderRadius(16)技巧:
linear-gradient是 ArkTS 支持的背景渐变语法,适合做卡片头部装饰。
1.4 网格布局(6 大分类)
使用Grid组件实现 2 行 3 列的诗词分类入口:
Grid(){ForEach(categories,(cat:string)=>{GridItem(){this.createCategoryCard(cat)}},(cat:string)=>cat)}.columnsTemplate('1fr 1fr 1fr')// 3列等宽.rowsTemplate('1fr 1fr')// 2行.rowsGap(12).columnsGap(12).width('100%')每个分类卡片包含 emoji 图标和文字标签,点击后跳转到诗词库页面并自动筛选该分类。
1.5 @Builder 组件复用
在 ArkTS 中,@Builder是组件复用的核心机制。需要注意一个关键限制:@Builder 内不能声明变量:
// ❌ 错误——@Builder 内不能有 const/interface@BuildercreateCategoryCard(name:string){consticons:Record<string,string>={...};// 编译报错!// ...}// ✅ 正确——将数据提取为普通方法getCatIcon(name:string):string{consticons:Record<string,string>={'唐诗三百':'📜','宋词精选':'🌸','元曲':'🎭','古诗十九首':'📖','乐府诗集':'🎵','诗经':'📗'};returnicons[name]||'📜';}@BuildercreateCategoryCard(name:string){Column(){Text(this.getCatIcon(name)).fontSize(28)Text(name).fontSize(12).fontColor($r('app.color.text_primary')).margin({top:8}).fontWeight(FontWeight.Medium)}// ...}1.6 图片圆形容器
首页右上角的用户头像使用了Circle组件 +.overlay()的组合:
Circle().width(40).height(40).fill($r('app.color.accent_purple')).overlay(this.avatarText())overlay是一个@Builder 方法:
@BuilderavatarText(){Text('诗').fontColor(Color.White).fontSize(18).fontWeight(FontWeight.Bold)}注意:在早期版本的 ArkTS 中,
.overlay()不能直接接受Text()组件,必须通过@Builder方法包装。
二、诗词库页面开发(PoemListPage.ets)
2.1 交互功能概览
诗词库页面是用户浏览诗词的核心入口,包含三个维度:
| 交互维度 | 实现方式 | 数据来源 |
|---|---|---|
| 搜索 | TextInput组件 | 用户输入,实时过滤 |
| 朝代筛选 | 标签按钮 Row | 6 个选项(全部/先秦/唐/五代/宋/元) |
| 类型筛选 | 标签按钮 Row | 5 个选项(全部/五绝/七律/词/乐府) |
2.2 数据过滤逻辑
之前我们使用了get filteredPoems()访问器,但在运行时发现其在模板中会返回undefined:
// ❌ 不可行——get 访问器在模板返回 undefinedgetfilteredPoems():PoemItem[]{// ...过滤逻辑returnresult;// 运行时始终 undefined!}正确做法:使用@State+@Watch组合:
@State@Watch('onFilterChange')searchText:string='';@State@Watch('onFilterChange')activeDynasty:string='all';@State@Watch('onFilterChange')activeType:string='all';@StatefilteredList:PoemItem[]=allPoems;// 存储过滤结果onFilterChange():void{letresult:PoemItem[]=allPoems;if(this.searchText.length>0){constkeyword:string=this.searchText.toLowerCase();result=result.filter((p:PoemItem)=>p.title.includes(keyword)||p.author.includes(keyword));}if(this.activeDynasty!=='all'){result=result.filter((p:PoemItem)=>p.dynasty===this.activeDynasty);}if(this.activeType!=='all'){result=result.filter((p:PoemItem)=>p.type===this.activeType);}this.filteredList=result;// 更新状态触发重新渲染}工作原理:当searchText、activeDynasty或activeType任一状态变化时,@Watch('onFilterChange')自动触发onFilterChange()方法,更新filteredList,UI 随之刷新。
2.3 搜索框实现
TextInput是鸿蒙中的文本输入组件:
Row(){Text('🔍').fontSize(16).margin({left:12})TextInput({placeholder:'搜索诗词名称或作者...',text:this.searchText}).layoutWeight(1).backgroundColor(Color.Transparent).fontSize(14).placeholderColor($r('app.color.text_secondary')).onChange((val:string)=>{this.searchText=val;})// 搜索框不为空时显示清除按钮if(this.searchText.length>0){Text('✕').fontSize(16).fontColor($r('app.color.text_secondary')).margin({right:12}).onClick(()=>{this.searchText='';})}}.width('100%').height(44).backgroundColor($r('app.color.bg_card')).borderRadius(22)// 圆角搜索框2.4 筛选标签
筛选标签的样式逻辑:选中的标签用主题色填充,未选中的用白色:
Text(filter.label).fontSize(13).fontColor(filter.name===this.activeDynasty?Color.White:$r('app.color.text_secondary')).padding({left:14,right:14,top:6,bottom:6}).backgroundColor(filter.name===this.activeDynasty?$r('app.color.accent_purple'):$r('app.color.bg_card')).borderRadius(16).onClick(()=>{this.activeDynasty=filter.name;})2.5 结果计数与空状态
// 结果计数Row(){Text('共 '+this.filteredList.length+' 首').fontSize(12).fontColor($r('app.color.text_secondary'))Blank()}// 空状态展示if(this.filteredList.length===0){Column(){Text('📖').fontSize(48)Text('没有找到相关诗词').fontSize(16).fontColor($r('app.color.text_secondary')).margin({top:12})}.width('100%').height(200).alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)}2.6 卡片列表
每条诗词卡片显示:序号、标题、类型标签、朝代·作者、诗文节选、点赞数:
@BuildercreatePoemCard(poem:PoemItem){Row(){// 序号Text(poem.id.toString()).fontSize(22).fontWeight(FontWeight.Bold).fontColor($r('app.color.accent_purple')).opacity(0.3)Column(){Row(){Text(poem.title).fontSize(17).fontWeight(FontWeight.Bold)Text(poem.type).fontSize(10).fontColor($r('app.color.accent_purple')).padding({left:6,right:6,top:2,bottom:2}).backgroundColor($r('app.color.accent_purple')+'15').borderRadius(4)}Text(poem.dynasty+' · '+poem.author).fontSize(13).fontColor($r('app.color.text_secondary'))// 诗文节选(最多两行)Text(poem.content[0]+(poem.content.length>1?','+poem.content[1]:'')).fontSize(14).fontColor($r('app.color.text_secondary')).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})Text('❤ '+poem.likes.toString()).fontSize(12).fontColor($r('app.color.accent_red'))}}.width('100%').padding(14).backgroundColor($r('app.color.bg_card')).borderRadius(12).onClick(()=>{router.pushUrl({url:'pages/PoemDetailPage',params:{poemId:poem.id}});})}三、跨页面参数传递
3.1 从作者页跳转到诗词库并搜索
AuthorPage点击诗人卡片后,会跳转到诗词库并自动填入作者名进行搜索:
// AuthorPage.ets.onClick(()=>{router.pushUrl({url:'pages/PoemListPage',params:{searchAuthor:author.name}});})// PoemListPage.ets — 接收参数aboutToAppear():void{constparams=router.getParams()asRecord<string,Object>;if(params&¶ms['searchAuthor']!==undefined){this.searchText=params['searchAuthor']asstring;// @Watch 会自动触发 onFilterChange,更新 filteredList}}四、@Builder 中的 if 条件
在 ArkTS 中,if条件语句可以直接在build()和@Builder中使用:
@BuildercreatePoemCard(poem:PoemItem){Row(){if(this.editMode){Circle()// 编辑模式下的选择框.width(22).height(22).stroke($r('app.color.accent_purple')).strokeWidth(2).fill(Color.Transparent)}// ... 其余内容}}但需要注意:if条件内部只能包含 UI 组件语法,不能包含变量声明、函数调用赋值等。
小结
本章完成了首页和诗词库两个核心页面的开发,涵盖了:
- 渐变背景卡片的设计
- Grid 网格布局的使用
- @Builder 组件复用技巧
- 搜索 + 双维度筛选的实现
- 跨页面参数传递
- 数据过滤的最佳实践(@State + @Watch)
在下一章中,我们将继续开发诗词详情和作者天地两个页面,深入复杂数据展示和交互设计。
【系列目录】
- (一)项目初始化与架构设计
- (二)首页与诗词库页面开发 ← 本文
- (三)诗词详情与作者天地页面开发
- (四)收藏页面与底部导航实现
- (五)编译调试与问题修复经验
