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

2025-06-09 23:15:20
102次阅读
0个评论

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

项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star

效果演示

image.png

引言

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

收藏00

登录 后评论。没有帐号? 注册 一个。