156 lines
6.9 KiB
TypeScript
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);
|
||
}
|
||
}
|