作者:kumaleap | 项目地址:ArkSwipeDeck on GitHub
随着鸿蒙生态的快速发展,ArkTS 组件化开发成为主流。Tinder风格的卡片堆叠滑动交互广泛应用于社交、推荐、内容发现等场景。ArkSwipeDeck 致力于为 HarmonyOS 提供一个高性能、易扩展、纯净的卡片堆叠滑动组件,助力开发者快速实现炫酷的滑动卡片体验。
核心定位:
组件本身不预设任何UI样式,所有卡片内容均通过Builder函数自定义。例如:
SwipeCardStack({
cardDataList: this.cards,
cardBuilder: this.buildCard
})
@Builder
buildCard(data: UserInfo, index: number) {
Image(data.src ?? '')
.width('100%')
.height('100%')
.borderRadius(20)
}
组件支持丰富的事件回调,如卡片滑动、点击、栈空、预加载等,便于业务层实现动态数据加载和交互统计。
SwipeCardStack({
cardDataList: this.cards,
cardBuilder: this.buildCard,
eventHandler: {
onCardSwiped: this.handleCardSwiped,
onLoadNextPage: this.handleLoadNextPage
}
})
private handleCardSwiped(direction: SwipeDirection, data: object, index: number): void {
// 统计喜欢/不喜欢等业务逻辑
}
private handleLoadNextPage(currentIndex: number, remainingCount: number): void {
this.loadMoreCards(10);
}
每张卡片根据堆叠索引动态计算缩放、位移、透明度,实现真实的3D堆叠视觉。滑动时,顶层卡片动画与下层卡片联动,保证流畅的过渡体验。
核心代码片段:
// 计算堆叠动画参数
static calculateStackAnimation(index: number, scaleRatio: number, cardSpacing: number): AnimationParams {
const scale: number = Math.pow(scaleRatio, index);
const translateY: number = index * cardSpacing;
return {
translateX: 0,
translateY: translateY,
rotation: 0,
scale: scale,
opacity: 1
};
}
// 卡片渲染时应用堆叠动画
.translate(this.getCardTranslate(stackIndex, dataIndex))
.scale(this.getCardScale(stackIndex, dataIndex))
采用PanGesture监听拖拽,区分主卡片与背景卡片的手势响应。滑动距离超过阈值自动触发滑出动画,并通过回调上报滑动方向和数据。
核心代码片段:
PanGesture({ fingers: 1, direction: PanDirection.All, distance: 1 })
.onActionStart((_event: GestureEvent): void => {
if (dataIndex !== this.currentIndex) return;
this.cardState = CardState.DRAGGING;
this.dragProgress = 0;
})
.onActionUpdate((event: GestureEvent): void => {
if (dataIndex !== this.currentIndex) return;
const deltaX: number = event.offsetX;
const deltaY: number = event.offsetY;
// 拖拽进度与动画联动
this.dragProgress = Math.min(Math.sqrt(deltaX * deltaX + deltaY * deltaY) / this.finalConfig.swipeThreshold, 1.0);
this.topCardAnimation = { ... };
this.updateBackgroundCardAnimation();
})
.onActionEnd((event: GestureEvent): void => {
if (dataIndex !== this.currentIndex) return;
// 判断是否滑出
if (Math.sqrt(event.offsetX ** 2 + event.offsetY ** 2) >= this.finalConfig.swipeThreshold) {
this.performSwipeOut(event.offsetX, event.offsetY, data, dataIndex);
} else {
// 弹回动画
this.topCardAnimation = { ... };
this.dragProgress = 0;
this.updateBackgroundCardAnimation();
this.cardState = CardState.IDLE;
}
})
通过onLoadNextPage
事件,业务可在卡片即将滑完时动态加载新数据,实现无限滑动体验。支持自定义预加载阈值和最大可见卡片数。
核心代码片段:
private handleLoadNextPage(currentIndex: number, remainingCount: number): void {
// 仅在未加载时触发
if (this.isLoading) return;
this.loadMoreCards(10);
}
private loadMoreCards(count: number): void {
this.isLoading = true;
setTimeout((): void => {
const newCards: UserInfo[] = [];
for (let i = 0; i < count; i++) {
const imageUrl: string = this.imageList[Math.floor(Math.random() * this.imageList.length)];
newCards.push({ src: imageUrl });
}
for (let i = 0; i < newCards.length; i++) {
this.cards.push(newCards[i]);
}
this.isLoading = false;
}, 800);
}
核心代码片段:
const customConfig: SwipeConfig = {
maxVisibleCards: 3,
rotationAngle: 20,
scaleRatio: 0.9,
swipeThreshold: 150,
animationDuration: 400,
enableSpringBack: true,
cardSpacing: 12
};
SwipeCardStack({
cardDataList: this.cards,
swipeConfig: customConfig,
onCardSwiped: this.handleCardSwiped,
cardBuilder: this.buildCard
})
@Entry
struct Index {
private readonly imageList: string[] = [
'https://images.unsplash.com/photo-1.jpg',
'https://images.unsplash.com/photo-2.jpg',
// ...更多图片
];
@State private cards: UserInfo[] = [];
aboutToAppear(): void {
this.loadMoreCards(10);
}
build(): void {
SwipeCardStack({
cardDataList: this.cards,
cardBuilder: this.buildCard,
eventHandler: {
onCardSwiped: this.handleCardSwiped,
onLoadNextPage: this.handleLoadNextPage
}
})
}
@Builder
buildCard(data: UserInfo, index: number): void {
Image(data.src ?? '')
.width('100%')
.height('100%')
.borderRadius(20)
}
private handleCardSwiped(direction: SwipeDirection, data: object, index: number): void {
// 统计喜欢/不喜欢等业务逻辑
}
private handleLoadNextPage(currentIndex: number, remainingCount: number): void {
this.loadMoreCards(10);
}
private loadMoreCards(count: number): void {
// 动态加载图片数据
// ...
}
}
onLoadNextPage
实现懒加载onCardSwiped
统计用户行为maxVisibleCards
,避免一次渲染过多卡片ArkSwipeDeck 以纯ArkTS实现,兼容HarmonyOS主流版本,适合各类需要卡片滑动交互的场景。组件高度自定义、性能优异,易于集成和二次开发。未来可扩展更多动画类型、手势交互和业务场景,助力鸿蒙生态繁荣。
README.md
README.en.md
欢迎Star、Fork、Issue交流与贡献!