基于HarmonyOS Next的新闻类应用开发实战指南

一、项目概述与环境搭建

随着HarmonyOS Next的发布,开发者可以利用更强大的分布式能力和更完善的开发工具链来构建跨设备的应用体验。本教程将带领大家开发一个新闻类应用,涵盖新闻列表展示、详情查看、收藏功能等核心模块。

首先需要确保开发环境准备就绪:

  1. 安装最新版DevEco Studio(建议4.1及以上版本)
  2. 创建项目时选择"Application" -> "Empty Ability"
  3. 配置AppGallery Connect服务(后续会详细介绍)

项目结构主要包含以下几个关键部分:

  • pages:存放页面代码
  • resources:应用资源文件
  • entryability:应用入口
  • model:数据模型层

二、AppGallery Connect服务集成

AppGallery Connect为HarmonyOS应用提供了丰富的后端服务能力。我们需要先完成以下配置:

  1. 在AppGallery Connect控制台创建项目
  2. 启用认证服务(Auth Service)和云数据库(Cloud DB)
  3. 下载配置文件agconnect-services.json并放入项目entry目录

build.gradle中添加依赖:

// entry/build.gradle
dependencies {
    implementation 'io.agconnect.agconnect-core-harmony:agconnect-core:1.6.0.300'
    implementation 'io.agconnect.agconnect-auth-harmony:agconnect-auth:1.6.0.300'
    implementation 'io.agconnect.agconnect-clouddb-harmony:agconnect-clouddb:1.6.0.300'
}

初始化代码:

// entryability/EntryAbility.ts
import agconnect from '@hw-agconnect/api-ohos';
import '@hw-agconnect/core-ohos';
import '@hw-agconnect/auth-ohos';
import '@hw-agconnect/clouddb-ohos';

onCreate() {
    agconnect.instance().init(this.context);
    // 初始化云数据库
    this.initCloudDB();
}

async initCloudDB() {
    try {
        const cloudDBZoneConfig = new cloud.CloudDBZoneConfig('NewsDB');
        this.cloudDBZone = await cloud.CloudDBZone.open(cloudDBZoneConfig);
    } catch (err) {
        console.error('CloudDB init failed: ' + JSON.stringify(err));
    }
}

三、新闻数据模型设计

我们需要定义新闻数据的对象类型,以便在本地和云端存储。在model目录下创建News.ts

// model/News.ts
import { cloud } from '@hw-agconnect/clouddb-ohos';

export class News {
    // 使用装饰器定义云数据库字段
    @cloud.Field()
    id: string = '';

    @cloud.Field()
    title: string = '';

    @cloud.Field()
    content: string = '';

    @cloud.Field()
    author: string = '';

    @cloud.Field()
    publishTime: number = 0;

    @cloud.Field()
    category: string = '';

    @cloud.Field()
    imageUrl: string = '';

    @cloud.Field()
    isFavorite: boolean = false;

    constructor() {
        // 设置对象类型名称,需与云数据库中的对象类型名一致
        cloud.ObjectType.register(this, 'News');
    }
}

在云数据库控制台创建对应的对象类型后,我们就可以进行数据的增删改查操作了。

四、新闻列表页面开发

新闻列表是应用的核心页面,我们将使用List组件展示新闻数据,并实现下拉刷新和上拉加载功能。

// pages/NewsListPage.ets
import { News } from '../model/News';
import { cloud } from '@hw-agconnect/clouddb-ohos';

@Entry
@Component
struct NewsListPage {
    @State newsList: Array<News> = [];
    @State isLoading: boolean = false;
    @State isRefreshing: boolean = false;
    private pageSize: number = 10;
    private pageIndex: number = 0;

    build() {
        Column() {
            // 顶部标题栏
            Row() {
                Text('新闻头条')
                    .fontSize(24)
                    .fontWeight(FontWeight.Bold)
                Blank()
                Image($r('app.media.ic_search'))
                    .width(30)
                    .height(30)
                    .margin({ right: 15 })
            }
            .width('100%')
            .padding(15)
            .backgroundColor('#FF4D4F')

            // 新闻列表
            List({ space: 10 }) {
                ForEach(this.newsList, (item: News) => {
                    ListItem() {
                        NewsItem({ news: item })
                    }
                }, (item: News) => item.id)
            }
            .width('100%')
            .layoutWeight(1)
            .onScrollIndex((start: number) => {
                // 滚动到底部加载更多
                if (start >= this.newsList.length - 3 && !this.isLoading) {
                    this.loadMoreNews();
                }
            })
            .scrollBar(BarState.Off)
        }
        .width('100%')
        .height('100%')
        .onAppear(() => {
            this.refreshNews();
        })
    }

    // 下拉刷新
    async refreshNews() {
        this.isRefreshing = true;
        this.pageIndex = 0;
        try {
            const query = cloud.CloudDBZoneQuery.where(News).orderByDesc('publishTime').limit(this.pageSize);
            const result = await this.cloudDBZone.executeQuery(query, News);
            this.newsList = result;
        } catch (err) {
            console.error('Refresh news failed: ' + JSON.stringify(err));
        }
        this.isRefreshing = false;
    }

    // 加载更多
    async loadMoreNews() {
        this.isLoading = true;
        this.pageIndex++;
        try {
            const query = cloud.CloudDBZoneQuery.where(News)
                .orderByDesc('publishTime')
                .limit(this.pageSize)
                .offset(this.pageIndex * this.pageSize);
            const result = await this.cloudDBZone.executeQuery(query, News);
            this.newsList = this.newsList.concat(result);
        } catch (err) {
            console.error('Load more news failed: ' + JSON.stringify(err));
        }
        this.isLoading = false;
    }
}

// 新闻项组件
@Component
struct NewsItem {
    @Prop news: News;

    build() {
        Column() {
            // 新闻图片
            Image(this.news.imageUrl)
                .width('100%')
                .height(200)
                .objectFit(ImageFit.Cover)
                .borderRadius(8)

            // 新闻标题和简介
            Column() {
                Text(this.news.title)
                    .fontSize(18)
                    .fontWeight(FontWeight.Bold)
                    .margin({ bottom: 5 })
                Text(this.news.content.substring(0, 50) + '...')
                    .fontSize(14)
                    .fontColor('#666')
            }
            .padding(10)

            // 底部信息栏
            Row() {
                Text(this.news.author)
                    .fontSize(12)
                    .fontColor('#999')
                Blank()
                Text(this.formatTime(this.news.publishTime))
                    .fontSize(12)
                    .fontColor('#999')
                Image(this.news.isFavorite ? $r('app.media.ic_favorite') : $r('app.media.ic_favorite_border'))
                    .width(20)
                    .height(20)
                    .margin({ left: 10 })
                    .onClick(() => {
                        this.toggleFavorite();
                    })
            }
            .width('100%')
            .padding({ left: 10, right: 10, bottom: 10 })
        }
        .width('95%')
        .margin({ top: 5, bottom: 5 })
        .backgroundColor('#FFF')
        .borderRadius(8)
        .shadow({ radius: 4, color: '#10000000', offsetX: 0, offsetY: 2 })
        .onClick(() => {
            router.pushUrl({ url: 'pages/NewsDetailPage', params: { newsId: this.news.id } });
        })
    }

    // 格式化时间显示
    private formatTime(timestamp: number): string {
        const date = new Date(timestamp);
        return `${date.getMonth() + 1}月${date.getDate()}日`;
    }

    // 切换收藏状态
    private async toggleFavorite() {
        try {
            this.news.isFavorite = !this.news.isFavorite;
            await this.cloudDBZone.executeUpsert([this.news]);
        } catch (err) {
            console.error('Toggle favorite failed: ' + JSON.stringify(err));
        }
    }
}

五、新闻详情页面实现

新闻详情页需要展示完整的新闻内容,并支持收藏功能。

// pages/NewsDetailPage.ets
import { News } from '../model/News';

@Entry
@Component
struct NewsDetailPage {
    @State news: News = new News();
    private newsId: string = '';

    onPageShow() {
        this.newsId = router.getParams()?.newsId;
        this.loadNewsDetail();
    }

    build() {
        Column() {
            // 返回按钮和标题
            Row() {
                Image($r('app.media.ic_back'))
                    .width(24)
                    .height(24)
                    .onClick(() => {
                        router.back();
                    })
                Text('新闻详情')
                    .fontSize(20)
                    .fontWeight(FontWeight.Bold)
                    .margin({ left: 15 })
                Blank()
                Image(this.news.isFavorite ? $r('app.media.ic_favorite') : $r('app.media.ic_favorite_border'))
                    .width(24)
                    .height(24)
                    .onClick(() => {
                        this.toggleFavorite();
                    })
            }
            .width('100%')
            .padding(15)
            .backgroundColor('#FF4D4F')

            // 内容区域
            Scroll() {
                Column() {
                    // 标题和作者信息
                    Text(this.news.title)
                        .fontSize(22)
                        .fontWeight(FontWeight.Bold)
                        .margin({ bottom: 10 })
                    Row() {
                        Text(`作者:${this.news.author}`)
                            .fontSize(14)
                            .fontColor('#666')
                        Text(`发布时间:${this.formatTime(this.news.publishTime)}`)
                            .fontSize(14)
                            .fontColor('#666')
                            .margin({ left: 15 })
                    }
                    .margin({ bottom: 15 })

                    // 新闻图片
                    Image(this.news.imageUrl)
                        .width('100%')
                        .height(250)
                        .objectFit(ImageFit.Cover)
                        .margin({ bottom: 20 })

                    // 新闻内容
                    Text(this.news.content)
                        .fontSize(16)
                        .lineHeight(26)
                }
                .padding(20)
            }
            .scrollBar(BarState.Off)
            .layoutWeight(1)
        }
        .width('100%')
        .height('100%')
    }

    // 加载新闻详情
    private async loadNewsDetail() {
        try {
            const query = cloud.CloudDBZoneQuery.where(News).equalTo('id', this.newsId);
            const result = await this.cloudDBZone.executeQuery(query, News);
            if (result && result.length > 0) {
                this.news = result[0];
            }
        } catch (err) {
            console.error('Load news detail failed: ' + JSON.stringify(err));
        }
    }

    // 切换收藏状态
    private async toggleFavorite() {
        try {
            this.news.isFavorite = !this.news.isFavorite;
            await this.cloudDBZone.executeUpsert([this.news]);
        } catch (err) {
            console.error('Toggle favorite failed: ' + JSON.stringify(err));
        }
    }

    // 格式化时间显示
    private formatTime(timestamp: number): string {
        const date = new Date(timestamp);
        return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`;
    }
}

六、用户认证与收藏功能

为了让用户能够收藏新闻,我们需要实现用户认证功能。使用AppGallery Connect的认证服务可以快速实现。

// utils/AuthUtil.ts
import { agconnect } from '@hw-agconnect/api-ohos';
import '@hw-agconnect/auth-ohos';

export class AuthUtil {
    // 匿名登录
    static async anonymousLogin(): Promise<boolean> {
        try {
            const user = await agconnect.auth().signInAnonymously();
            return user != null;
        } catch (err) {
            console.error('Anonymous login failed: ' + JSON.stringify(err));
            return false;
        }
    }

    // 获取当前用户ID
    static getCurrentUserId(): string | null {
        const user = agconnect.auth().currentUser;
        return user ? user.uid : null;
    }

    // 登出
    static async logout(): Promise<void> {
        try {
            await agconnect.auth().signOut();
        } catch (err) {
            console.error('Logout failed: ' + JSON.stringify(err));
        }
    }
}

修改新闻模型,加入用户关联:

// model/News.ts
export class News {
    // ...原有字段
    
    @cloud.Field()
    userId: string = ''; // 关联用户ID

    // ...其他代码
}

收藏功能需要检查用户登录状态:

// 在NewsItem组件中修改toggleFavorite方法
private async toggleFavorite() {
    if (!AuthUtil.getCurrentUserId()) {
        const isLogin = await AuthUtil.anonymousLogin();
        if (!isLogin) {
            prompt.showToast({ message: '请先登录' });
            return;
        }
    }
    
    try {
        this.news.isFavorite = !this.news.isFavorite;
        this.news.userId = AuthUtil.getCurrentUserId() || '';
        await this.cloudDBZone.executeUpsert([this.news]);
    } catch (err) {
        console.error('Toggle favorite failed: ' + JSON.stringify(err));
    }
}

七、应用打包与发布

完成开发后,我们需要将应用打包并发布到AppGallery:

  1. 在DevEco Studio中选择Build -> Generate Key and CSR生成签名证书
  2. 配置签名信息在build-profile.json5
  3. 选择Build -> Build HAP(s)/APP(s) -> Build APP生成发布包
  4. 登录AppGallery Connect控制台,进入"我的应用"创建新应用
  5. 上传生成的APP文件,填写应用信息,提交审核

八、进阶功能建议

完成基础功能后,可以考虑添加以下进阶功能:

  1. 分类筛选:在新闻列表顶部添加分类标签,实现按分类筛选
  2. 评论功能:为新闻添加评论功能,使用云数据库存储评论数据
  3. 离线阅读:使用本地数据库存储已读新闻,支持离线查看
  4. 推送通知:集成AppGallery Connect的推送服务,推送热点新闻
  5. 多设备协同:利用HarmonyOS的分布式能力,实现手机与平板间的无缝切换

通过本教程,我们完成了一个基于HarmonyOS Next的新闻类应用开发,涵盖了从环境搭建到功能实现的完整流程。希望这能帮助你快速入门HarmonyOS应用开发,并在此基础上开发出更丰富的功能。


林钟雪
4 声望0 粉丝