在今天的博文中,我们将深入探讨如何在 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
其他资源
滑块视图容器
文档中心