当前位置: 首页 > news >正文

HarmonyOS NEXT 应用开发实战(七、知乎日报轮播图的完整实现)

在今天的博文中,我们将深入探讨如何在 HarmonyOS NEXT 中使用 ArkUI 实现一个轮播图组件。我们将通过一个示例代码来演示这个完整的过程,其中包含获取数据、管理数据源以及渲染组件等多个部分。 

 先来看下最终实现效果:

项目准备

首先,我们需要导入一些必要的模块和API,如下所示:

import { getSwiperList } from "../../common/api/home";
import { getZhiHuNews } from '../../common/api/zhihu';
import { BaseResponse, ErrorResp, ZhiNewsItem } from '../../common/bean/ApiTypes';
import { Log } from '../../utils/logutil';
import { formatDate } from '../../utils/time';

在这些模块中,getSwiperList 和 getZhiHuNews 是用于获取轮播图和知乎新闻数据的API。

api接口的定义十分简洁,实现如下:

import { setRequestConfig } from '../../utils/http';
import { BaseResponse,ZhiNewsRespData,ZhiDetailRespData } from '../bean/ApiTypes';// 调用setRequestConfig函数进行请求配置
setRequestConfig();const http = globalThis.$http;// 获取知乎列表页api接口
export const getZhiHuNews = (date:string): Promise<BaseResponse<ZhiNewsRespData>> => http.get('/zhihunews/'+date);// 获取知乎详情页api接口
export const getZhiHuDetail = (id:string): Promise<BaseResponse<ZhiDetailRespData>> => http.get('/zhihudetail/'+id);

api接口如何做到如此简洁的?这里推荐博主开源的官方http网络库封装:

HarmonyOS NEXT应用开发实战(二、封装比UniApp和小程序更简单好用的网络库)_鸿蒙网络库-CSDN博客 

数据源管理

这部分才是重点!在轮播图组件中,我们需要管理数据源。这部分使用了懒加载LazyForEach。该方法需要我们先定义加载数据源,我们首先编写一个泛型的公共数据源类,该类需要实现IDataSource接口,并重写totalCount、getData等方法。在这里,我们定义了两个数据源类:BasicDataSource 和 SwiperDataSource。这两个类可以帮助我们管理数据的监听和更新。

代码如下所示:

class BasicDataSource<T> implements IDataSource {// 监听器和数据数组private listeners: DataChangeListener[] = [];private originDataArray: T[] = [];totalCount(): number {return this.originDataArray.length;}getData(index: number): T {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {// 注册数据变化监听器}unregisterDataChangeListener(listener: DataChangeListener): void {// 反注册数据变化监听器}// 通知重新加载数据notifyDataReload() { /* ... */ }// 通知添加数据notifyDataAdd(index: number) { /* ... */ }
}class SwiperDataSource<T> extends BasicDataSource<T> {private dataArray: T[] = [];totalCount(): number {return this.dataArray.length;}getData(index: number): T {return this.dataArray[index];}pushData(data: T): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}reloadData(): void {this.dataArray = [];this.notifyDataReload();}
}

组件实现

接下来是主组件的实现,在 ZhiHu 类中,我们使用 Swiper 组件来创建轮播图。

@Component
export default struct ZhiHu {// 状态管理@State message: string = 'Hello World';private swiperController: SwiperController = new SwiperController();private swiperData: SwiperDataSource<ZhiNewsItem> = new SwiperDataSource();@State zhiNews: ZhiNewsItem[] = [];private currentDate = '';// 组件生命周期aboutToAppear() {getSwiperList().then((res) => {// 处理响应数据}).catch((err: BaseResponse<ErrorResp>) => {// 错误处理});getZhiHuNews(this.currentDate).then((res) => {this.zhiNews = res.data.stories;for (const itm of res.data.top_stories) {this.swiperData.pushData(itm);}}).catch((err: BaseResponse<ErrorResp>) => {// 错误处理});}build() {Navigation(this.pageStack) {Row() {Column({ space: 0 }) {// 标题栏Text("日报").size({ width: '100%', height: 50 }).backgroundColor("#28bff1").fontColor("#ffffff").textAlign(TextAlign.Center).fontSize("18fp");// 轮播图组件Swiper(this.swiperController) {LazyForEach(this.swiperData, (item: ZhiNewsItem) => {Stack({ alignContent: Alignment.Center }) {Image(item.image).width('100%').height(180).backgroundColor(0xAFEEEE).onClick(() => {this.pageStack.pushDestinationByName("PageOne", { id: item.id });});Text(item.title).padding(5).margin({ top: 60 }).width('100%').height(50).textAlign(TextAlign.Center).maxLines(2).textOverflow({ overflow: TextOverflow.Clip });}}, (item: ZhiNewsItem) => item.id).autoPlay(true).interval(4000).loop(true);}}}}.mode(NavigationMode.Stack).width('100%').height('100%');}
}

完整代码 

import {getSwiperList,getHotMovie} from "../../common/api/home"
import { getZhiHuNews } from '../../common/api/zhihu';
import { BaseResponse,ErrorResp,ZhiNewsItem } from '../../common/bean/ApiTypes';
import { Log } from '../../utils/logutil'
import { formatDate } from '../../utils/time';class BasicDataSource<T> implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: T[] = [];totalCount(): number {return this.originDataArray.length;}getData(index: number): T {return this.originDataArray[index];}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {this.listeners.push(listener);}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {this.listeners.slice(pos, 1);}}// 通知LazyForEach组件需要重新重载所有子组件notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}// 通知LazyForEach组件需要在index对应索引处添加子组件notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}
}class SwiperDataSource<T> extends BasicDataSource<T> {private dataArray: T[] = [];totalCount(): number {return this.dataArray.length;}getData(index: number): T {return this.dataArray[index];}// 在列表末尾添加数据并通知监听器pushData(data: T): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}// 重载数据reloadData(): void {// 不会引起状态变化this.dataArray = [];// 必须通过DataChangeListener来更新this.notifyDataReload();}
}@Component
export default struct ZhiHu{@State message: string = 'Hello World';private swiperController: SwiperController = new SwiperController()private swiperData: SwiperDataSource<ZhiNewsItem> = new SwiperDataSource()@State zhiNews:ZhiNewsItem[] = []private currentDate= '' // 初始化为今天的日期private previousDate= '' // 上一天的日期pageStack: NavPathStack = new NavPathStack()// 组件生命周期aboutToAppear() {Log.info('ZhiHu aboutToAppear');this.currentDate = formatDate(new Date())getSwiperList().then((res) => {Log.debug(res.data.message)Log.debug("request","res.data.code:%{public}d",res.data.code)Log.debug("request","res.data.data[0]:%{public}s",res.data.data[0].id)Log.debug("request","res.data.data[0]:%{public}s",res.data.data[0].imageUrl)Log.debug("request","res.data.data[0]:%{public}s",res.data.data[0].title)}).catch((err:BaseResponse<ErrorResp>) => {Log.debug("request","err.data.code:%d",err.data.code)Log.debug("request",err.data.message)});//获取知乎新闻列表getZhiHuNews(this.currentDate).then((res) => {Log.debug(res.data.message)Log.debug("request","res.data.code:%{public}d",res.data.code)this.zhiNews = res.data.storiesfor (const itm of res.data.top_stories) {this.swiperData.pushData(itm)}}).catch((err:BaseResponse<ErrorResp>) => {Log.debug("request","err.data.code:%d",err.data.code)Log.debug("request",err.data.message)});}// 组件生命周期aboutToDisappear() {Log.info('ZhiHu aboutToDisappear');}build() {Navigation(this.pageStack){Row() {Column({ space: 0 }) {// 标题栏Text("日报").size({ width: '100%', height: 50 }).backgroundColor("#28bff1").fontColor("#ffffff").textAlign(TextAlign.Center).fontSize("18fp")// 内容项Swiper(this.swiperController) {LazyForEach(this.swiperData, (item: ZhiNewsItem) => {Stack({ alignContent: Alignment.Center }) {Image(item.image).width('100%').height(180).backgroundColor(0xAFEEEE).zIndex(1).onClick(() => {//this.pageStack.pushPathByName("PageOne", item)this.pageStack.pushDestinationByName("PageOne", { id:"9773231" }).catch((e:Error)=>{// 跳转失败,会返回错误码及错误信息console.log(`catch exception: ${JSON.stringify(e)}`)}).then(()=>{// 跳转成功});})// 显示轮播图标题Text(item.title).padding(5).margin({ top:60 }).width('100%').height(50).textAlign(TextAlign.Center).maxLines(2).textOverflow({overflow:TextOverflow.Clip}).fontSize(20).fontColor(Color.White).opacity(100) // 设置标题的透明度 不透明度设为100%,表示完全不透明.backgroundColor('#808080AA') // 背景颜色设为透明.zIndex(2)}}, (item: ZhiNewsItem) => item.id)}.cachedCount(2).index(1).autoPlay(true).interval(4000).loop(true).indicatorInteractive(true).duration(1000).itemSpace(0).curve(Curve.Linear).onChange((index: number) => {console.info(index.toString())}).onGestureSwipe((index: number, extraInfo: SwiperAnimationEvent) => {console.info("index: " + index)console.info("current offset: " + extraInfo.currentOffset)}).height(180) // 设置高度List({ space: 12 }) {ForEach(this.zhiNews, (item:ZhiNewsItem) => {ListItem() {Column({ space: 0 }) {Row() {Column({ space: 15 }) {Text(item.title).fontSize(16).fontWeight(FontWeight.Bold).align(Alignment.Start).width('100%')Text(item.hint).align(Alignment.Start).width('100%')}.justifyContent(FlexAlign.Start).width('70%').padding(5)Image(item.image).objectFit(ImageFit.Cover).borderRadius(5).height(100).padding(2)}.size({ width: '100%', height: 100 })Divider().strokeWidth(2).color('#F1F3F5')}.size({ width: '100%', height: 100 })}}, (itm:ZhiNewsItem) => itm.id)}.size({ width: '100%', height: '100%' })}.size({ width: '100%', height: '100%' })}}.mode(NavigationMode.Stack).width('100%').height('100%')}
}

项目开源地址

zhihudaily: HarmonyOS NEXT 项目开发实战,仿知乎日报的实现 

写在最后

最后,推荐下笔者的业余开源app影视项目“爱影家”,推荐分享给与我一样喜欢免费观影的朋友。【注:该项目仅限于学习研究使用!请勿用于其他用途!】

开源地址:爱影家app开源项目介绍及源码

https://gitee.com/yyz116/imovie

 其他资源

滑块视图容器

文档中心


http://www.mrgr.cn/news/53870.html

相关文章:

  • unity学习笔记-安装与部署
  • 接口测试(三)jmeter——连接mysql数据库
  • 基于MEDLL的Loran-C天地波周期联合识别及信号跟踪方法及MATLAB仿真代码和实测信号处理
  • 内网穿透很简单
  • (六) 进程控制
  • Java 多线程(五)—— 阻塞队列、wait、notify
  • 集合论基本概念——紧致性(compact或compactness)
  • 【v5Lite】识别+串口
  • 大厂面试真题-了解云原生吗,简单说一下docker和k8s
  • vue之打包配置环境
  • Qt贪吃蛇-游戏房间窗口(3)
  • 设置了超时时间但是不起作用,浏览器里的setTimeout有 bug?
  • Linux下的进程解析(level 2)
  • 闪迪sd卡如何恢复删除的内容?这3种方法很实用
  • 【数据库系统概论】关系数据库标准语言SQL(一)数据定义【超详细】
  • c++基础知识1
  • MYSQL OPTIMIZE TABLE 命令重建表和索引
  • C++初阶——入门
  • vuex的store应用
  • 枸杞常见病虫害识别数据集(猫脸码客 第220期)
  • 【C++笔试强训】如何成为算法糕手Day11
  • Python编程探索:从基础语法到循环结构实践(下)
  • Unity Apple Vision Pro 自定义手势识别交互
  • 编写自定义组件props报错 TypeError: Right-hand side of ‘instanceof‘ is not an object的解决方法
  • AI论文写作:如何轻松实现高原创度大揭秘
  • 一次恶意程序分析