HarmonyOS应用开发实战:半天实现知乎日报项目(六、首页轮播图的完整实现)

2024-11-13 13:18:07
15次阅读
0个评论
最后修改时间:2024-11-13 15:11:48

 在今天的博文中,我们将深入探讨如何在 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.stories
          for (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

作者:猫哥 (blog.csdn.net/qq8864),转载请注明出处。

团队:坚果派 团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁夏等地,目前已开发鸿蒙原生应用,三方库60+,欢迎交流。

 其他资源

滑块视图容器

文档中心

收藏00

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