cocosCreator 之 dispatchEvent事件分发

版本: 3.8.0

语言: TypeScript

环境: Mac


Node事件派发


cocosCreator支持使用Node节点进行事件派发(dispatchEvent),事件派发系统是按照 Web 的事件冒泡及捕获标准 实现的。

事件派发主要通过冒泡的方式逐渐向父节点传递。

bubble-event

在派发后,会经历如下阶段:

  • 捕获:事件从场景根节点,逐级向子节点传递,直到到达目标节点或者在某个节点的响应函数中中断事件传递
  • 目标:事件在目标节点上触发
  • 冒泡:事件由目标节点,逐级向父节点冒泡传递,直到到达根节点或者在某个节点的响应函数中中断事件传递

实现的主要接口在Node的基类BaseNode中,主要有:

export class BaseNode extends CCObject implements ISchedulable {
  // 在节点上注册指定类型的回调函数,也可以设置 target 用于绑定响应函数的 this 对象
  on(type: string | __private.cocos_core_scene_graph_node_event_NodeEventType, callback: __private.AnyFunction, target?: unknown, useCapture?: any): void;
  // 删除之前与同类型,回调,目标或 useCapture 注册的回调
  off(type: string, callback?: __private.AnyFunction, target?: unknown, useCapture?: any): void;
  // 注册节点的特定事件类型回调,回调会在第一时间被触发后删除自身
  once(type: string, callback: __private.AnyFunction, target?: unknown, useCapture?: any): void;
  // 通过事件名发送自定义事件, 支持最多5个参数的传递
  emit(type: string, arg0?: any, arg1?: any, arg2?: any, arg3?: any, arg4?: any): void;
  // 分发事件到事件流中
  dispatchEvent(event: Event): void;
  // 检查事件目标对象是否有为特定类型的事件注册的回调
  hasEventListener(type: string, callback?: __private.AnyFunction, target?: unknown): any;
  // 移除目标上的所有注册事件
  targetOff(target: string | unknown): void;   
}

使用dispatchEvent进行事件分发,需要Event对象的支持,它是所有事件对象的基类,主要定义有:

export class Event {
  static NO_TYPE: string;				// 没有类型的事件
  static TOUCH: string;					// 触摸事件类型
  static MOUSE: string;					// 鼠标事件类型
  static KEYBOARD: string;			// 键盘事件类型
  static ACCELERATION: string;	// 加速器事件类型
  
  static NONE: number;						// 尚未派发事件阶段
  static CAPTURING_PHASE: number;	// 捕获阶段
  static AT_TARGET: number;				// 目标阶段
  static BUBBLING_PHASE: number;	// 冒泡阶段
  
  bubbles: boolean;			// 事件是否冒泡
  target: any;					// 最初事件触发的目标
  currentTarget: any;		// 当前目标
  
  // 事件阶段,主要用于返回NONE,CAPTURING_PHASE,AT_TARGET,BUBBLING_PHASE等
  eventPhase: number;
  // 停止传递当前事件
  propagationStopped: boolean;
  // 立即停止当前事件的传递,事件甚至不会被分派到所连接的当前目标
  propagationImmediateStopped: boolean;
  
  // 检查该事件是否已经停止传递
  isStopped(): boolean;
  // 获取当前目标节点
  getCurrentTarget(): any;
  // 获取事件类型
  getType(): __private.cocos_input_types_event_enum_SystemEventTypeUnion;
}

以上图为例,假设事件从节点c派发事件,节点a和b都收到事件的监听,可以这样编写示例:

// common.ts
class MyEvent extends Event {
    constructor(name: string, bubbles?: boolean, detail?: any) {
        super(name, bubbles);
        this.detail = detail;
    }
    public detail: any = null;  // 自定义的属性
}

// c.ts
import { Event } from 'cc';

public demo() {
  this.node.dispatchEvent( new MyEvent('foobar', true, 'detail info') );
}

// b.ts
this.node.on('foobar', (event: MyEvent) => {
  // 设置是否停止传递当前事件,如果为true,则a不再收到监听事件相关
  event.propagationStopped = true;
});

// a.ts
this.node.on('foobar', (event: MyEvent) => {
  //
});

如上内容,从官方文档: 节点事件系统 移植而来,主要用于对后面的自定义事件派发进行铺垫。


自定义事件派发


使用Node节点的冒泡派发,如果组件节点过多,可能会存在不够灵活和高效的问题。

事件分发的大概原理是:

  • 通过dispatchEvent将事件相关注册到一个事件表中
  • 通过addEventListener 根据事件类型检测事件表中是否存在,如果存在则执行
  • 通过removeEventListener根据事件类型将事件相关从表中移除,如果存在则移除

因此可封装一个简单的事件管理类: EventManager,大致实现:

import { error, _decorator } from "cc";
const { ccclass } = _decorator;

@ccclass("EventManager")
export class EventManager {
  static handlers: { [name: string]: { handler: Function, target: any }[] };
  // 添加监听(事件类型名,回调,目标节点)
  public static addEventListener(name: string, handler: Function, target?: any) {
    const objHandler = {handler: handler, target: target};
    if (this.handlers === undefined) {
      this.handlers = {};
    }
    let handlerList = this.handlers[name];
    if (!handlerList) {
      handlerList = [];
      this.handlers[name] = handlerList;
    }

    for (var i = 0; i < handlerList.length; i++) {
      if (!handlerList[i]) {
        handlerList[i] = objHandler;
        return i;
      }
    }

    handlerList.push(objHandler);

    return handlerList.length;
  };
  // 移除监听(事件类型名,回调,目标节点)
  public static removeEventListener(name: string, handler: Function, target?: any) {
    const handlerList = this.handlers[name];
    if (!handlerList) {
      return;
    }

    for (let i = 0; i < handlerList.length; i++) {
      const oldObj = handlerList[i];
      if (oldObj.handler === handler && (!target || target === oldObj.target)) {
        handlerList.splice(i, 1);
        break;
      }
    }
  };
  // 事件分发(事件类型名,自定义参数)
  public static dispatchEvent(name: string, ...args: any) {
    const handlerList = this.handlers[name];

    const params = [];
    let i;
    for (i = 1; i < arguments.length; i++) {
      params.push(arguments[i]);
    }

    if (!handlerList) {
      return;
    }

    for (i = 0; i < handlerList.length; i++) {
      const objHandler = handlerList[i];
      if (objHandler.handler) {
        objHandler.handler.apply(objHandler.target, args);
      }
    }
  };
};

使用示例:

// demoLayer.ts
import { EventManager } from '../EventManager';
export class demoLayer extends Component {
  protected onEnable(): void {
    EventManager.on("DEBUG_CUSTOM", this.customEvent, this);
  }

  protected onDisable(): void {
    EventManager.off("DEBUG_CUSTOM", this.customEvent, this);
  }
  
  private customEvent(param) {
    console.log(param);
  }
}

// gameManager.ts
EventManager.dispatchEvent("DEBUG_CUSTOM", 1);

理解可能有误,欢迎指出;最后祝大家学习生活愉快!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹤九日

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值