暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

104. [HarmonyOS NEXT 实战案例:新闻阅读应用] 进阶篇 - 交互功能与状态管理

原创 若城 2025-06-09
106

[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() { // 组件内容 } }

在这个组件中,我们添加了以下状态变量:

  1. searchText:用于存储搜索框中的文本
  2. favoriteNews:用于存储收藏的新闻标题集合
  3. selectedNews:用于存储当前选中的新闻项
  4. 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)

在这个实现中,我们添加了以下功能:

  1. 在标题行添加了一个返回按钮,当处于新闻详情模式时显示
  2. 使用条件渲染,根据isDetailMode状态显示不同的内容:
    • isDetailModefalse时,显示分类和新闻列表
    • isDetailModetrue时,显示新闻详情

左侧新闻分类区域

左侧新闻分类区域与基础篇相同,使用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%')

在这个实现中,我们添加了以下功能:

  1. 搜索框:使用TextInput组件实现,添加onChange事件处理器更新searchText状态
  2. 搜索按钮:添加onClick事件处理器,输出搜索文本
  3. 新闻列表:使用getFilteredNews方法过滤新闻数据,添加onClick事件处理器,在点击时更新selectedNewsisDetailMode状态

新闻项组件

新闻项组件添加了收藏功能:

@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%') }

在这个实现中,我们添加了一个收藏按钮,用于收藏或取消收藏新闻:

  1. 根据favoriteNews状态设置按钮的文本、背景色和文字颜色
  2. 添加onClick事件处理器,在点击时更新favoriteNews状态
  3. 使用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集合中移除,按钮样式也会相应地变化。

新闻详情查看

新闻详情查看通过selectedNewsisDetailMode状态变量来控制:

@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状态会设置为falseselectedNews状态会设置为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的状态管理机制,我们可以轻松地实现这些交互功能,使界面更加动态和交互友好。我们还学习了如何使用条件渲染来根据状态显示不同的内容,以及如何在事件处理器中更新状态。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论