鸿蒙banner轮播堆叠效果

完整代码 : https://github.com/dazeGitHub/TestHarmonyBanner

定义组件 SwiperComponentHalfFold :

import { SwiperData } from '../model/SwiperData';
import Logger from '../utils/Logger';

/**
 * 层叠轮播图组件
 * 是 SwiperComponent 的修改版, 只显示右侧的折叠部分
 */
@Component
export struct SwiperComponentHalfFold {
  @State currentIndex: number = 0;
  @State swiperData: SwiperData[] = [
    new SwiperData($r("app.media.mp_chart"), 'MpChart图表实现案例'),
    new SwiperData($r("app.media.lottie"), 'Lottie动画'),
    new SwiperData($r("app.media.component_tack"), '组件堆叠'),
    new SwiperData($r("app.media.ic_swiper1"), '轮播1'),
    new SwiperData($r("app.media.ic_swiper2"), '轮播2'),
    new SwiperData($r("app.media.ic_swiper3"), '轮播3'),
  ];
  private halfCount: number = Math.floor(6 / 2);  //半数 = 总数 / 2
  private manualSlidingDuration: number = 800;    //手动滑动时长
  private automaticSlidingDuration: number = 300;
  private automaticSwitchTime: number = 5000;
  private offsetXValue = 15;
  @State swiperInterval: number = 0

  aboutToAppear(): void {
    this.currentIndex = this.halfCount;
    this.swiperInterval = setInterval(() => {
      this.startAnimation(true, this.manualSlidingDuration);
    }, this.automaticSwitchTime);
  }

  /**
   * 获取图片系数
   * @param index:索引值
   * @returns
   */
  getImgCoefficients(index: number): number {
    const coefficient: number = this.currentIndex - index; // 计算图片左右位置
    const tempCoefficient: number = Math.abs(coefficient);
    if (tempCoefficient <= this.halfCount) {
      return coefficient;
    }
    const dataLength: number = this.swiperData.length;
    let tempOffset: number = dataLength - tempCoefficient; // 判断图片位于左右层级位置
    if (tempOffset <= this.halfCount) { //如果在左侧
      if (coefficient > 0) {
        return -tempOffset;
      }
      return tempOffset;
    }
    return 0;
  }

  /**
   * 计算偏移量
   * @param index:索引值
   * @returns
   */
  getOffSetX(index: number): number {
    const offsetIndex: number = this.getImgCoefficients(index);
    const tempOffset: number = Math.abs(offsetIndex);
    let offsetX: number = 0;
    if (tempOffset === 1) {
      // 根据图片层级系数来决定左右偏移量
      offsetX = -15 * offsetIndex;
    }
    if (tempOffset === 2) {
      // 根据图片层级系数来决定左右偏移量
      offsetX = -this.offsetXValue * offsetIndex;
    }
    Logger.info("TAG", "index = " + index + " offsetX = " + offsetX)
    return offsetX;
  }

  // 性能:显式动画(https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V2/ts-explicit-animation-0000001478341181-V2)
  startAnimation(isLeft: boolean, duration: number): void {
    animateTo({
      duration: duration,
    }, () => {
      const dataLength: number = this.swiperData.length;
      const tempIndex: number = isLeft ? this.currentIndex + 1 : this.currentIndex - 1 + dataLength;
      this.currentIndex = tempIndex % dataLength;
    })
  }

  build() {
    Column() {
      Stack() {
        // LazyForEach必须在容器组件内使用,仅有List、Grid、Swiper以及WaterFlow组件支持数据懒加载,其他组件仍然是一次性加载所有的数据。
        ForEach(this.swiperData, (item: SwiperData, index: number) => {
          Stack({ alignContent: Alignment.BottomStart }) {
            Image(item.imageSrc)
              .objectFit(ImageFit.Cover)
              .width('100%')
              .height('100%')
              .borderRadius($r('app.string.main_page_top_borderRadius'))
            // 轮播图底部蒙层 必定在上方
            Stack() {
              Column() {
              }
              .width('100%')
              .height('100%')
              .backgroundColor(Color.Black)
              .opacity(0.3)
              .borderRadius({
                topLeft: 0,
                topRight: 0,
                bottomLeft: $r('app.string.main_page_top_borderRadius'),
                bottomRight: $r('app.string.main_page_top_borderRadius')
              })

              Text(item.name)
                .width('100%')
                .height('100%')
                .fontSize(16)
                .fontColor(Color.White)
                .textAlign(TextAlign.Start)
                .padding($r('app.string.main_page_padding5'))
            }
            .height($r('app.string.bottom_title_height'))
          }
          .backgroundColor(Color.White)
          .borderRadius(8)
          .offset({
            x: this.getOffSetX(index),
            y: 0
          })
          .blur(index !== this.currentIndex ? 12 : 0)
          // TODO: 知识点:通过animateTo实现动画并且同时改变currentIndex数据中间值来判断组件zIndex实现切换动画
          .zIndex(index !== this.currentIndex && this.getImgCoefficients(index) === 0 ?
            0 : 2 - Math.abs(this.getImgCoefficients(index)))
          .width($r('app.string.swiper_stack_width'))
          // .height(index !== this.currentIndex ? $r('app.string.swiper_stack_height1') : $r('app.string.swiper_stack_height2'))
          .height(
            index === this.currentIndex ?
              $r("app.string.swiper_stack_height1")
              : ((index === this.currentIndex - 1 || index === this.currentIndex + 1) ?
                $r("app.string.swiper_stack_height2") :
                $r("app.string.swiper_stack_height3"))
          )
          .onClick(() => {
            // 点击轮播图Item时,根据点击的模块信息,将页面放入路由栈
            // DynamicsRouter.push(item.routerInfo, item.param);
          })
        })
      }
      .onVisibleAreaChange([0.0, 1.0], (isVisible: boolean, currentRatio: number) => {
        clearInterval(this.swiperInterval);
        if (isVisible && currentRatio >= 1.0) {
          this.swiperInterval = setInterval(() => {
            this.startAnimation(true, this.manualSlidingDuration);
          }, this.automaticSwitchTime)
        }

        if (!isVisible && currentRatio <= 0.0) {
          clearInterval(this.swiperInterval);
        }
      })
      //高度必须和 app.string.swiper_stack_height1 相同
      .height($r('app.string.swiper_stack_height1'))
      .width('100%')
      .gesture(
        PanGesture({ direction: PanDirection.Horizontal })
          .onActionStart((event: GestureEvent) => {
            clearInterval(this.swiperInterval);
            this.startAnimation(event.offsetX < 0, this.automaticSlidingDuration);
          })
          .onActionEnd(() => {
            this.swiperInterval = setInterval(() => {
              this.startAnimation(true, this.manualSlidingDuration);
            }, this.automaticSwitchTime);
          })
      )
      .alignContent(Alignment.Start)
      .backgroundColor($r("app.color.green"))
      .clip(true) //裁剪超出 banner 左侧的层叠部分
      .margin({left: "100vp"})
      // .margin({left: -this.offsetXValue * 2}) //整体左移动两个位移

      //底部指示器
      Row({ space: 10 }) {
        ForEach(this.swiperData, (item: SwiperData, index: number) => {
          Ellipse(index !== this.currentIndex ? { width: 8, height: 8 } : { width: 10, height: 8 })
            .fill(index !== this.currentIndex ? Color.Black : Color.Red)
            .fillOpacity(0.6)
        })
      }
      .margin({ top: 12 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r("app.color.blue"))
    .justifyContent(FlexAlign.Start)
  }
}

使用组件 SwiperComponentHalfFold :

import { SwiperComponentHalfFold } from '../components/SwiperComponentHalfFold'

@Entry
@Component
struct Index {
  @State message: string = 'Hello World'

  build() {
    Row() {
      Column() {
        // SwiperComponent()
        // SwiperComponentThreeEle()
        SwiperComponentHalfFold()
      }
      .width('100%')
    }
    .height('100%')
  }
}

运行结果如下 :

Snipaste_2024-04-13_15-34-29.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,322评论 5 465
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,288评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,227评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,015评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,936评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,534评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,995评论 3 389
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,616评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,907评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,923评论 2 315
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,741评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,525评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,016评论 3 301
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,141评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,453评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,054评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,249评论 2 339