HarmonyOS 5开发从入门到精通(十七):新闻阅读应用实战(上)

HarmonyOS 5开发从入门到精通(十七):新闻阅读应用实战(上)

本章将带领大家开发一个完整的新闻阅读应用,涵盖新闻列表展示、详情页跳转、网络数据获取等核心功能。通过本案例,你将掌握HarmonyOS应用开发的关键技能。

一、核心概念

1. 新闻数据模型与网络请求

新闻应用的核心是获取远程API数据并转换为本地数据模型。HarmonyOS提供了@ohos.net.http模块进行网络请求,配合JSON解析将服务器返回的数据转换为TypeScript对象,实现数据驱动UI。

2. 页面路由与导航

页面导航是应用的核心交互方式,通过@ohos.router模块实现页面跳转和参数传递。结合ArkUI的声明式开发范式,可以构建流畅的页面切换体验。

二、关键API详解

1. 网络请求模块

import http from '@ohos.net.http'

// 创建HTTP请求实例
const httpRequest = http.createHttp()

// 发起GET请求
httpRequest.request(url, {
  method: http.RequestMethod.GET,
  connectTimeout: 10000,
  readTimeout: 10000
})

2. JSON解析

// 解析JSON字符串
const newsData = JSON.parse(response.result)

// 转换为JSON字符串
const jsonStr = JSON.stringify(newsData)

3. 新闻数据模型

class NewsItem {
  id: string = ''
  title: string = ''
  content: string = ''
  publishTime: string = ''
  source: string = ''
}

4. 页面路由

import router from '@ohos.router'

// 跳转到详情页
router.pushUrl({
  url: 'pages/NewsDetail',
  params: { id: newsId }
})

// 接收参数
const params = router.getParams()
const newsId = params['id']

5. 列表组件渲染

List() {
  ForEach(this.newsList, (news: NewsItem) => {
    ListItem() {
      Text(news.title)
    }
  })
}

6. 异步数据获取

async getNewsList(): Promise<NewsItem[]> {
  try {
    const response = await httpRequest.request(url)
    return JSON.parse(response.result)
  } catch (error) {
    console.error('获取新闻数据失败')
  }
}

7. 权限配置

{
  "requestPermissions": [
    {
      "name": "ohos.permission.INTERNET"
    }
  ]
}

8. 状态管理

@State newsList: NewsItem[] = []
@State isLoading: boolean = true

9. 页面生命周期

aboutToAppear() {
  this.loadNewsData()
}

onPageShow() {
  this.refreshData()
}

10. 错误处理

try {
  // 网络请求
} catch (error) {
  console.error('请求失败:', error)
}

11. 数据绑定

Text(this.newsList.length + '条新闻')
  .fontSize(16)
  .fontColor(Color.Gray)

12. 组件复用

@Component
struct NewsCard {
  @Prop news: NewsItem
  
  build() {
    Column() {
      Text(this.news.title)
      Text(this.news.publishTime)
    }
  }
}

三、实战案例

完整代码实现

import http from '@ohos.net.http'
import router from '@ohos.router'

// 新闻数据模型
class NewsItem {
  id: string = ''
  title: string = ''
  content: string = ''
  publishTime: string = ''
  source: string = ''
}

@Entry
@Component
struct NewsApp {
  @State newsList: NewsItem[] = []
  @State isLoading: boolean = true
  @State errorMessage: string = ''

  private httpRequest = http.createHttp()

  aboutToAppear() {
    this.loadNewsData()
  }

  // 加载新闻数据
  private async loadNewsData() {
    try {
      this.isLoading = true
      const url = 'https://api.example.com/news'
      const response = await this.httpRequest.request(url, {
        method: http.RequestMethod.GET
      })
      
      if (response.responseCode === 200) {
        const data = JSON.parse(response.result)
        this.newsList = data.map((item: any) => ({
          id: item.id,
          title: item.title,
          content: item.content,
          publishTime: item.publishTime,
          source: item.source
        }))
      }
    } catch (error) {
      this.errorMessage = '网络请求失败,请检查网络连接'
      console.error('获取新闻数据失败:', error)
    } finally {
      this.isLoading = false
    }
  }

  // 跳转到详情页
  private navigateToDetail(news: NewsItem) {
    router.pushUrl({
      url: 'pages/NewsDetail',
      params: { 
        id: news.id,
        title: news.title,
        content: news.content,
        publishTime: news.publishTime,
        source: news.source
      }
    })
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('新闻头条')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .margin({ left: 20 })
        
        Button('刷新')
          .margin({ left: 20 })
          .onClick(() => this.loadNewsData())
      }
      .width('100%')
      .height(60)
      .backgroundColor(Color.White)
      .justifyContent(FlexAlign.SpaceBetween)

      // 内容区域
      if (this.isLoading) {
        LoadingProgress()
          .width(50)
          .height(50)
          .margin({ top: 100 })
      } else if (this.errorMessage) {
        Text(this.errorMessage)
          .fontSize(16)
          .fontColor(Color.Red)
          .margin({ top: 100 })
      } else {
        List({ space: 10 }) {
          ForEach(this.newsList, (news: NewsItem) => {
            ListItem() {
              NewsCard({ news: news })
                .onClick(() => this.navigateToDetail(news))
            }
          })
        }
        .width('100%')
        .height('100%')
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

// 新闻卡片组件
@Component
struct NewsCard {
  @Prop news: NewsItem

  build() {
    Column() {
      // 新闻标题
      Text(this.news.title)
        .fontSize(18)
        .fontColor(Color.Black)
        .maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .margin({ bottom: 8 })

      // 新闻来源和时间
      Row() {
        Text(this.news.source)
          .fontSize(14)
          .fontColor(Color.Gray)
        
        Text(this.news.publishTime)
          .fontSize(14)
          .fontColor(Color.Gray)
          .margin({ left: 10 })
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(8)
    .shadow({ radius: 2, color: '#10000000', offsetX: 0, offsetY: 1 })
    .margin({ left: 16, right: 16, top: 8, bottom: 8 })
  }
}

// 新闻详情页
@Entry
@Component
struct NewsDetail {
  @State title: string = ''
  @State content: string = ''
  @State publishTime: string = ''
  @State source: string = ''

  aboutToAppear() {
    const params = router.getParams()
    this.title = params['title'] || ''
    this.content = params['content'] || ''
    this.publishTime = params['publishTime'] || ''
    this.source = params['source'] || ''
  }

  build() {
    Column() {
      // 返回按钮
      Row() {
        Image($r('app.media.back'))
          .width(24)
          .height(24)
          .onClick(() => router.back())
        
        Text('返回')
          .fontSize(16)
          .margin({ left: 8 })
      }
      .width('100%')
      .padding(16)
      .justifyContent(FlexAlign.Start)

      // 新闻内容
      Scroll() {
        Column() {
          Text(this.title)
            .fontSize(24)
            .fontWeight(FontWeight.Bold)
            .margin({ bottom: 16 })

          Row() {
            Text(this.source)
              .fontSize(14)
              .fontColor(Color.Gray)
            
            Text(this.publishTime)
              .fontSize(14)
              .fontColor(Color.Gray)
              .margin({ left: 10 })
          }
          .margin({ bottom: 20 })

          Text(this.content)
            .fontSize(16)
            .lineHeight(24)
        }
        .padding(16)
      }
      .width('100%')
      .height('100%')
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.White)
  }
}

四、总结

关键知识点

  • 网络请求的异步处理与错误捕获
  • JSON数据的解析与模型转换
  • 页面路由的参数传递与接收
  • 列表组件的渲染与点击事件处理
  • 组件复用的最佳实践

🔧 核心API列表

  • http.createHttp()- 创建HTTP请求实例
  • http.RequestMethod.GET- GET请求方法
  • JSON.parse()- JSON字符串解析
  • router.pushUrl()- 页面跳转
  • router.getParams()- 获取路由参数
  • ForEach()- 列表数据循环渲染
  • @State- 状态管理装饰器
  • @Prop- 属性传递装饰器
  • async/await- 异步编程语法糖

💡 应用建议

  1. 网络请求建议使用try-catch包裹,处理网络异常
  2. 列表数据可添加分页加载,提升用户体验
  3. 建议使用真实的新闻API替换示例中的模拟接口
  4. 可以添加下拉刷新功能,方便用户更新数据

通过本章学习,你已经掌握了新闻阅读应用的核心开发技能。下一章我们将继续完善应用,添加更多实用功能。

posted @ 2025-12-23 21:30  奇崽  阅读(0)  评论(0)    收藏  举报