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

news2024/11/28 9:33:36

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

 其他资源

滑块视图容器

文档中心

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2219035.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JMeter之mqtt-jmeter 插件介绍

前言 mqtt-jmeter插件是JMeter中的一个第三方插件&#xff0c;用于支持MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;协议的性能测试。MQTT是一种轻量级的发布/订阅消息传输协议&#xff0c;广泛应用于物联网和传感器网络中。 一、安装插件 mqtt-jmeter项目…

【智能算法应用】雪消融优化算法求解二维路径规划问题

摘要 本文研究了雪消融优化算法在二维路径规划问题中的应用。该算法基于自然界中雪消融现象的模拟&#xff0c;通过优化策略寻找无人机路径的最优解。实验结果表明&#xff0c;该算法在路径规划中表现出较高的效率和收敛速度&#xff0c;能够有效地避开障碍物并找到代价最低的…

基于SpringBoot+Vue的校园周边美食探索及分享平台的设计与实现(带文档)

基于SpringBootVue的校园周边美食探索及分享平台的设计与实现&#xff08;带文档) 开发语言:Java数据库:MySQL技术:SpringBootMyBatisVue等工具:IDEA/Ecilpse、Navicat、Maven 源码 校园周边美食探索及分享平台是一个旨在为校园用户提供便捷的美食发现和分享服务的系统。该平…

我的JAVA项目构建

1.Maven maven就是pip 设置maven下载的的jar包位置 换源 下载插件maven-search 配置dependency 2.Tomcat 设置环境变量JAVA_HOME 设置编码方式 方框就是路径的前缀 3.Servlet 新建项目 写一个类继承HttpServlet&#xff0c;复写doGet(应对Get请求)&#xff0c;doPost(应对…

vue组件传值之$attrs

1.概述&#xff1a;$attrs用于实现当前组件的父组件&#xff0c;向当前组件的子组件通信&#xff08;祖-》孙&#xff09; 2.具体说明&#xff1a;$attrs是一个对象&#xff0c;包含所有父组件传入的标签属性。 注意&#xff1a;$attrs会自动排除props中声明的属性&#xff0…

从0开始深度学习(14)——模型选择、欠拟合、过拟合

① 模型在训练数据上拟合的比在潜在分布中更接近的现象&#xff0c;就叫过拟合&#xff08;overfitting&#xff09; ② 用于对抗过拟合的技术称为正则化&#xff08;regularization&#xff09; 1 训练误差和泛化误差 ①训练误差&#xff08;training error&#xff09;&…

scrapy 爬虫学习之【中医药材】爬虫

本项目纯学习使用。 1 scrapy 代码 爬取逻辑非常简单&#xff0c;根据url来处理翻页&#xff0c;然后获取到详情页面的链接&#xff0c;再去爬取详情页面的内容即可&#xff0c;最终数据落地到excel中。 经测试&#xff0c;总计获取 11299条中医药材数据。 import pandas as…

CTFHUB技能树之HTTP协议——响应包源代码

开启靶场&#xff0c;打开链接&#xff1a; 是个贪吃蛇小游戏&#xff0c;看不出来有什么特别的地方 用burp抓包看看情况&#xff1a; 嗯&#xff1f;点击“开始”没有抓取到报文&#xff0c;先看看网页源代码是什么情况 居然直接给出flag了&#xff0c;不知道这题的意义何在 …

某MDM主数据管理系统与微软Dynamic CRM系统(国内节点)集成案例

一、需求分析 需要完成的核心场景&#xff1a; 客户主数据&#xff1a;通过SAP PO集成中间件平台&#xff0c;某MDM主数据实时推送客户主数据信息至微软CRM系统&#xff0c;方便微软CRM系统进行客户方面的管理&#xff0c;并供微软CRM查询员工信息&#xff0c;修改员工&…

IDEA运行Java程序时出错。提示:命令行过长。通过 JAR 清单或通过类路径文件缩短命令行,然后重新运行。

文章目录 一、遇到问题二、分析问题三、解决办法 一、遇到问题 运行 OpenCVUtils.test 时出错。命令行过长。 通过 JAR 清单或通过类路径文件缩短命令行&#xff0c;然后重新运行。二、分析问题 IDEA提示很明显了。 三、解决办法 运行——>编辑配置 运行/调试配置——&g…

024_Symbolic_Math_in_Matlab符号数学工具箱的使用思路

符号运算与数值计算 缘&#xff0c;妙不可言 给本科、硕士、博士、研究实习员、助理研究员、副研究员改过Matlab代码&#xff0c;最有意思也最好玩的就是兄弟姐妹们喜欢把符号运算跟数值计算混合在一起。 从概念上看&#xff0c;还是挺不错的。 大佬们的计划都是这样的&…

64页精品PPT | 汽车经销商数据应用解决方案

汽车经销商正面临前所未有的盈利能力挑战。从18年起 &#xff0c;传统燃油车汽车行业开始步入低速增长阶段 &#xff0c;卖车已经挣不到钱 &#xff0c;利润往往来自任务完成的厂家返利&#xff1b;新兴的直营模式的出现 &#xff0c;冲击了传统授权经销的方式 &#xff0c;疫情…

车辆管理新篇章:SpringBoot技术解析

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

sns数据分析

探索性数据分析 这一部分目的在于了解数据&#xff0c;包括数据是什么类型&#xff0c;数据有什么特点 数据信息 print(data.shape) data.info()(1086, 12) <class pandas.core.frame.DataFrame> Index: 1086 entries, 2020/7/1 0:00 to nan Data columns (total 12 c…

鸿蒙网络编程系列24-Web组件与应用互操作示例

1. APP内嵌网页与应用互操作概述 在通常的APP开发中&#xff0c;经常会采用内嵌网页的形式&#xff0c;通过网页来展现丰富的动态内容&#xff0c;虽少了很多原生开发的功能&#xff0c;但是这么做无可厚非&#xff0c;毕竟APP需要适配的系统平台很多&#xff0c;比如安卓、苹…

leetcode289:生命游戏

根据 百度百科 &#xff0c; 生命游戏 &#xff0c;简称为 生命 &#xff0c;是英国数学家约翰何顿康威在 1970 年发明的细胞自动机。 给定一个包含 m n 个格子的面板&#xff0c;每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态&#xff1a; 1 即为 活细胞 &am…

babylonjs shader学习之copy shadertoy案例

shadertoy案例&#xff1a; 准备 const onSceneReady (scene: Scene) > {const light new HemisphericLight(light, new Vector3(0, 1, 0), scene);light.intensity 0.7;Effect.ShadersStore[planeMatVertexShader] precision highp float;attribute vec3 position;attr…

SpringMVC一个拦截器和文件上传下载的完整程序代码示例以及IDEA2024部署报错 找不到此 Web 模块的 out\artifacts\..问题

一、SpringMVC一个拦截器和文件上传下载的完整程序代码示例 本文章是一个 SpringMVC拦 截器和文件上传下载的完整程序代码示例&#xff0c;使用的开发工具是 IntelliJ IDEA 2024.1.6 (Ultimate Edition)&#xff0c; 开发环境是 OpenJDK-21 java version 21.0.2。Tomcatt版本为…

Flux.concat 使用说明书

public static <T> Flux<T> concat(Iterable<? extends Publisher<? extends T>> sources)Concatenate all sources provided in an Iterable, forwarding elements emitted by the sources downstream. 连接可迭代集合中提供的所有源&#xff0c;将…

【web】JDBC

项目连接数据库 右侧导航栏找到databsae 如果没有驱动&#xff0c;先下载驱动 填写数据库用户名密码 勾选对应的表即可 JDBC代码流程 1,配置信息 2,加载驱动 从MySQL Connector/J 5.1版本开始&#xff0c;推荐使用com.mysql.cj.jdbc.Driver这个新的驱动类。 3,链接数据库…