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

1.上下堆叠由大到小的卡片层叠效果。
2.上层卡片可手势左右滑动,自动左滑动。
3.三层卡片随滑动,内容进行依次切换。
鸿蒙应用如何实现层级轮播卡片大中小的叠加效果?
更多关于HarmonyOS鸿蒙Next应用如何实现层级轮播卡片大中小的叠加效果?的实战教程也可以访问 https://www.itying.com/category-93-b0.html
一、代码实现和详细解释
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循环渲染多个卡片。通过zIndex和offset属性控制层级与位置偏移,实现视觉叠加。
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的声明式特性和动画能力,通过组合手势、动画和布局属性,即可实现流畅的层级轮播效果。

