鸿蒙开发画廊效果:
画廊这种效果确实不错,看起来高端大气。在Android那边已经有不少案例了,但是鸿蒙这边还是很少。今天来分享一个。
先看下效果图:
没法弄gif,就是左右可以看到前一张,下一张。
也可以看我b站完整的视频:https://www.bilibili.com/video/BV13eHre7EK9/
实现思路:
Swiper的onGestureSwipe里面算偏移量
部分参考代码:
// 联系:893151960
@Component
export struct CardSwiper {
// 卡片数据源
data: BaseDataSource<UserDataBean> = new BaseDataSource()
// 卡片偏移度列表
@State private cardsOffset: number[] = [];
// 屏幕宽度
private displayWidth: number = 0;
// Swiper 两侧的偏移量
private swiperMargin: number = CommonConstants.SWIPER_MARGIN;
// Swiper 当前索引值
@State private currentSwiperIndex: number = 0;
private readonly DEVICESIZE: number = 600; // 依据Navigation的mode属性说明,如使用Auto,窗口宽度>=600vp时,采用Split模式显示;窗口宽度<600vp时,采用Stack模式显示。
onChange?:(index:number)=>void
controller: SwiperController = new SwiperController();
aboutToAppear(): void {
// 获取屏幕大小,用于后续计算卡片的偏移量
const displayData: display.Display = display.getDefaultDisplaySync();
this.displayWidth = px2vp(displayData.width);
if ((display.isFoldable() && display.getFoldStatus() === display.FoldStatus.FOLD_STATUS_EXPANDED) || this.displayWidth >= this.DEVICESIZE) {
this.displayWidth = px2vp(displayData.width) / 2;
}
// 计算当前卡片及关联卡片的偏移量
this.calculateOffset(0);
}
build() {
Column() {
Swiper(this.controller) {
LazyForEach(this.data, (item: UserDataBean, index: number) => {
CardComponent({
userInfo: item,
cardOffset: this.cardsOffset[index],
cardIndex: index,
showingCard: this.currentSwiperIndex,
cardWith:this.displayWidth - 160,
cardHeight:this.displayWidth - 100,
})
},(item: UserDataBean,index:number) => `${index}-${JSON.stringify(item)}`)
}
.index($$this.currentSwiperIndex)
.loop(false)
.prevMargin(this.swiperMargin)
.nextMargin(this.swiperMargin)
.duration(CommonConstants.DURATION)
.curve(Curve.Friction)
.onChange((index) => {
this.calculateOffset(index);
this.onChange?.(index)
})
.indicator(false)
.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);
})
.height('100%')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
/**
* 计算卡片偏移量,并维护偏移量列表。
* @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;
}
}
/**
* 检查卡片索引值的合法性。
* @param index {number} input card's index.
* @returns true or false.
*/
isIndexValid(index: number): boolean {
return index >= 0 && index < this.data.totalCount();
}
/**
* 计算指定卡片的最大偏移量。
* @param index {number} target card's index.
* @returns offset value.
*/
getMaxOffset(index: number): number {
/*
* 这里的偏移量指相对容器左侧的值。
* 计算公式为:屏幕宽度 - Swiper两侧突出的偏移量 - 卡片自身的宽度。
* 此值即为卡片可偏移的最大值,也就是卡片右对齐的状态值。
* 如果居中,则将最大偏移量 / 2。
*/
//let areaWidth = this.data.getAllData()?.[index]?.areaWidth
return this.displayWidth - (index===this.currentSwiperIndex?this.displayWidth - 110:this.displayWidth - 160) - 2 * this.swiperMargin;
}
}
现有的demo项目结构图:
有需要源码或者有问题的可私信我