Files
MatchMaster/assets/shop/script/ShopActivityCarousel.ts
T
2026-09-29 18:57:56 +08:00

156 lines
6.9 KiB
TypeScript

const { ccclass } = cc._decorator;
/** 按可用活动横向翻页;纵向手势继续交给商城 ScrollView。 */
@ccclass
export default class ShopActivityCarousel extends cc.Component {
private activity: cc.Node = null;
private progress: cc.Node = null;
private index: number = 1;
private centerX: number = 0;
private touchId: number = null;
private startX: number = 0;
private dragX: number = 0;
private direction: string = "";
private cancelling: boolean = false;
private snap: cc.Tween = null;
private pages: cc.Node[] = [];
private dotSpacing: number = 0;
onLoad() {
this.activity = cc.find("activity", this.node);
this.progress = cc.find("progress", this.node);
this.centerX = this.activity.x;
const dots = this.progress.children;
this.dotSpacing = dots.length > 1 ? dots[1].x - dots[0].x : 0;
this.setEntries(["newbieGift", "jungle", "banre"]);
}
/** names 按优先级传入;三个入口保留原来的左、中、右排列。 */
setEntries(names: string[], reset: boolean = false) {
const available = names.map(name => this.activity.getChildByName(name)).filter(node => !!node);
const pages = available.length === 3 ? [available[1], available[0], available[2]] : available;
const changed = pages.length !== this.pages.length || pages.some((page, i) => page !== this.pages[i]);
if (!changed && !reset) return;
this.touchId = null;
if (this.snap) this.snap.stop();
this.pages = pages;
this.activity.children.forEach(child => { child.active = pages.indexOf(child) >= 0; });
pages.forEach((page, i) => page.setSiblingIndex(i));
this.activity.active = pages.length > 0;
this.progress.active = pages.length > 0;
const layout = this.activity.getComponent(cc.Layout);
if (layout && pages.length) layout.updateLayout();
this.index = pages.length === 3 ? 1 : 0;
this.progress.children.forEach((dot, i) => {
dot.active = i < pages.length;
if (dot.active) dot.x = (i - (pages.length - 1) / 2) * this.dotSpacing;
});
this.showPage(false);
}
onEnable() {
// 让卡片之间的空隙也能接收触摸;在 ScrollView 之前判定方向。
this.activity.on(cc.Node.EventType.TOUCH_START, this.onActivityTouch, this);
this.node.on(cc.Node.EventType.TOUCH_START, this.onTouchStart, this, true);
this.node.on(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this, true);
this.node.on(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this, true);
this.node.on(cc.Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this, true);
}
onDisable() {
this.activity.off(cc.Node.EventType.TOUCH_START, this.onActivityTouch, this);
this.node.off(cc.Node.EventType.TOUCH_START, this.onTouchStart, this, true);
this.node.off(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this, true);
this.node.off(cc.Node.EventType.TOUCH_END, this.onTouchEnd, this, true);
this.node.off(cc.Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this, true);
this.touchId = null;
this.showPage(false);
}
private onActivityTouch() {}
private pageX(index: number): number {
return this.centerX - this.pages[index].x * this.activity.scaleX;
}
private showPage(animated: boolean) {
if (this.snap) this.snap.stop();
const x = this.pages.length ? this.pageX(this.index) : this.centerX;
if (animated) {
this.snap = cc.tween(this.activity).to(0.2, { x }, { easing: "quadOut" }).start();
} else {
this.activity.x = x;
}
this.progress.children.forEach((dot, index) => {
const select = dot.getChildByName("select");
if (select) select.active = index < this.pages.length && index === this.index;
});
}
private onTouchStart(event: cc.Event.EventTouch) {
if (this.touchId !== null || this.pages.length < 2) return;
let target: cc.Node = event.target;
while (target && target !== this.activity) target = target.parent;
if (!target) return;
if (this.snap) this.snap.stop();
this.touchId = event.getID();
this.startX = this.activity.parent.convertToNodeSpaceAR(event.getLocation()).x;
this.dragX = this.activity.x;
this.direction = "";
}
private onTouchMove(event: cc.Event.EventTouch) {
if (event.getID() !== this.touchId) return;
const delta = event.getLocation().sub(event.getStartLocation());
if (!this.direction) {
if (Math.max(Math.abs(delta.x), Math.abs(delta.y)) < 8) {
event.stopPropagation();
return;
}
this.direction = Math.abs(delta.x) > Math.abs(delta.y) ? "horizontal" : "vertical";
if (this.direction === "horizontal") {
// 取消按钮按下和外层滚动的触摸,避免松手时误触购买。
const cancel = new cc.Event.EventTouch(event.getTouches(), event.bubbles);
cancel.type = cc.Node.EventType.TOUCH_CANCEL;
cancel.touch = event.touch;
this.cancelling = true;
event.target.dispatchEvent(cancel);
this.cancelling = false;
}
}
if (this.direction !== "horizontal") return;
event.stopPropagation();
const offset = this.activity.parent.convertToNodeSpaceAR(event.getLocation()).x - this.startX;
const left = this.pageX(Math.min(this.index + 1, this.pages.length - 1));
const right = this.pageX(Math.max(this.index - 1, 0));
this.activity.x = Math.max(left, Math.min(right, this.dragX + offset));
}
private onTouchEnd(event: cc.Event.EventTouch) {
if (event.getID() !== this.touchId) return;
if (this.direction === "horizontal") {
event.stopPropagation();
const offset = this.activity.parent.convertToNodeSpaceAR(event.getLocation()).x - this.startX;
if (Math.abs(offset) >= 20) {
this.index = Math.max(0, Math.min(this.pages.length - 1,
this.index + (offset > 0 ? -1 : 1)));
}
}
this.touchId = null;
this.showPage(true);
}
private onTouchCancel(event: cc.Event.EventTouch) {
// ScrollView 为取消按钮点击而派发的模拟事件不是手势结束。
if (this.cancelling || (event as any).simulate || event.getID() !== this.touchId) return;
// Cocos 在原触摸节点外松手也派发 TOUCH_CANCEL,底层事件仍为 ENDED。
if (event.getEventCode() === (cc.Event.EventTouch as any).ENDED) {
this.onTouchEnd(event);
return;
}
if (this.direction === "horizontal") event.stopPropagation();
this.touchId = null;
this.showPage(true);
}
}