03主入口页面与导航结构-鸿蒙PC端Electron开发
本文介绍了HarmonyOS应用开发中的页面路由机制和主入口页面实现。主要内容包括:1)HarmonyOS的路由系统工作原理及API使用;2)NativeListPage的完整布局结构,包含标题栏、功能卡片、搜索栏和单词列表;3)功能卡片实现细节,如渐变色背景和阴影效果;4)实时搜索功能的实现方法;5)列表渲染优化技巧,包括ForEach和LazyForEach的使用场景;6)常用布局技巧和主题色
欢迎加入开源鸿蒙 PC社区
https://harmonypc.csdn.net/
源码仓库
https://atomgit.com/qq_33247427/englishProject.git
效果截图

系列教程导航
|
篇号 |
标题 |
状态 |
|
01 |
✅ |
|
|
02 |
✅ |
|
|
03 |
主入口页面与导航结构 |
本篇 |
|
04 |
极速划词页面实现 |
下一篇 |
一、页面路由机制
1.1 HarmonyOS 的路由系统
HarmonyOS 使用 @kit.ArkUI 中的 router 模块管理页面跳转。每个页面都是一个独立的 .ets 文件,通过 @Entry 装饰器标记为可路由页面。
路由的工作流程:
用户点击 → router.pushUrl() → 系统创建新页面实例 → 渲染展示
用户返回 → router.back() → 系统销毁当前页面 → 回到上一页
1.2 注册页面路由
所有可跳转的页面必须在 resources/base/profile/main_pages.json 中注册:
{
"src": [
"pages/NativeListPage",
"pages/Index",
"pages/DictationPage",
"pages/SpeedVocabPage"
]
}
未注册的页面调用 router.pushUrl() 会报错。路径不需要 .ets 后缀,也不需要 src/main/ets/ 前缀。
1.3 路由跳转 API
import { router } from '@kit.ArkUI';
// 基本跳转
router.pushUrl({ url: 'pages/SpeedVocabPage' });
// 带参数跳转
router.pushUrl({
url: 'pages/Index',
params: {
selectedWord: word,
mode: 'practice'
}
});
// 返回上一页
router.back();
// 获取传入参数(在目标页面中)
aboutToAppear() {
const params = router.getParams() as Record<string, Object>;
if (params && params['selectedWord']) {
this.word = params['selectedWord'] as VocabularyWord;
}
}
二、NativeListPage 整体结构
2.1 页面布局规划
主入口页面从上到下分为四个区域:
┌─────────────────────────────────┐
│ 标题栏:词汇手写练习 │
├─────────────────────────────────┤
│ 功能入口卡片(极速划词 | 默写) │
├─────────────────────────────────┤
│ 结果统计 │
├─────────────────────────────────┤
│ 单词列表(可滚动) │
│ ... │
│ ... │
└─────────────────────────────────┘
2.2 完整页面代码
import { router } from '@kit.ArkUI';
import { HandwritingWordRepository } from '../data/HandwritingWordRepository';
import { VocabularyWord } from '../models/VocabularyWord';
@Entry
@Component
struct NativeListPage {
private repository: HandwritingWordRepository = new HandwritingWordRepository();
private allWords: VocabularyWord[] = this.repository.getAllWords();
@State filteredWords: VocabularyWord[] = this.allWords;
@State searchText: string = '';
onSearchChange(value: string) {
this.searchText = value;
if (!value.trim()) {
this.filteredWords = this.allWords;
return;
}
const keyword = value.toLowerCase().trim();
this.filteredWords = this.allWords.filter((word: VocabularyWord) => {
return word.english.toLowerCase().includes(keyword) ||
word.meaning.includes(keyword) ||
(word.transliteration && word.transliteration.includes(keyword));
});
}
onWordClick(word: VocabularyWord) {
router.pushUrl({
url: 'pages/Index',
params: { selectedWord: word }
});
}
build() {
Column() {
// 标题
Row() {
Text('词汇手写练习')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#111827')
}
.width('100%')
.padding({ left: 24, right: 24, top: 20, bottom: 12 })
// 功能入口卡片
Row({ space: 12 }) {
this.FeatureCard('极速划词', '⚡', '单词列表·点击显示',
[['#B6C496', 0], ['#8B9D6B', 1]], 'pages/SpeedVocabPage')
this.FeatureCard('默写单词', '📝', '看释义·默写考验',
[['#7C5CFF', 0], ['#6A4FDF', 1]], 'pages/DictationPage')
}
.width('100%')
.padding({ left: 24, right: 24, bottom: 16 })
// 搜索栏
Row() {
TextInput({ placeholder: '搜索单词、释义或音译...' })
.fontSize(16)
.backgroundColor('#F3F4F6')
.borderRadius(12)
.height(48)
.padding({ left: 16, right: 16 })
.layoutWeight(1)
.onChange((value: string) => { this.onSearchChange(value); })
}
.width('100%')
.padding({ left: 24, right: 24, bottom: 12 })
// 结果统计
Text(`${this.filteredWords.length} 个单词`)
.fontSize(13)
.fontColor('#9CA3AF')
.padding({ left: 24, bottom: 8 })
// 单词列表
List({ space: 0 }) {
ForEach(this.filteredWords, (word: VocabularyWord, index: number) => {
ListItem() {
this.WordListItem(word, index)
}
}, (word: VocabularyWord) => word.id)
}
.layoutWeight(1)
.width('100%')
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring)
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
}
@Builder
FeatureCard(title: string, icon: string, subtitle: string,
colors: [string, number][], targetUrl: string) {
Column({ space: 6 }) {
Text(icon).fontSize(28)
Text(title)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
Text(subtitle)
.fontSize(11)
.fontColor('#FFFFFF')
.opacity(0.7)
}
.width('100%')
.height(90)
.justifyContent(FlexAlign.Center)
.borderRadius(14)
.linearGradient({ angle: 135, colors: colors })
.shadow({ radius: 8, color: '#8B9D6B30', offsetY: 3 })
.layoutWeight(1)
.onClick(() => {
router.pushUrl({ url: targetUrl });
})
}
@Builder
WordListItem(word: VocabularyWord, index: number) {
Row() {
Text(`${index + 1}`)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#7C5CFF')
.width(40)
.textAlign(TextAlign.Center)
Column({ space: 4 }) {
Row({ space: 8 }) {
Text(word.english)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#111827')
if (word.phonetic) {
Text(word.phonetic)
.fontSize(13)
.fontColor('#6B7280')
}
}
Text(word.meaning)
.fontSize(14)
.fontColor('#4B5563')
.maxLines(2)
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.width('100%')
.height(80)
.padding({ left: 20, right: 20 })
.alignItems(VerticalAlign.Center)
.onClick(() => { this.onWordClick(word); })
}
}
三、功能入口卡片详解
3.1 渐变色背景
使用 linearGradient 实现从浅到深的渐变效果:
.linearGradient({
angle: 135, // 渐变角度(左上到右下)
colors: [['#B6C496', 0], ['#8B9D6B', 1]] // [颜色, 位置] 数组
})
角度说明:
0:从下到上90:从左到右135:从左上到右下(最常用)180:从上到下
3.2 阴影效果
.shadow({
radius: 8, // 模糊半径
color: '#8B9D6B30', // 阴影颜色(30 = 约 19% 透明度)
offsetY: 3 // 垂直偏移
})
颜色后面的两位十六进制是 Alpha 通道:00 = 完全透明,FF = 完全不透明,30 ≈ 19%。
3.3 @Builder 装饰器
@Builder 用于定义可复用的 UI 片段,类似于函数组件:
@Builder
FeatureCard(title: string, icon: string, ...) {
// UI 描述
}
// 使用时像调用方法一样
this.FeatureCard('极速划词', '⚡', ...)
与 @Component 的区别:
@Builder:轻量级,没有独立的状态和生命周期,适合纯展示片段@Component:完整组件,有自己的@State、生命周期,适合复杂交互
四、搜索功能实现
4.1 实时搜索
使用 TextInput 的 onChange 回调实现实时过滤:
TextInput({ placeholder: '搜索单词、释义或音译...' })
.onChange((value: string) => { this.onSearchChange(value); })
4.2 多字段模糊匹配
onSearchChange(value: string) {
if (!value.trim()) {
this.filteredWords = this.allWords; // 空搜索显示全部
return;
}
const keyword = value.toLowerCase().trim();
this.filteredWords = this.allWords.filter((word: VocabularyWord) => {
return word.english.toLowerCase().includes(keyword) || // 英文匹配
word.meaning.includes(keyword) || // 中文释义匹配
(word.transliteration && word.transliteration.includes(keyword)); // 音译匹配
});
}
搜索支持三种维度:
- 输入
trans→ 匹配 transform、transportation - 输入
转变→ 匹配 transform - 输入
特瑞→ 匹配音译中包含"特瑞"的单词
4.3 性能考虑
当前使用 filter 做全量遍历,对于几百个单词完全没有性能问题。如果词库扩展到上万级别,可以考虑:
- 防抖(debounce):用户停止输入 300ms 后再搜索
- 索引:预建倒排索引加速查找
- 虚拟列表:使用
LazyForEach替代ForEach
五、单词列表渲染
5.1 List 组件
List({ space: 0 }) {
ForEach(this.filteredWords, (word: VocabularyWord, index: number) => {
ListItem() {
this.WordListItem(word, index)
}
}, (word: VocabularyWord) => word.id)
}
.layoutWeight(1) // 占满剩余空间
.scrollBar(BarState.Off) // 隐藏滚动条
.edgeEffect(EdgeEffect.Spring) // 弹性边缘效果
5.2 LazyForEach 优化(大数据量)
当列表项超过 100 个时,建议使用 LazyForEach 实现懒加载:
class WordDataSource implements IDataSource {
private words: VocabularyWord[];
private listeners: DataChangeListener[] = [];
constructor(words: VocabularyWord[]) {
this.words = words;
}
totalCount(): number {
return this.words.length;
}
getData(index: number): VocabularyWord {
return this.words[index];
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listeners.push(listener);
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) this.listeners.splice(pos, 1);
}
}
// 使用
List() {
LazyForEach(new WordDataSource(this.filteredWords), (word: VocabularyWord) => {
ListItem() { /* ... */ }
}, (word: VocabularyWord) => word.id)
}
LazyForEach 只会创建可视区域内的组件,滚动时动态回收和复用,内存占用大幅降低。
六、布局技巧总结
6.1 layoutWeight 弹性布局
Column() {
Row().height(60) // 固定高度
Row().height(90) // 固定高度
List().layoutWeight(1) // 占满剩余空间
}
layoutWeight(1) 让组件占据父容器中所有未被固定尺寸组件占用的空间。
6.2 padding vs margin
padding:内边距,内容与边框之间的距离margin:外边距,组件与相邻组件之间的距离
// padding:搜索栏内容距离容器边缘 24px
Row().padding({ left: 24, right: 24 })
// margin:卡片之间间距 12px
Row({ space: 12 }) // space 等效于子元素之间的 margin
6.3 对齐方式
Column() { ... }
.alignItems(HorizontalAlign.Start) // 子元素左对齐
Row() { ... }
.alignItems(VerticalAlign.Center) // 子元素垂直居中
.justifyContent(FlexAlign.SpaceBetween) // 水平两端对齐
七、主题色在入口页的应用
回顾我们的主题色体系,在入口页中的使用:
|
元素 |
色值 |
说明 |
|
极速划词卡片渐变 |
#B6C496 → #8B9D6B |
主题绿 |
|
默写单词卡片渐变 |
#7C5CFF → #6A4FDF |
紫色(区分功能) |
|
序号文字 |
#7C5CFF |
紫色强调 |
|
标题文字 |
#111827 |
近黑色 |
|
正文文字 |
#4B5563 |
深灰 |
|
辅助文字 |
#9CA3AF |
浅灰 |
|
搜索框背景 |
#F3F4F6 |
极浅灰 |
八、本篇小结
通过本篇教程,我们完成了:
- 理解了 HarmonyOS 的页面路由机制
- 创建了主入口页面 NativeListPage
- 实现了渐变色功能入口卡片
- 实现了多字段实时搜索
- 掌握了 List + ForEach 列表渲染
- 了解了 LazyForEach 性能优化方案
- 学习了 @Builder 可复用 UI 片段
下一篇预告
第 4 篇:极速划词页面实现 — 我们将创建 SpeedVocabPage,实现左侧 Tab 导航栏、右侧两列单词卡片网格,以及点击显示/隐藏释义的交互。
更多推荐



所有评论(0)