【Harmony】轮播图特效,持续更新中。。。。

news2024/11/13 6:47:50

效果预览
swiper官网例子
请添加图片描述
Swiper 高度可变化
请添加图片描述
两边等长露出,跟随手指滑动
请添加图片描述
Swiper 指示器导航点位于 Swiper 下方
请添加图片描述

一、官网 例子

请添加图片描述
参考代码:

// xxx.ets
class MyDataSource implements IDataSource {
  private list: number[] = []

  constructor(list: number[]) {
    this.list = list
  }

  totalCount(): number {
    return this.list.length
  }

  getData(index: number): number {
    return this.list[index]
  }

  registerDataChangeListener(listener: DataChangeListener): void {
  }

  unregisterDataChangeListener() {
  }
}

@Entry
@Component
struct SwiperExample {
  private swiperController: SwiperController = new SwiperController()
  private data: MyDataSource = new MyDataSource([])

  aboutToAppear(): void {
    let list: number[] = []
    for (let i = 1; i <= 10; i++) {
      list.push(i);
    }
    this.data = new MyDataSource(list)
  }

  build() {
    Column({ space: 5 }) {
      Swiper(this.swiperController) {
        LazyForEach(this.data, (item: string) => {
          Text(item.toString())
            .width('90%')
            .height(160)
            .backgroundColor(0xAFEEEE)
            .textAlign(TextAlign.Center)
            .fontSize(30)
        }, (item: string) => item)
      }
      .cachedCount(2)
      .index(1)
      .autoPlay(true)
      .interval(4000)
      .indicator(Indicator.digit() // 设置数字导航点样式
        .right("43%")
        .top(200)
        .fontColor(Color.Gray)
        .selectedFontColor(Color.Gray)
        .digitFont({ size: 20, weight: FontWeight.Bold })
        .selectedDigitFont({ size: 20, weight: FontWeight.Normal }))
      .loop(true)
      .duration(1000)
      .itemSpace(0)
      .displayArrow(true, false)

      Row({ space: 12 }) {
        Button('showNext')
          .onClick(() => {
            this.swiperController.showNext()
          })
        Button('showPrevious')
          .onClick(() => {
            this.swiperController.showPrevious()
          })
      }.margin(5)
    }.width('100%')
    .margin({ top: 5 })
  }
}

二、Swiper 高度可变化

请添加图片描述
主要逻辑代码:

// TODO: 知识点: Swiper组件绑定onGestureSwipe事件,在页面跟手滑动过程中,逐帧触发该回调
      // 性能知识点: onGestureSwipe属于频繁回调,不建议在onGestureSwipe做耗时和冗余操作
      .onGestureSwipe((index:number,extraInfo:SwiperAnimationEvent)=>{
        animateTo({
          duration: Constants.DURATION_SWIPER,
          curve: Curve.EaseOut,
          playMode: PlayMode.Normal,
          onFinish: () => {
            // logger.info('play end');
          }
        }, () => { // 通过左右滑动的距离来计算对应的上下位置的变化
          if (index === 0 && extraInfo.currentOffset < 0) {
            this.swiperDistance = extraInfo.currentOffset / Constants.SCROLL_WIDTH * Constants.SMALL_FONT_SIZE;
          } else if (index === 1 && extraInfo.currentOffset > 0) {
            this.swiperDistance = extraInfo.currentOffset / Constants.SCROLL_WIDTH * Constants.SMALL_FONT_SIZE - Constants.SMALL_FONT_SIZE;
          } else if (index === 2 && extraInfo.currentOffset < 0) {
            this.swiperDistance = extraInfo.currentOffset / Constants.SCROLL_WIDTH * Constants.GRID_SINGLE_HEIGHT - Constants.SMALL_FONT_SIZE;
          } else if (index === 3 && extraInfo.currentOffset > 0) {
            this.swiperDistance = extraInfo.currentOffset / Constants.SCROLL_WIDTH * Constants.GRID_SINGLE_HEIGHT - Constants.SMALL_FONT_SIZE - Constants.GRID_SINGLE_HEIGHT;
          }
        })
      })
      .onAnimationStart((_: number, targetIndex: number)=>{
        animateTo({
          duration: Constants.DURATION_DOWN_PAGE,
          curve: Curve.EaseOut,
          playMode: PlayMode.Normal,
          onFinish: () => {
            // logger.info('play end');
          }
        }, () => {
          if (targetIndex === 0) {
            this.swiperDistance = 0;
          } else if (targetIndex === 1 || targetIndex === 2) {
            this.swiperDistance = -Constants.SMALL_FONT_SIZE;
          } else {
            this.swiperDistance = -Constants.SMALL_FONT_SIZE - Constants.GRID_SINGLE_HEIGHT;
          }
        })
      })
      .indicator(new DotIndicator()
        // .selectedItemWidth($r('app.float.swipersmoothvariation_select_item_width'))
        .selectedItemWidth('18fp')
        // .selectedItemHeight($r('app.float.swipersmoothvariation_select_item_height'))
        .selectedItemHeight('3vp')
        // .itemWidth($r('app.float.swipersmoothvariation_default_item_width'))
        .itemWidth('5vp')
        // .itemHeight($r('app.float.swipersmoothvariation_default_item_height'))
        .itemHeight('-3vp')
        // .selectedColor($r('app.color.swipersmoothvariation_swiper_selected_color'))
        .selectedColor(Color.Yellow)
        // .color($r('app.color.swipersmoothvariation_swiper_unselected_color')))
        .color('#FFFF8662')
      )

逻辑结构相对复杂,请查看下面 demo 开源地址

三、Swiper 指示器导航点位于 Swiper 下方

请添加图片描述
主要是分离内容区域和空白区域给指示器留白蛤

  Column() {
      Swiper(this.swiperController){
        // TODO 高性能知识点:此处为了演示场景,列表数量只有3个,使用ForEach,列表数量较多的场景,推荐使用LazyForEach+组件复用+缓存列表项实现
        ForEach(this.swiperData,(item:Resource)=>{
          Column(){
            // TODO 知识点:将swiper区域分割成内容区和空白区
            Image(item)
              .width('100%')
              .height('22%')
              .borderRadius(10)

            Column()
              .width('100%')
              .height(50)
              .backgroundColor(Color.Gray)
          }
        })
      }
      .width('95%')
      .loop(true)
      .autoPlay(true)
      // TODO 知识点:通过indicator属性,将导航点放置到空白区域,实现指示器导航点位于swiper下方的效果
      .indicator(new DotIndicator().bottom(15))
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)

四、Swiper组件实现容器视图居中完全展示,两边等长露出,跟随手指滑动

请添加图片描述
逻辑简约描述:
难点在于偏移的计算
要特别注意宽度和高度值设置,保持统一单位。
在实际的开发过程中,因为单位的马虎导致即使代码是一样的,也出现过多次错位等问题
建议先完整参考代码写一遍之后再按实际需求进行偏移算法修改
比较烧脑,准备两罐红牛缓解疲劳蛤!

偏移计算:

/**
   * 计算卡片偏移量,并维护偏移量列表。
   * @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;
    }
  }

滑动触发偏移计算:

.onChange((index) => {
        // logger.info(TAG, `Target index: ${index}`);
        this.calculateOffset(index);
      })
      .onGestureSwipe((index, event) => {
        const currentOffset = event.currentOffset;
        // 获取当前卡片(居中)的原始偏移量
        const 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)) {
            // 下一个卡片的偏移量
            const maxOffset = this.getMaxOffset(index + 1) / 2;
            this.cardsOffset[index + 1] = (-currentOffset / this.displayWidth) * maxOffset;
          }
          if (this.isIndexValid(index - 1)) {
            // 上一个卡片的偏移量
            const 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)) {
            const maxOffset = this.getMaxOffset(index + 1) / 2;
            this.cardsOffset[index + 1] = (currentOffset / this.displayWidth) * maxOffset;
          }
          if (this.isIndexValid(index - 1)) {
            const maxOffset = this.getMaxOffset(index - 1) / 2;
            this.cardsOffset[index - 1] = 2 * maxOffset - (currentOffset / this.displayWidth) * maxOffset;
          }
        }
      })
      .onAnimationStart((index, targetIndex) => {
        this.calculateOffset(targetIndex);
      })

~~~~~~~~~~~待扩展

五、收尾两侧都有等长偏移的露出

六、卡片叠加楼层效果

开源 Demo 工程地址

Demo 工程

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

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

相关文章

python:Django与Celery配合实现定时任务

Celery是一个基于python开发的分布式任务队列&#xff0c;而做python WEB开发最为流行的框架莫属Django&#xff0c;但是Django的请求处理过程都是同步的无法实现异步任务&#xff0c;若要实现异步任务处理需要通过其他方式&#xff08;前端的一般解决方案是ajax操作&#xff0…

监控网线和电话线水晶头

监控网线 1、网络摄像机网线接口的线序与B类网线的对应关系&#xff08;表格从左到右代表线序1-8&#xff09; 表格解读&#xff1a; &#xff08;1&#xff09;请先查看摄像机网线对应的颜色&#xff0c;确定是第一种还是第二种摄像机类型 &#xff08;2&#xff09;确定好…

计算机网络基础 - 应用层(3)

计算机网络基础 应用层P2P 应用P2P 体系结构的扩展性BitTorrent 协议torrenl 洪流BitTorrent 运行的过程 P2P文件共享应用非结构化 P2PDHT 结构化 P2P&#xff08;了解&#xff09; 视频流和内容分发网视频流化服务HTTP 流和 DASH内容分发网 CDN面临挑战CDN 概述CDN 操作过程集…

MFC获取网页的html文本

使用 CInternetSession 类和 CHttpFile 类&#xff1b; 在stdafx.h中加入 #include <afxinet.h> &#xff1b; 基本的代码如下&#xff0c; void CMFCApplication3Dlg::OnBnClickedButton1() {// TODO: 在此添加控件通知处理程序代码try{CInternetSession session;CH…

4.事件组

事件组的本质:一个整数 里面的每一个bit,表示一类事件 任务A:可以等待这个整数的"bitx,bity,bitz....."都被设置为1. 这就是"AND"的关系 也可以等待这个整数的"bitx bity bitz..."任意一个被设置为1. 事件组有一个特别的地方在于: 1.假设任…

【QML 基础】QML ——描述性脚本语言,用于用户界面的编写

文章目录 1. QML 定义 1. QML 定义 &#x1f427; QML全称为Qt Meta-Object Language&#xff0c;QML是一种描述性的脚本语言&#xff0c;文件格式以.qml结尾。支持javascript形式的编程控制。QML是Qt推出的Qt Quick技术当中的一部分&#xff0c;Qt Quick是 Qt5中用户界面的涵…

React框架搭建,看这一篇就够了,看完你会感谢我

传统搭建框架的方式 在2024年以前&#xff0c;我们构建框架基本上采用官方脚手架&#xff0c;但是官方脚手架其实大概率都不符合我们的项目要求&#xff0c;搭建完了以后往往需要再继续集成一些第三方的包。这时候又会碰到一些版本冲突&#xff0c;配置教程等&#xff0c;往往…

C++入门基础知识九

1.string类对象的容量操作 函数名称功能说明size返回字符串有效长度length返回字符串有效长度capacity返回总空间大小empty检测字符串是否为空&#xff0c;为空返回true&#xff0c;否则falseclear清空有效字符reserve为字符预留空间number大小空间resize将有效字符改为n个&am…

Qt窗口——QToolBar

文章目录 工具栏创建工具栏设置toolTip工具栏配合菜单栏工具栏浮动状态 工具栏 QToolBar工具栏是应用程序中集成各种功能实现快捷键使用的一个区域。 可以有多个&#xff0c;也可以没有。 创建工具栏 #include "mainwindow.h" #include "ui_mainwindow.h&qu…

【Python报错已解决】ModuleNotFoundError: No module named ‘sklearn‘

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

App Fiddler抓包配置

1. 概述 Android Fiddler是一个非常强大的抓包工具&#xff0c;可以用来捕获并分析Android设备上的网络请求和响应。在实现"android fiddler 抓包https"的过程中&#xff0c;我们需要进行以下步骤&#xff1a; 安装Fiddler并配置代理 配置Android设备的网络代理 在Fi…

机器人的动力学——牛顿欧拉,拉格朗日,凯恩

机器人的动力学推导方法有很多&#xff0c;常用得有牛顿&#xff0c;拉格朗日&#xff0c;凯恩等方法&#xff0c;接下来&#xff0c;简单说说他们之间的使用。注&#xff1a;这里不考虑怎么来的&#xff0c;只说怎么应用。 参考1&#xff1a;4-14动力学分析方法-牛顿—欧拉方…

linux下的日志编写

1、日志初始化创建 2、日志写入 3、日志关闭 log.c #include "log.h"static log_t LOG;//初始化日志文件&#xff0c;在当前目录创建日志文件 int log_init(char *pdirname) {time_t t;struct tm *ptm NULL;char filepath[64] {0};int ret 0;time(&t);ptm …

MySQL_表的基本操作

课 程 推 荐我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448;入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448;虚 拟 环 境 搭 建 &#xff1a;&#x1…

Contact Form 7最新5.9.8版错误修复方案

最近有多位用户反应Contact Form 7最新5.9.8版的管理页面有错误如下图所示 具体错误文件的路径为wp-content\plugins\contact-form-7\admin\includes\welcome-panel.php on line 153 找到welcome-panel.php这个文件编辑它&#xff0c;将如下图选中的部分删除 删除以后&#xf…

华为OD机试 - 报数问题 - 约瑟夫环(Java 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;E卷D卷A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加…

python画图|在3D图上画2D直方图(作图平面移动)

前期我们已经学习过2D和3D的直方图绘制&#xff1a; 二维常规直方图绘制&#xff1a;python画图|水平直方图绘制_绘制水平直方图-CSDN博客 二维极坐标直方图绘制&#xff1a;python画图|极坐标中画直方图_ax1.plot()怎么画直方图-CSDN博客 三维直方图绘制&#xff1a;python…

Spring考点总结

01.Spring框架的基本理解 关键字:核心思想IOC\AOP\作用(解耦、简化)&#xff0c;简单描述框架组成 Spring框架是一款轻量级的开发框架&#xff0c;核心思想是IOC&#xff08;控制反转&#xff09;和AOP&#xff08;面向切面编程&#xff09;&#xff0c; 为Java应用程序开发…

使用Addressables+SpriteAtlas打包产生冗余

1&#xff09;使用AddressablesSpriteAtlas打包产生冗余 2&#xff09;使用SBP打AssetBundle脚本引用丢失 3&#xff09;Unity构建后处理&#xff08;IPostprocessBuildWithReport等接口&#xff09;抛出异常后&#xff0c;构建不会停止 4&#xff09;Unity 2022.3.0版本使用Oc…

基于YOLOv8的RTSP视频流实时目标检测与告警系统设计与实现(超详细)

前言 在训练模型完成后&#xff0c;想把模型应用起来&#xff0c;比如模型可以部署到项目中&#xff0c;实时接收RTSP视频流进行识别检测&#xff0c;一旦达到自己所设置的置信度阈值&#xff08;例如大于0.5&#xff09;&#xff0c;系统就会实时把报警信息发送给服务端&…