[HarmonyOS NEXT 实战案例:新闻阅读应用] 进阶篇 - 交互功能与状态管理
[HarmonyOS NEXT 实战案例:新闻阅读应用] 进阶篇 - 交互功能与状态管理
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
引言
在基础篇中,我们学习了如何使用HarmonyOS NEXT的RowSplit
组件构建新闻阅读应用的基本布局。在本篇教程中,我们将进一步探讨如何为新闻阅读应用添加交互功能和状态管理,包括新闻分类切换、新闻搜索、新闻收藏、新闻详情查看等功能,使界面更加动态和交互友好。
状态管理概述
在交互式应用中,状态管理是至关重要的。HarmonyOS NEXT提供了多种状态管理机制,如@State
、@Prop
、@Link
等装饰器。在本案例中,我们将主要使用@State
装饰器来管理组件内部状态。
状态变量 | 类型 | 功能描述 |
---|---|---|
selectedCategory |
string | 当前选中的新闻分类 |
searchText |
string | 搜索框中的文本 |
favoriteNews |
Set | 收藏的新闻标题集合 |
selectedNews |
NewsItem | null | 当前选中的新闻项 |
isDetailMode |
boolean | 是否处于新闻详情模式 |
代码实现
组件结构与状态定义
首先,我们在组件中定义状态变量:
@Component
export struct NewsReaderExample {
@State selectedCategory: string = '推荐';
@State searchText: string = '';
@State favoriteNews: Set<string> = new Set<string>();
@State selectedNews: NewsItem | null = null;
@State isDetailMode: boolean = false;
private categories: string[] = ['推荐', '科技', '体育', '财经', '娱乐', '健康'];
@State newsData: NewsItem[] = [
new NewsItem('HarmonyOS NEXT发布,带来全新的分布式体验', '科技日报', '10分钟前', $r('app.media.big30'), '科技'),
new NewsItem('全国科技创新大会在北京召开', '新闻网', '30分钟前', $r('app.media.big31'), '科技'),
new NewsItem('2023年全球智能手机市场分析报告', '科技评论', '1小时前', $r('app.media.big32'), '科技'),
new NewsItem('国足最新一期集训名单公布', '体育新闻', '2小时前', $r('app.media.big33'), '体育'),
new NewsItem('NBA季后赛最新战报', '体育周刊', '3小时前', $r('app.media.big34'), '体育'),
new NewsItem('央行发布最新货币政策报告', '财经日报', '4小时前', $r('app.media.big35'), '财经'),
new NewsItem('A股市场今日行情分析', '证券时报', '5小时前', $r('app.media.big36'), '财经'),
new NewsItem('某流量明星最新电影票房破10亿', '娱乐周刊', '6小时前', $r('app.media.big37'), '娱乐'),
new NewsItem('夏季养生指南:如何科学防暑', '健康时报', '7小时前', $r('app.media.big38'), '健康')
];
build() {
// 组件内容
}
}
在这个组件中,我们添加了以下状态变量:
searchText
:用于存储搜索框中的文本favoriteNews
:用于存储收藏的新闻标题集合selectedNews
:用于存储当前选中的新闻项isDetailMode
:用于标记是否处于新闻详情模式
外层容器
外层容器结构与基础篇相同,使用Column
组件包含标题文本和主要内容区域:
Column() {
Row() {
Text('新闻阅读应用布局')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
if (this.isDetailMode) {
Button('返回列表')
.fontSize(14)
.height(32)
.backgroundColor('#007DFF')
.onClick(() => {
this.isDetailMode = false;
this.selectedNews = null;
})
}
}
.width('100%')
.margin({ bottom: 10 })
if (!this.isDetailMode) {
RowSplit() {
// 左侧新闻分类区域
Column() {
// 新闻分类内容
}
.width('25%')
.backgroundColor('#f5f5f5')
// 右侧新闻列表区域
Column() {
// 搜索框和新闻列表
}
.width('75%')
}
.height(600)
} else {
// 新闻详情页
this.NewsDetailComponent(this.selectedNews!)
}
}
.width('100%')
.padding(15)
在这个实现中,我们添加了以下功能:
- 在标题行添加了一个返回按钮,当处于新闻详情模式时显示
- 使用条件渲染,根据
isDetailMode
状态显示不同的内容:- 当
isDetailMode
为false
时,显示分类和新闻列表 - 当
isDetailMode
为true
时,显示新闻详情
- 当
左侧新闻分类区域
左侧新闻分类区域与基础篇相同,使用Column
组件包含一系列分类按钮:
Column() {
Button('我的收藏')
.width('90%')
.height(50)
.fontSize(16)
.margin({ top: 10, bottom: 10 })
.borderRadius(8)
.backgroundColor('#ff9500')
.fontColor('#ffffff')
.onClick(() => {
this.selectedCategory = '收藏';
})
ForEach(this.categories, (category: string) => {
Button(category)
.width('90%')
.height(50)
.fontSize(16)
.margin({ top: 10 })
.borderRadius(8)
.backgroundColor(this.selectedCategory === category ? '#007DFF' : '#ffffff')
.fontColor(this.selectedCategory === category ? '#ffffff' : '#333333')
.onClick(() => {
this.selectedCategory = category;
})
})
}
.width('25%')
.backgroundColor('#f5f5f5')
.padding({ top: 10 })
在这个实现中,我们添加了一个"我的收藏"按钮,用于显示收藏的新闻。
右侧新闻列表区域
右侧新闻列表区域添加了搜索框和收藏功能:
Column() {
// 搜索框
Row() {
TextInput({ placeholder: '搜索新闻', text: this.searchText })
.width('80%')
.height(40)
.backgroundColor('#f0f0f0')
.borderRadius(20)
.padding({ left: 15, right: 15 })
.onChange((value: string) => {
this.searchText = value;
})
Button('搜索')
.width('18%')
.height(40)
.fontSize(14)
.margin({ left: '2%' })
.borderRadius(20)
.backgroundColor('#007DFF')
.onClick(() => {
// 搜索逻辑
console.info(`搜索:${this.searchText}`);
})
}
.width('100%')
.padding({ left: 10, right: 10, top: 10, bottom: 10 })
// 新闻列表
List() {
ForEach(this.getFilteredNews(), (item: NewsItem) => {
ListItem() {
this.NewsItemComponent(item)
}
.padding(10)
.onClick(() => {
this.selectedNews = item;
this.isDetailMode = true;
})
})
}
.width('100%')
.height('100%')
.divider({ strokeWidth: 1, color: '#f0f0f0', startMargin: 10, endMargin: 10 })
}
.width('75%')
在这个实现中,我们添加了以下功能:
- 搜索框:使用
TextInput
组件实现,添加onChange
事件处理器更新searchText
状态 - 搜索按钮:添加
onClick
事件处理器,输出搜索文本 - 新闻列表:使用
getFilteredNews
方法过滤新闻数据,添加onClick
事件处理器,在点击时更新selectedNews
和isDetailMode
状态
新闻项组件
新闻项组件添加了收藏功能:
@Builder
private NewsItemComponent(item: NewsItem) {
Row() {
Column() {
Text(item.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 5 })
Row() {
Text(item.source)
.fontSize(14)
.fontColor('#666')
Text(item.time)
.fontSize(14)
.fontColor('#666')
.margin({ left: 10 })
Blank()
Button(this.favoriteNews.has(item.title) ? '已收藏' : '收藏')
.fontSize(12)
.height(24)
.backgroundColor(this.favoriteNews.has(item.title) ? '#ff9500' : '#f0f0f0')
.fontColor(this.favoriteNews.has(item.title) ? '#ffffff' : '#333333')
.borderRadius(12)
.onClick((event: ClickEvent) => {
event.stopPropagation();
if (this.favoriteNews.has(item.title)) {
this.favoriteNews.delete(item.title);
} else {
this.favoriteNews.add(item.title);
}
// 强制更新Set
this.favoriteNews = new Set(this.favoriteNews);
})
}
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
Image(item.imageUrl)
.width(100)
.height(70)
.objectFit(ImageFit.Cover)
.borderRadius(5)
.margin({ left: 10 })
}
.width('100%')
}
在这个实现中,我们添加了一个收藏按钮,用于收藏或取消收藏新闻:
- 根据
favoriteNews
状态设置按钮的文本、背景色和文字颜色 - 添加
onClick
事件处理器,在点击时更新favoriteNews
状态 - 使用
event.stopPropagation()
阻止事件冒泡,避免触发列表项的点击事件
新闻详情组件
为了显示新闻详情,我们定义了一个NewsDetailComponent
方法:
@Builder
private NewsDetailComponent(item: NewsItem) {
Column() {
// 新闻标题
Text(item.title)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 15 })
// 新闻来源和时间
Row() {
Text(item.source)
.fontSize(14)
.fontColor('#666')
Text(item.time)
.fontSize(14)
.fontColor('#666')
.margin({ left: 10 })
Blank()
Button(this.favoriteNews.has(item.title) ? '已收藏' : '收藏')
.fontSize(14)
.height(32)
.backgroundColor(this.favoriteNews.has(item.title) ? '#ff9500' : '#f0f0f0')
.fontColor(this.favoriteNews.has(item.title) ? '#ffffff' : '#333333')
.borderRadius(16)
.onClick(() => {
if (this.favoriteNews.has(item.title)) {
this.favoriteNews.delete(item.title);
} else {
this.favoriteNews.add(item.title);
}
// 强制更新Set
this.favoriteNews = new Set(this.favoriteNews);
})
}
.width('100%')
.margin({ bottom: 20 })
// 新闻图片
Image(item.imageUrl)
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
.borderRadius(8)
.margin({ bottom: 20 })
// 新闻内容
Text(this.generateNewsContent(item))
.fontSize(16)
.lineHeight(24)
.margin({ bottom: 20 })
// 相关新闻
Text('相关新闻')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
List() {
ForEach(this.getRelatedNews(item), (relatedItem: NewsItem) => {
ListItem() {
Row() {
Text(relatedItem.title)
.fontSize(14)
.layoutWeight(1)
Text(relatedItem.time)
.fontSize(12)
.fontColor('#666')
}
.width('100%')
.padding({ top: 8, bottom: 8 })
}
.onClick(() => {
this.selectedNews = relatedItem;
})
})
}
.width('100%')
.height(150)
.divider({ strokeWidth: 1, color: '#f0f0f0' })
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.padding(15)
}
在这个方法中,我们显示了新闻的详细信息,包括标题、来源、时间、图片、内容和相关新闻。
辅助方法
为了支持上述功能,我们添加了以下辅助方法:
private getFilteredNews(): NewsItem[] {
// 根据选中的分类和搜索文本过滤新闻
let filteredNews = this.newsData;
// 根据分类过滤
if (this.selectedCategory !== '推荐') {
if (this.selectedCategory === '收藏') {
// 显示收藏的新闻
filteredNews = filteredNews.filter(item => this.favoriteNews.has(item.title));
} else {
// 显示特定分类的新闻
filteredNews = filteredNews.filter(item => item.category === this.selectedCategory);
}
}
// 根据搜索文本过滤
if (this.searchText.trim() !== '') {
const searchLower = this.searchText.toLowerCase();
filteredNews = filteredNews.filter(item =>
item.title.toLowerCase().includes(searchLower) ||
item.source.toLowerCase().includes(searchLower) ||
item.category.toLowerCase().includes(searchLower)
);
}
return filteredNews;
}
private getRelatedNews(currentNews: NewsItem): NewsItem[] {
// 获取与当前新闻相关的新闻(同一分类的其他新闻)
return this.newsData
.filter(item => item.category === currentNews.category && item.title !== currentNews.title)
.slice(0, 3); // 最多显示3条相关新闻
}
private generateNewsContent(item: NewsItem): string {
// 生成新闻内容(实际应用中应该从后端获取)
return `这是一篇关于${item.category}的新闻。${item.title}。这里是新闻的详细内容,包含了事件的起因、经过和结果。\n\n这是第二段落,提供了更多的背景信息和相关数据。根据最新的统计数据显示,这一领域的发展趋势非常明显。\n\n这是第三段落,包含了专家的观点和分析。多位专家认为,这一事件将对行业产生深远的影响。`;
}
这些方法用于过滤新闻数据、获取相关新闻和生成新闻内容,使代码更加模块化和可维护。
交互功能分析
分类切换
分类切换通过selectedCategory
状态变量来控制:
@State selectedCategory: string = '推荐';
// 分类按钮
Button(category)
// 按钮属性
.backgroundColor(this.selectedCategory === category ? '#007DFF' : '#ffffff')
.fontColor(this.selectedCategory === category ? '#ffffff' : '#333333')
.onClick(() => {
this.selectedCategory = category;
})
当用户点击分类按钮时,selectedCategory
状态会更新为选中的分类,按钮样式和新闻列表也会相应地变化。
新闻搜索
新闻搜索通过searchText
状态变量来控制:
@State searchText: string = '';
// 搜索框
TextInput({ placeholder: '搜索新闻', text: this.searchText })
// 输入框属性
.onChange((value: string) => {
this.searchText = value;
})
// 搜索按钮
Button('搜索')
// 按钮属性
.onClick(() => {
// 搜索逻辑
console.info(`搜索:${this.searchText}`);
})
当用户在搜索框中输入文本时,searchText
状态会更新,新闻列表也会根据搜索文本进行过滤。
新闻收藏
新闻收藏通过favoriteNews
状态变量来控制:
@State favoriteNews: Set<string> = new Set<string>();
// 收藏按钮
Button(this.favoriteNews.has(item.title) ? '已收藏' : '收藏')
// 按钮属性
.backgroundColor(this.favoriteNews.has(item.title) ? '#ff9500' : '#f0f0f0')
.fontColor(this.favoriteNews.has(item.title) ? '#ffffff' : '#333333')
.onClick((event: ClickEvent) => {
event.stopPropagation();
if (this.favoriteNews.has(item.title)) {
this.favoriteNews.delete(item.title);
} else {
this.favoriteNews.add(item.title);
}
// 强制更新Set
this.favoriteNews = new Set(this.favoriteNews);
})
当用户点击收藏按钮时,新闻标题会被添加到或从favoriteNews
集合中移除,按钮样式也会相应地变化。
新闻详情查看
新闻详情查看通过selectedNews
和isDetailMode
状态变量来控制:
@State selectedNews: NewsItem | null = null;
@State isDetailMode: boolean = false;
// 新闻列表项
ListItem() {
this.NewsItemComponent(item)
}
.padding(10)
.onClick(() => {
this.selectedNews = item;
this.isDetailMode = true;
})
// 返回按钮
Button('返回列表')
// 按钮属性
.onClick(() => {
this.isDetailMode = false;
this.selectedNews = null;
})
// 条件渲染
if (!this.isDetailMode) {
// 显示分类和新闻列表
} else {
// 显示新闻详情
this.NewsDetailComponent(this.selectedNews!)
}
当用户点击新闻列表项时,selectedNews
状态会更新为选中的新闻项,isDetailMode
状态会设置为true
,界面会切换到新闻详情模式。当用户点击返回按钮时,isDetailMode
状态会设置为false
,selectedNews
状态会设置为null
,界面会切换回新闻列表模式。
状态管理技巧
状态变量的选择
在本案例中,我们使用@State
装饰器来管理组件内部状态。@State
装饰器适用于组件内部的状态管理,当状态变化时,组件会自动重新渲染。
@State selectedCategory: string = '推荐';
@State searchText: string = '';
@State favoriteNews: Set<string> = new Set<string>();
@State selectedNews: NewsItem | null = null;
@State isDetailMode: boolean = false;
条件渲染
我们使用条件渲染来根据状态显示不同的内容:
// 根据isDetailMode状态显示不同的内容
if (!this.isDetailMode) {
// 显示分类和新闻列表
} else {
// 显示新闻详情
this.NewsDetailComponent(this.selectedNews!)
}
// 根据favoriteNews状态设置按钮的文本和样式
Button(this.favoriteNews.has(item.title) ? '已收藏' : '收藏')
.backgroundColor(this.favoriteNews.has(item.title) ? '#ff9500' : '#f0f0f0')
.fontColor(this.favoriteNews.has(item.title) ? '#ffffff' : '#333333')
状态更新
我们在事件处理器中更新状态:
// 更新selectedCategory状态
.onClick(() => {
this.selectedCategory = category;
})
// 更新searchText状态
.onChange((value: string) => {
this.searchText = value;
})
// 更新favoriteNews状态
.onClick((event: ClickEvent) => {
event.stopPropagation();
if (this.favoriteNews.has(item.title)) {
this.favoriteNews.delete(item.title);
} else {
this.favoriteNews.add(item.title);
}
// 强制更新Set
this.favoriteNews = new Set(this.favoriteNews);
})
// 更新selectedNews和isDetailMode状态
.onClick(() => {
this.selectedNews = item;
this.isDetailMode = true;
})
当状态更新时,组件会自动重新渲染,显示最新的状态。
数据过滤
我们使用辅助方法来过滤数据:
private getFilteredNews(): NewsItem[] {
// 根据选中的分类和搜索文本过滤新闻
let filteredNews = this.newsData;
// 根据分类过滤
if (this.selectedCategory !== '推荐') {
if (this.selectedCategory === '收藏') {
// 显示收藏的新闻
filteredNews = filteredNews.filter(item => this.favoriteNews.has(item.title));
} else {
// 显示特定分类的新闻
filteredNews = filteredNews.filter(item => item.category === this.selectedCategory);
}
}
// 根据搜索文本过滤
if (this.searchText.trim() !== '') {
const searchLower = this.searchText.toLowerCase();
filteredNews = filteredNews.filter(item =>
item.title.toLowerCase().includes(searchLower) ||
item.source.toLowerCase().includes(searchLower) ||
item.category.toLowerCase().includes(searchLower)
);
}
return filteredNews;
}
这个方法根据选中的分类和搜索文本过滤新闻数据,返回符合条件的新闻列表。
总结
在本教程中,我们学习了如何为新闻阅读应用添加交互功能和状态管理,包括新闻分类切换、新闻搜索、新闻收藏、新闻详情查看等功能。 通过使用HarmonyOS NEXT的状态管理机制,我们可以轻松地实现这些交互功能,使界面更加动态和交互友好。我们还学习了如何使用条件渲染来根据状态显示不同的内容,以及如何在事件处理器中更新状态。
- 0回答
- 3粉丝
- 0关注
- [HarmonyOS NEXT 实战案例:聊天应用] 进阶篇 - 交互功能与状态管理
- [HarmonyOS NEXT 实战案例:电商应用] 进阶篇 - 交互功能与状态管理
- [HarmonyOS NEXT 实战案例:设置页面] 进阶篇 - 交互功能与状态管理
- [HarmonyOS NEXT 实战案例:新闻阅读应用] 基础篇 - 水平分割布局构建新闻阅读界面
- [HarmonyOS NEXT 实战案例:音乐播放器] 进阶篇 - 交互式音乐播放器的状态管理与控制
- [HarmonyOS NEXT 实战案例:分割布局] 进阶篇 - 交互式邮件应用布局
- [HarmonyOS NEXT 实战案例:新闻阅读应用] 高级篇 - 高级布局技巧与组件封装
- [HarmonyOS NEXT 实战案例:分割布局] 进阶篇 - RowSplit与ColumnSplit的组合应用
- [HarmonyOS NEXT 实战案例十八] 日历日程视图网格布局(进阶篇)
- 98.[HarmonyOS NEXT 实战案例:分割布局] 高级篇 - 邮件应用的高级功能与优化
- [HarmonyOS NEXT 实战案例十五] 电商分类导航网格布局(进阶篇)
- 03 HarmonyOS Next仪表盘案例详解(二):进阶篇
- [HarmonyOS NEXT 实战案例:分割布局] 进阶篇 - 设置中心的动态内容与复用构建
- [HarmonyOS NEXT 实战案例:分割布局] 进阶篇 - 三栏布局的嵌套与复杂界面构建
- HarmonyOS Next V2 状态管理实战