HarmonyOS鸿蒙Next应用如何实现层级轮播卡片大中小的叠加效果?

HarmonyOS鸿蒙Next应用如何实现层级轮播卡片大中小的叠加效果? cke_239.png

1.上下堆叠由大到小的卡片层叠效果。

2.上层卡片可手势左右滑动,自动左滑动。

3.三层卡片随滑动,内容进行依次切换。

鸿蒙应用如何实现层级轮播卡片大中小的叠加效果?


更多关于HarmonyOS鸿蒙Next应用如何实现层级轮播卡片大中小的叠加效果?的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

一、代码实现和详细解释

1.最上层使用swiper进行轮播效果的实现。

@Builder
HeadSwiperLayerView() {
  Swiper(this.swiperController) {
    this.HeadCard()
    this.MiddenCard()
    this.EndCard()
  }
  .loop(true)
  .autoPlay(true)
  .indicator(false)
  .duration(500)
  .curve(Curve.Friction)
  .displayMode(SwiperDisplayMode.STRETCH)
  .onChange(this.monChangeCallBack)
  .onAnimationEnd(this.monAnimationEndCallBack)

}

@Builder
HeadCard() {
  Row() {
     if (this.HEAD_INDEX == this.mIndex) {
      Image(this.mImageResource[this.HEAD_INDEX])
        .BGCardCardWidthAndHeight()
        .opacity(this.mCardOpacity)
        .CardAnim()
        .borderWidth(px2vp(2))
        .borderColor(Color.Red)
        .backgroundColor(Color.Blue)

    }
  }
  .BGWidthAndHeight()
}

// MiddenCard EndCard 卡片依照HeadCard进行创建

2.下层两个卡片使用stack堆叠,依次显示。

build() {
  Stack({ alignContent: Alignment.Center }) {
    // 最底层卡片
    this.EndLayerView()

    // 中间层卡片
    this.MiddenLayerView()

    // 最上层swiper滑动层
    this.HeadSwiperLayerView()
  }
}

3.整体卡片内容通过list数据进行链式切换。

// 卡片图片资源池
@State mImageResource: Array<string | Resource > = [
  $r("app.media.icon_img1"),
  $r("app.media.icon_img2"),
  $r("app.media.icon_img3"),
  $r("app.media.icon_img4"),
  $r("app.media.icon_img5"),
];

4.调整切换的index下标数据,控制数据切换,和逻辑计算。

getImageResource(current: number, mIndex: number) {
  let currentPage: string | Resource = ''
  if (current == this.END_INDEX) {
    if (mIndex == this.HEAD_INDEX) {
      currentPage = this.mImageResource[2];
    } else if (mIndex == this.MIDDEN_INDEX) {
      currentPage = this.mImageResource[0];
    } else if (mIndex == this.END_INDEX) {
      currentPage = this.mImageResource[1];
    }
  } else if (current == this.MIDDEN_INDEX) {
    if (mIndex >= 2) {
      currentPage = this.mImageResource[0];
    } else {
      currentPage = this.mImageResource[mIndex+1];
    }
  } else if (current == this.HEAD_INDEX) {
    currentPage = this.mImageResource[this.mIndex];
  }

  return currentPage;
}

5.细磨效果,添加透明度动画润色过渡操作的效果。(为了效果更好后续也可添加移动,放大缩小等。示例代码只是添加了透明度。)

二、DEMO源码示例:

滑动组件View SlidingCardView.ets

/**
 * 层叠轮播图
 */
@Component
export default struct SlidingCardView {
  // 滑动层 透明背景宽高
  private BG_WIDTH: number = px2vp(800);
  private BG_HEIGHT: number = px2vp(800);
  // 滑动层 可视卡片宽高
  private BG_CARD_WIDTH: number = px2vp(700);
  private BG_CARD_HEIGHT: number = px2vp(800);
  private CARD_ANIM_START: number = 0.1;
  private CARD_ANIM_END: number = 1;
  private LAYER_CARD_ANIM_START: number = 0.9;
  private LAYER_CARD_ANIM_END: number = 1;
  // 移动下标
  @State mIndex: number = 0;

  @State mCardOpacity: number = this.CARD_ANIM_END;
  @State mLayerOpacity: number = this.LAYER_CARD_ANIM_END;
  // 卡片图片资源池
  @State mImageResource: Array<string | Resource > = [
    $r("app.media.icon_img1"),
    $r("app.media.icon_img2"),
    $r("app.media.icon_img3"),
    $r("app.media.icon_img4"),
    $r("app.media.icon_img5"),
  ];
  private swiperController: SwiperController = new SwiperController();
  private END_INDEX: number = 2;
  private MIDDEN_INDEX: number = 1;
  private HEAD_INDEX: number = 0;

  getImageResource(current: number, mIndex: number) {
    let currentPage: string | Resource = ''
    if (current == this.END_INDEX) {
      if (mIndex == this.HEAD_INDEX) {
        currentPage = this.mImageResource[2];
      } else if (mIndex == this.MIDDEN_INDEX) {
        currentPage = this.mImageResource[0];
      } else if (mIndex == this.END_INDEX) {
        currentPage = this.mImageResource[1];
      }
    } else if (current == this.MIDDEN_INDEX) {
      if (mIndex >= 2) {
        currentPage = this.mImageResource[0];
      } else {
        currentPage = this.mImageResource[mIndex+1];
      }
    } else if (current == this.HEAD_INDEX) {
      currentPage = this.mImageResource[this.mIndex];
    }

    return currentPage;
  }

  private monAnimationEndCallBack = (): void => {
    this.mLayerOpacity = 0.9;
  }

  private monChangeCallBack = (index: number): void => {
    this.mIndex = index;
    this.mCardOpacity = this.CARD_ANIM_START;
    this.mLayerOpacity = this.LAYER_CARD_ANIM_START;
    setTimeout(() => {
      this.mCardOpacity = this.CARD_ANIM_END;
      this.mLayerOpacity = this.LAYER_CARD_ANIM_END;
    }, 100);
  }

  @Builder
  EndLayerView() {
    Row() {
      Blank()
      Image(this.getImageResource(this.END_INDEX, this.mIndex))
        .width(px2vp(500))
        .height(px2vp(600))
        .opacity(this.mLayerOpacity)
        .CardAnim()
        .borderWidth(px2vp(2))
        .borderColor(Color.Red)
        .backgroundColor(Color.Black)
    }
    .width(px2vp(1000))
    .height(px2vp(800))
  }

  @Builder
  MiddenLayerView() {
    Row() {
      Blank()
      Image(this.getImageResource(this.MIDDEN_INDEX, this.mIndex))
        .width(px2vp(400))
        .height(px2vp(700))
        .opacity(this.mLayerOpacity)
        .CardAnim()
        .borderWidth(px2vp(2))
        .borderColor(Color.Red)
        .backgroundColor(Color.Red)
    }
    .width(px2vp(800))
    .height(px2vp(800))
  }

  @Builder
  HeadCard() {
    Row() {
       if (this.HEAD_INDEX == this.mIndex) {
        Image(this.mImageResource[this.HEAD_INDEX])
          .BGCardCardWidthAndHeight()
          .opacity(this.mCardOpacity)
          .CardAnim()
          .borderWidth(px2vp(2))
          .borderColor(Color.Red)
          .backgroundColor(Color.Blue)

      }
    }
    .BGWidthAndHeight()
  }

  @Builder
  MiddenCard() {
    Row() {
      if (this.MIDDEN_INDEX == this.mIndex) {
        Image(this.mImageResource[this.MIDDEN_INDEX])
          .BGCardCardWidthAndHeight()
          .opacity(this.mCardOpacity)
          .CardAnim()
          .borderWidth(px2vp(2))
          .borderColor(Color.Red)
          .backgroundColor(Color.Blue)

       }
    }
    .BGWidthAndHeight()
  }

  @Builder
  EndCard() {
    Row() {
      if (this.END_INDEX == this.mIndex) {
        Image(this.mImageResource[this.END_INDEX])
          .BGCardCardWidthAndHeight()
          .opacity(this.mCardOpacity)
          .CardAnim()
          .borderWidth(px2vp(2))
          .borderColor(Color.Red)
          .backgroundColor(Color.Blue)

       }
    }
    .BGWidthAndHeight()
  }

  @Builder
  HeadSwiperLayerView() {
    Swiper(this.swiperController) {
      this.HeadCard()
      this.MiddenCard()
      this.EndCard()
    }
    .loop(true)
    .autoPlay(true)
    .indicator(false)
    .duration(500)
    .curve(Curve.Friction)
    .displayMode(SwiperDisplayMode.STRETCH)
    .onChange(this.monChangeCallBack)
    .onAnimationEnd(this.monAnimationEndCallBack)

  }

  @Styles
  BGWidthAndHeight(){
    .width(this.BG_WIDTH)
    .height(this.BG_HEIGHT)
  }

  @Styles
  BGCardCardWidthAndHeight(){
    .width(this.BG_CARD_WIDTH)
    .height(this.BG_CARD_HEIGHT)
    .borderWidth(px2vp(2))
    .borderColor(Color.Red)
  }

  @Styles
  CardAnim(){
    .animation({
      duration: 1000,
      tempo: 1,
      delay: 0,
      curve: Curve.Friction,
      playMode: PlayMode.Normal,
      iterations: 1
    })
  }

  build() {
    Stack({ alignContent: Alignment.Center }) {
      // 最底层卡片
      this.EndLayerView()

      // 中间层卡片
      this.MiddenLayerView()

      // 最上层swiper滑动层
      this.HeadSwiperLayerView()
    }
  }
}

入口界面类 Index.ets

import SlidingCardView from './SlidingCardView'

@Entry
@Component
struct Index {

  build() {
    Column(){
      SlidingCardView()
    }
    .width("100%")
    .height("100%")
  }
}

更多关于HarmonyOS鸿蒙Next应用如何实现层级轮播卡片大中小的叠加效果?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


鸿蒙Next中实现层级轮播卡片叠加效果

主要使用ArkUI的Swiper组件配合Stack布局。通过Stack将不同尺寸的卡片叠加,利用Swiper的loop与animationMode属性控制轮播。关键点在于动态调整每个卡片的zIndex、scale和translate属性,根据Swiper当前索引计算各卡片的偏移量与缩放比例,从而形成大、中、小的视觉层级。通常结合Swiper的onChange事件来更新每张卡片的样式状态。

在HarmonyOS Next中实现层级轮播卡片叠加效果,可以通过以下核心方案实现:

1. 布局与层级结构 使用Stack组件作为容器,配合ForEach循环渲染多个卡片。通过zIndexoffset属性控制层级与位置偏移,实现视觉叠加。

Stack({ alignContent: Alignment.Bottom }) {
  ForEach(this.cardList, (item: CardData, index: number) => {
    CardView(item)
      .zIndex(this.cardList.length - index) // 控制层级
      .offset({ y: this.getCardOffset(index) }) // 垂直偏移实现叠加
      .scale(this.getCardScale(index)) // 缩放控制大小
  })
}

2. 手势与动画控制 为顶层卡片绑定滑动手势事件,使用PanGesture监听左右滑动:

.gesture(
  PanGesture(this.panOption)
    .onActionUpdate((event: GestureEvent) => {
      // 更新卡片位置
      this.currentOffset = event.offsetX
    })
    .onActionEnd(() => {
      // 滑动结束后触发切换动画
      this.triggerCardSwitch()
    })
)

3. 自动轮播与切换逻辑 通过animateTo实现平滑过渡动画,结合定时器实现自动轮播:

// 卡片切换动画
animateTo({
  duration: 300,
  curve: Curve.EaseOut
}, () => {
  // 更新卡片位置和缩放值
  this.updateCardTransforms()
})

// 自动轮播
setInterval(() => {
  this.switchToNextCard()
}, 5000)

4. 关键样式计算

  • 缩放比例:根据卡片层级计算递减的scale值(如1.0、0.85、0.7)
  • 垂直偏移:每张卡片向上偏移固定像素,形成阶梯效果
  • 透明度渐变:底层卡片适当降低透明度增强层次感

5. 性能优化建议

  • 使用@Reusable装饰器优化卡片组件复用
  • 滑动时使用translate而非修改布局属性
  • 合理设置willChange属性提示渲染优化

这种实现方式充分利用了ArkUI的声明式特性和动画能力,通过组合手势、动画和布局属性,即可实现流畅的层级轮播效果。

回到顶部