HarmonyOS 应用开发-自定义Swiper卡片预览效果实现

news/2024/7/21 12:28:11 标签: harmonyos, 华为, 网络, 鸿蒙开发, 服务器, 运维

介绍

本方案做的是采用Swiper组件实现容器视图居中完全展示,两边等长露出,并且跟手滑动效果。

效果图预览

实现思路

本解决方案通过维护所有卡片偏移的数组,实时更新卡片的偏移量,以实现swiper子组件内图片居中展示,两边等长露出。

  1. 左右露出效果静态实现。

Swiper组件基础视图效果如下。

如果所有子组件卡片大小一样,子组件内卡片居中展示即可实现效果。但是当子组件的卡片大小不一样时,无法通过简单的设置居中布局实现左右的等长露出。
此时需要计算当前状态下的卡片的偏移量。

  /**
   * 计算指定卡片的最大偏移量。
   * @param index {number} target card's index.
   * @returns offset value.
   */
  getMaxOffset(index: number): number {
    /*
     * 这里的偏移量指相对容器左侧的值。
     * 计算公式为:屏幕宽度 - Swiper两侧突出的偏移量 - 卡片自身的宽度。
     * 此值即为卡片可偏移的最大值,也就是卡片右对齐的状态值。
     * 如果居中,则将最大偏移量 / 2。
     */
    return this.displayWidth - this.cardsList[index].width - 2 * this.swiperMargin;
  }
        
  /**
   * 计算卡片偏移量,并维护偏移量列表。
   * @param targetIndex { number } swiper target card's index.
   */
  calculateOffset(target: number) {
    let left = target - 1;
    let right = target + 1;

    // 计算上一张卡片的偏移值
    if (this.isIndexValid(left)) {
      this.cardsOffset[left] = this.getMaxOffset(left);
    }
    // 计算当前卡片的偏移值
    if (this.isIndexValid(target)) {
      this.cardsOffset[target] = this.getMaxOffset(target) / 2;
    }
    // 下一张片的偏移值
    if (this.isIndexValid(right)) {
      this.cardsOffset[right] = 0;
    }
  }
  1. 滑动跟手实现

滑动swiper组件动态位置更新原理和上一步静态位置获取原理一样,只不过在滑动过程通过相应的回调函数实时位置更新。
在以下这三个swiper回调接口中,分别实现卡片跟手、离手、导航点切换时的卡片偏移量更新

接口名基本功能
onGestureSwipe在页面跟手滑动过程中,逐帧触发该回调。
onAnimationStart切换动画开始时触发该回调。
onChange子组件索引变化时触发该事件。

具体api接口信息查看:Swiper事件。

  • 在onGestureSwiper回调中,根据手指滑动的距离实时维护卡片的偏移量。
.onGestureSwipe((index, event) => {
  let currentOffset = event.currentOffset;
  // 获取当前卡片(居中)的原始偏移量
  let maxOffset = this.getMaxOffset(index) / 2;
  // 实时维护卡片的偏移量列表,做到跟手效果
  if (currentOffset < 0) {
    // 向左偏移
    /*
     * 此处计算原理为:按照比例设置卡片的偏移量。
     * 当前卡片居中,向左滑动后将在左边,此时卡片偏移量即为 maxOffset * 2(因为向右对齐)。
     * 所以手指能够滑动的最大距离(this.displayWidth)所带来的偏移量即为 maxOffset。
     * 易得公式:卡片实时偏移量 = (手指滑动长度 / 屏幕宽度) * 卡片最大可偏移量 + 当前偏移量。
     * 之后的计算原理相同,将不再赘述。
     */
    this.cardsOffset[index] = (-currentOffset / this.displayWidth) * maxOffset + maxOffset;
    if (this.isIndexValid(index + 1)) {
      // 下一个卡片的偏移量
      let maxOffset = this.getMaxOffset(index + 1) / 2;
      this.cardsOffset[index + 1] = (-currentOffset / this.displayWidth) * maxOffset;
    }
    if (this.isIndexValid(index - 1)) {
      // 上一个卡片的偏移量
      let maxOffset = this.getMaxOffset(index - 1) / 2;
      this.cardsOffset[index - 1] = (currentOffset / this.displayWidth) * maxOffset + 2 * maxOffset;
    }
  } else if (currentOffset > 0) {
    // 向右滑动
    this.cardsOffset[index] = maxOffset - (currentOffset / this.displayWidth) * maxOffset;
    if (this.isIndexValid(index + 1)) {
      let maxOffset = this.getMaxOffset(index + 1) / 2;
      this.cardsOffset[index + 1] = (currentOffset / this.displayWidth) * maxOffset;
    }
    if (this.isIndexValid(index - 1)) {
      let maxOffset = this.getMaxOffset(index -1) / 2;
      this.cardsOffset[index - 1] = 2 * maxOffset - (currentOffset / this.displayWidth) * maxOffset;
    }
  }
})
  • 在onAnimationStart回调中,计算手指离开屏幕时卡片的偏移量,避免产生突变的偏移量。
.onAnimationStart((_, targetIndex) => {
  this.calculateOffset(targetIndex);
})

这里的 calculateOffset 函数即步骤1中维护卡片偏移量的函数。

  • 在onChange回调中提前计算Swiper滑动后卡片的位置。
.onChange((index) => {
  logger.info(TAG, `Target index: ${index}`);
  this.calculateOffset(index);
})

计算方式同上一步。

  1. 图片预览效果实现

图片预览动效是通过共享元素转场结合全屏模态实现的。
通过geometryTransition属性绑定两个需要“一镜到底”的组件(本案例中的图片),结合模态窗口转场即可。

// 以下代码仅展示关键部分,详请查看源码
Row() {
  Image(this.cardInfo.src)
    .objectFit(ImageFit.Cover)
    .borderRadius($r('app.integer.photo_radius'))
      // TODO 知识点:geometryTransition通过id参数绑定两个组件转场关系,实现一镜到底动画
    .geometryTransition(this.cardIndex.toString(), { follow: true })
    .transition(TransitionEffect.OPACITY.animation({ duration: Constants.DURATION, curve: Curve.Friction }))
}
...
.bindContentCover(
  this.isPhotoShow,
  this.photoShowBuilder(this.cardInfo.src, this.cardIndex.toString()),
  { backgroundColor: $r('app.color.photo_preview_build_background'), modalTransition: ModalTransition.ALPHA }
)
...
// 全屏模态组件
@Builder photoShowBuilder(img: Resource, id: string) {
  Column() {
    Image(img)
      .borderRadius($r('app.integer.photo_radius'))
      .geometryTransition(id, { follow: true })
      .width($r('app.string.photo_preview_width'))
      .transition(TransitionEffect.opacity(Constants.OPACITY))
  }
  ...
  .onClick(() => {
    animateTo({
      duration: Constants.DURATION,
      curve: Curve.Friction
    }, () => {
      this.isPhotoShow = !this.isPhotoShow;
    })
  })
}

高性能知识点

  • 本示例使用了LazyForEach进行数据懒加载以降低内存占用。
  • Swiper组件的onGestureSwipe事件是个高频回调,注意在里面不要调用冗余操作和耗时操作。

工程结构&模块类型

cardswiperanimation            // har包
 ├─components
 │  ├─mainpage
 │  │  └─ CardSwiper.ets       // 卡片滑动组件入口
 │  ├─model
 │  │  └─ CardModel.ets        // 定义卡片类型
 │  ├─viewmodel
 │     └─ CardViewModel.ets    // 定义卡片组件
 ├─utils
 │  ├─ Constants.ets           // 常量数据

模块依赖

  • routermodule

参考资料

  • Swiper容器组件
  • 共享元素转场

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.
鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向


http://www.niftyadmin.cn/n/5477857.html

相关文章

Rust---模式(Pattern)匹配

目录 模式是什么它用来做什么模式匹配和赋值为什么会有模式匹配模式匹配用在什么地方match 表达式if let表达式while let表达式for 循环let 语句函数参数不可驳模式匹配和可驳模式匹配模式是什么 在Rust中,模式(Pattern)是一种用于匹配和解构数据的语法结构。模式匹配中常用…

鸿蒙OS实战开发:【多设备自适应服务卡片】

介绍 服务卡片的布局和使用&#xff0c;其中卡片内容显示使用了一次开发&#xff0c;多端部署的能力实现多设备自适应。 用到了卡片扩展模块接口&#xff0c;[ohos.app.form.FormExtensionAbility] 。 卡片信息和状态等相关类型和枚举接口&#xff0c;[ohos.app.form.formInf…

无处不在的 AI

无处不在的 AI 无处不在的 AIGPT-4云产品中无形的 AIAIGC虚拟数字人 操作系统中的 AI写在最后 在正文开始之前先欣赏一下关键词AIGC生成的图片 整体的意境很好&#xff0c;这里说明一下&#xff0c;这是通义万相AI文生图的效果哈。下面再回归想说的。 无处不在的 AI 说到 AI…

互动内容的魅力:Kompas.ai在提升用户参与度中的角色

在数字营销的世界中&#xff0c;互动内容已经成为提升用户参与度和用户体验的重要工具。互动内容不仅能够吸引用户的注意力&#xff0c;还能够提供更加个性化和有趣的用户体验。本文将深入分析互动内容对于吸引用户参与和提升用户体验的重要性&#xff0c;详细介绍Kompas.ai如何…

Python学习从0开始——专栏汇总

Python学习从0开始——000参考 一、推荐二、基础三、项目一 一、推荐 Hello World in Python - 这个项目列出了用Python实现的各种"Hello World"程序。 Python Tricks - 这个项目包含了Python中的高级技巧和技术。 Think Python - 这是一本教授Python的在线书籍&…

Redis性能管理和集群的三种模式(二)

一、Redis集群模式 1.1 redis的定义 redis 集群 是一个提供高性能、高可用、数据分片、故障转移特性的分布式数据解决方案 1.2 redis的功能 数据分片&#xff1a;redis cluster 实现了数据自动分片&#xff0c;每个节点都会保存一份数据故障转移&#xff1a;若个某个节点发生故…

C语言面试题之化栈为队

化栈为队 实例要求 C语言实现实现一个MyQueue类&#xff0c;该类用两个栈来实现一个队列&#xff1b;示例&#xff1a; MyQueue queue new MyQueue();queue.push(1); queue.push(2); queue.peek(); // 返回 1 queue.pop(); // 返回 1 queue.empty(); // 返回 false说明&…

【ArcGIS 小技巧】隐藏tif影像的黑边或白边

tif影像是规划中常用的参考数据。 但是当我们把tif拖入到ArcGIS中查看时&#xff0c;经常会出现黑色或白色的多余区域遮挡显示。 下面介绍我碰到的两种情况及解决办法。 1、三波段tif 三波段指的是R、G、B三个波段&#xff0c;可在tif影像的属性中查看是否有RGB三波段。 这…