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

156 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}