You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
13 KiB
397 lines
13 KiB
4 weeks ago
|
|
||
|
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
|
||
|
);
|
||
|
}
|
||
|
}
|