import { LogUtils } from "../Util/LogUtils"; import FMItemLayout from "./FMItemLayout"; 1./* * @Descripttion: 自定义带遮罩的容器,里面包含一个 content节点 * @version: 1.0.0 * @Author: YeeChan * @Date: 2020-07-17 16:52:59 */ const { ccclass, property, requireComponent, disallowMultiple, menu, } = cc._decorator; const EPSILON = 1e-4; //滑动的方向 export enum SlideDirection { HORIZONTAL = 1,//水平滚动 VERTICAL = 2//垂直滚动 } export enum FMTouchEvent { TouchStart = "touch_start", Scrolling = "scrolling", TouchEnded = "touch_ended" } @ccclass @disallowMultiple() //防止多个相同类型(或子类型)的组件被添加到同一个节点 //@menu("FM组件/FMTouchMaskView") export default class FMTouchMaskView extends cc.Component { //layout预制体 @property({ tooltip: "layout预制体(0,1)", type: cc.Prefab }) public itemPrefab: cc.Prefab = null; //滑动模式 @property() protected _slideDirection: SlideDirection = SlideDirection.HORIZONTAL; @property({ type: cc.Enum(SlideDirection), tooltip: "滚动方向:\n HORIZONTAL 水平滚动\nVERTICAL垂直滚动" }) set slideDirection(val: SlideDirection) { this._slideDirection = val; } get slideDirection() { return this._slideDirection; } //----------------------------------- private _tempPoint_custom = cc.v2(); private _tempPrevPoint_custom = cc.v2(); private _outOfBoundaryAmount_custom = cc.v2(0, 0); private _outOfBoundaryAmountDirty_custom = true; //是否初始化过 标签 private _inited_custom: boolean = false; //------------------------- //是否移动 protected _touchMoved_custom = false; //如果这个属性被设置为 true,那么滚动行为会取消子节点上注册的触摸事件,默认被设置为 true。* 注意,子节点上的 touchstart 事件仍然会触发,触点移动距离非常短的情况下 touchmove 和 touchend 也不会受影响。 protected cancelInnerEvents_custom = true; //预制的宽高 protected itemPrefabWidth_custom: number = 0; protected itemPrefabHeight_custom: number = 0; //滚动区域 protected content_custom: cc.Node; //预制体的脚本 private scriptItemPrefab_custom: FMItemLayout; protected onLoad() { this._init_custom(); } /** * 初始 */ protected _init_custom(): boolean { if (this._inited_custom) { //是否初始化过 return false; } let widget = this.node.getComponent(cc.Widget); if (widget) { widget.updateAlignment(); } //遮罩 let view: cc.Node = new cc.Node(); view.anchorX = 0; view.anchorY = 1; let viewWidget = view.addComponent(cc.Widget); viewWidget.top = 0; viewWidget.left = 0; viewWidget.right = 0; viewWidget.bottom = 0; viewWidget.isAlignTop = true; viewWidget.isAlignLeft = true; viewWidget.isAlignRight = true; viewWidget.isAlignBottom = true; this.node.addChild(view); viewWidget.updateAlignment(); let mash = view.addComponent(cc.Mask); mash.type = cc.Mask.Type.RECT; //显示面板 滚动节点 this.content_custom = new cc.Node(); view.addChild(this.content_custom); this.content_custom.width = view.width; this.content_custom.height = view.height; this.content_custom.anchorX = 0; this.content_custom.anchorY = 1; this.content_custom.x = 0; this.content_custom.y = 0; this.itemPrefabWidth_custom = this.itemPrefab.data.width; this.itemPrefabHeight_custom = this.itemPrefab.data.height; this.scriptItemPrefab_custom = this.itemPrefab.data.getComponent(FMItemLayout); if (!this.scriptItemPrefab_custom) { LogUtils.error_custom("FMTouchMaskView -> _init -> scriptItemPrefab is null"); } else { if (this.scriptItemPrefab_custom.getItemChildrenCount_custom() == 0) { LogUtils.error_custom("FMTouchMaskView -> _init -> scriptItemPrefab item child count ==0"); } } this._inited_custom = true; return true; } protected _dispatchEvent_custom(name: FMTouchEvent) { console.log("FMTouchMaskView -> _dispatchEvent -> name", name); } /** * 如果这个属性被设置为 true,那么滚动行为会取消子节点上注册的触摸事件,默认被设置为 true。* * 注意,子节点上的 touchstart 事件仍然会触发,触点移动距离非常短的情况下 touchmove 和 touchend 也不会受影响。 * @param inner */ public setCancelInnerEvents_custom(inner: boolean) { this.cancelInnerEvents_custom = inner; } /** * 按下 * @param touch */ protected handlePressLogic_custom(touch: cc.Touch) { this._dispatchEvent_custom(FMTouchEvent.TouchStart); } /** * 移动 * @param touch */ protected handleMoveLogic_custom(touch: cc.Touch) { let deltaMove = this.getLocalAxisAlignDelta_custom(touch); deltaMove = this.clampDelta_custom(deltaMove); let realMove = deltaMove; let outOfBoundary = this.getHowMuchOutOfBoundary_custom(realMove); realMove = realMove.add(outOfBoundary); if (realMove.x !== 0 || realMove.y !== 0) { this._moveContent_custom(realMove); this._dispatchEvent_custom(FMTouchEvent.Scrolling); } } /** * 释放 * @param touch */ protected handleReleaseLogic_custom(touch: cc.Touch) { let delta = this.getLocalAxisAlignDelta_custom(touch); this._dispatchEvent_custom(FMTouchEvent.TouchEnded); } private _flattenVectorByDirection_custom(vector) { let result = vector; result.x = this._slideDirection == SlideDirection.HORIZONTAL ? result.x : 0; result.y = this._slideDirection == SlideDirection.VERTICAL ? result.y : 0; return result; } private _moveContent_custom(deltaMove) { let adjustedMove = this._flattenVectorByDirection_custom(deltaMove); let newPosition = this.getContentPosition_custom().add(adjustedMove); this.setContentPosition_custom(newPosition); } // Contains node angle calculations private getLocalAxisAlignDelta_custom(touch): cc.Vec2 { this.node.convertToNodeSpaceAR(touch.getLocation(), this._tempPoint_custom); this.node.convertToNodeSpaceAR(touch.getPreviousLocation(), this._tempPrevPoint_custom); return this._tempPoint_custom.sub(this._tempPrevPoint_custom); } //this is for nested scrollview private _hasNestedViewGroup_custom(event: cc.Event.EventTouch, captureListeners) { if (event.eventPhase !== cc.Event.CAPTURING_PHASE) return; if (captureListeners) { //captureListeners are arranged from child to parent for (let i = 0; i < captureListeners.length; ++i) { let item = captureListeners[i]; if (this.node === item) { if (event.target.getComponent(cc.ViewGroup)) { return true; } return false; } if (item.getComponent(cc.ViewGroup)) { return true; } } } return false; } //This is for Scrollview as children of a Button private _stopPropagationIfTargetIsMe_custom(event) { if (event.eventPhase === cc.Event.AT_TARGET && event.target === this.node) { event.stopPropagation(); } } // touch event handler private _onTouchBegan_custom(event: cc.Event.EventTouch, captureListeners) { if (!this.enabledInHierarchy) return; if (this._hasNestedViewGroup_custom(event, captureListeners)) return; let touch = event.touch; if (this.content_custom) { this.handlePressLogic_custom(touch); } this._touchMoved_custom = false; this._stopPropagationIfTargetIsMe_custom(event); } private _onTouchMoved_custom(event: cc.Event.EventTouch, captureListeners) { if (!this.enabledInHierarchy) return; if (this._hasNestedViewGroup_custom(event, captureListeners)) return; let touch = event.touch; if (!this.node["_hitTest"](touch.getLocation(), this.node)) {//过滤掉不在区域内移动 return; } if (this.content_custom) { this.handleMoveLogic_custom(touch); } // Do not prevent touch events in inner nodes if (!this.cancelInnerEvents_custom) { return; } let deltaMove = touch.getLocation().sub(touch.getStartLocation()); //FIXME: touch move delta should be calculated by DPI. if (deltaMove.mag() > 7) { if (!this._touchMoved_custom) { // && event.target !== this.node // Simulate touch cancel for target node let cancelEvent = new cc.Event.EventTouch( event.getTouches(), event.bubbles ); cancelEvent.type = cc.Node.EventType.TOUCH_CANCEL; cancelEvent.touch = event.touch; cancelEvent["simulate"] = true; event.target.dispatchEvent(cancelEvent); this._touchMoved_custom = true; } } this._stopPropagationIfTargetIsMe_custom(event); } private _onTouchEnded_custom(event: cc.Event.EventTouch, captureListeners) { if (!this.enabledInHierarchy) return; if (this._hasNestedViewGroup_custom(event, captureListeners)) return; let touch = event.touch; if (this.content_custom) { this.handleReleaseLogic_custom(touch); } if (this._touchMoved_custom) { event.stopPropagation(); } else { this._stopPropagationIfTargetIsMe_custom(event); } } private _onTouchCancelled_custom(event, captureListeners) { if (!this.enabledInHierarchy) return; if (this._hasNestedViewGroup_custom(event, captureListeners)) return; // Filte touch cancel event send from self if (!event.simulate) { let touch = event.touch; if (this.content_custom) { this.handleReleaseLogic_custom(touch); } } this._stopPropagationIfTargetIsMe_custom(event); } private clampDelta_custom(delta) { if (this._slideDirection == SlideDirection.HORIZONTAL) { delta.y = 0; } else if (this._slideDirection == SlideDirection.VERTICAL) { delta.x = 0; } return delta; } private getHowMuchOutOfBoundary_custom(addition) { addition = addition || cc.v2(0, 0); if (addition.fuzzyEquals(cc.v2(0, 0), EPSILON) && !this._outOfBoundaryAmountDirty_custom) { return this._outOfBoundaryAmount_custom; } let outOfBoundaryAmount = cc.v2(0, 0); if (addition.fuzzyEquals(cc.v2(0, 0), EPSILON)) { this._outOfBoundaryAmount_custom = outOfBoundaryAmount; this._outOfBoundaryAmountDirty_custom = false; } outOfBoundaryAmount = this.clampDelta_custom(outOfBoundaryAmount); return outOfBoundaryAmount; } private getContentPosition_custom() { return this.content_custom.getPosition(); } private setContentPosition_custom(position) { if (position.fuzzyEquals(this.getContentPosition_custom(), EPSILON)) { return; } this.content_custom.setPosition(position); this._outOfBoundaryAmountDirty_custom = true; } onEnable() { this._registerEvent_custom(); } onDisable() { this._unregisterEvent_custom(); } //private methods private _registerEvent_custom() { this.node.on(cc.Node.EventType.TOUCH_START, this._onTouchBegan_custom, this, true); this.node.on(cc.Node.EventType.TOUCH_MOVE, this._onTouchMoved_custom, this, true); this.node.on(cc.Node.EventType.TOUCH_END, this._onTouchEnded_custom, this, true); this.node.on( cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancelled_custom, this, true ); } private _unregisterEvent_custom() { this.node.off( cc.Node.EventType.TOUCH_START, this._onTouchBegan_custom, this, true ); this.node.off(cc.Node.EventType.TOUCH_MOVE, this._onTouchMoved_custom, this, true); this.node.off(cc.Node.EventType.TOUCH_END, this._onTouchEnded_custom, this, true); this.node.off( cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancelled_custom, this, true ); } }