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