CoronaSDK之交互系统和事件检测
事件(Event)是Corona应用程序的基础。他们用来触发不同事件对应的响应,例如触摸屏幕、检测一个特定的系统事件、定时器的完成、两个物理体的碰撞,等等。通常我们使用display object的对象方法addEventListener来添加处理函数,以关联需要监听的事件。
1 Runtime事件(似乎也可以叫做全局事件)
Runtime事件被分配给全局的Runtime监听器。这些事件并不直接和任何具体的对象相关;当然他们广播给所有感兴趣的监听器。Runtime event包括下列这些:
- enterFrame:发生在每秒帧数间隔之间的事件(corona渲染一般每秒30或60帧)
- system:某种由系统状况引发的事件,例如设备挂起应用或退出应用
- orientation:当改变设备的方向,横向到竖向或者反之,而发生的事件
下面的代码展示了一个应用如何响应system事件:
local function onSystemEvent( event ) local eventType = event.type if ( eventType == "applicationStart" ) then --occurs when the application is launched and all code in "main.lua" is executed elseif ( eventType == "applicationExit" ) then --occurs when the user or OS task manager quits the application elseif ( eventType == "applicationSuspend" ) then --perform all necessary actions for when the device suspends the application, i.e. during a phone call elseif ( eventType == "applicationResume" ) then --perform all necessary actions for when the app resumes from a suspended state elseif ( eventType == "applicationOpen" ) then --occurs when the application is asked to open a URL resource (Android and iOS only) end end Runtime:addEventListener( "system", onSystemEvent )
2 本地事件(local event)
本地事件被发送给一个单独的Listener,而不是广播给全局的运行时环境。一般本地事件包括下列事件:
hit:当用户触摸设备屏幕时发生的事件
collision:物理对象碰撞时发生的事件
timer:在运行timer完成时发生的事件
audio:当一个音频播放完成时分发的事件
3 function listener 和 table listener
Listener可以是一个function,也可以是一个table。
当你一个function Listener被调用,它会接受一个table参数,作为event。
local myListener = function( event ) print( "Listener called with event of type: "..event.name ) end Runtime:addEventListener( "touch", myListener ) Runtime:addEventListener( "enterFrame", myListener )
有时一个function Listener并不方便,因为当Listener被调用时一些变量并不在作用域里。在这种情况里,table Listener应该被使用。table Listener必须有一个有对应事件名的实例方法:
--assume myClass and myClass:new() already exist function myClass:enterFrame( event ) print( "enterFrame called at time: "..event.time ) end function myClass:touch( event ) print( "touch occurred at: "..event.x..","..event.y ) end local myObject = myClass:new() Runtime:addEventListener( "touch", myObject ) Runtime:addEventListener( "enterFrame", myObject )
注意:
这里是对象方法,用的是“:”,而不是“.”。所以实际上函数内部是可以访问self引用的。这里的self就是myObject的引用。
4 理解hit事件
当用户触摸屏幕时,一个hit事件会生成并分发给显示层次中的显示对象(display object)。只有那些在屏幕上位置和触摸点位置发生交集的对象会收到事件。hit事件会以一个特定的顺序穿过这些对象。默认情况下,第一个收到事件的对象是发生交集的对象中最上层的显示对象,往下一个发生交集的显示对象会第二个收到事件,如此类推。
hit事件会一直传递下去,除非它被处理掉。这意味着如果你有多个层叠在一起的对象,并且一个hit事件监听器被应用到所有这些对象,hit事件将会穿过着所有的对象。然而,你可以停止传递给下一个低层对象,这就需要你告诉corona这个event已经被处理了。简单的方法就是在事件处理函数直接返回一个true--这将会停止传递周期,并且阻止任何底下对象响应这个hit事件。
local function myTouchListener( event ) if ( event.phase == "began" ) then --code executed when the button is touched print( "object touched = "..tostring(event.target) ) --'event.target' is the touched object end return true --prevents touch propagation to underlying objects end local myButton = display.newRect( 100, 100, 200, 50 ) myButton:addEventListener( "touch", myTouchListener )
注意:如果hit事件遍历了整个显示层次中所有的显示对象,仍然没有被处理,它将会像全局事件一样广播给Runtime Listener。换言之,runtime listener还有机会对未命中任何对象的触摸事件做出反应。
4.1 设置焦点
你可以直接发送任何hit事件给一个指定的display object,来为这个对象设置焦点。比如一个典型的button,如果用户触摸这个按钮--还没有抬起手-又滑出了按钮,这个按钮一般不应该激发一个行为。同理,如果手指滑出按钮并接触到另一个交互对象,你可能不想激活那个对象,因为触摸是从按钮开始的,而不是交互对象。
解决这个问题的简单方法就如下,进入按钮时设置焦点,结束和取消时再取消焦点。这样滑出到其他对象时,其他对象都收不到事件:
local function myTouchListener( event ) if ( event.phase == "began" ) then display.getCurrentStage():setFocus( event.target ) --'event.target' is the touched object elseif ( event.phase == "ended" or event.phase == "cancelled" ) then display.getCurrentStage():setFocus( nil ) --setting focus to 'nil' removes the focus end return true end local myButton = display.newRect( 100, 100, 200, 50 ) myButton:addEventListener( "touch", myTouchListener )
4.2 hit event具体分为
- Tap
- Touch
- Multitouch
5 event 参数
上面举出的每个例子,事件都会被分发给事件监听函数。一个event参数始终会被传递给监听函数。
local function myTouchListener( event ) print( "Touch X location"..event.x ) print( "Touch Y location"..event.y ) end local myButton = display.newRect( 100, 100, 200, 50 ) myButton:addEventListener( "touch", myTouchListener )
注意:传递给myTouchListener函数的event参数中包含手指接触屏幕位置的x和y坐标。event参数关联的属性和event类型是一一关联的。什么事件 对应哪些属性。
6 注册EventListener
我们一般用对象方法addEventListener()来注册事件。只要传入事件名称字符串,以及对应处理这个事件的处理函数即可。
--standard touch listener on an object myButton:addEventListener( "touch", myTouchListener ) --Runtime 'system' event listener Runtime:addEventListener( "system", onSystemEvent )
重要提示:
如上所示,两种类型的事件监听器都可以通过addEventListener对象方法来创建:监听器附着到显示对象,或者监听器附着到全局Rumtime。
当你创建一个显示对象或添加一个本地的touch事件监听器 给它,你需要通过引用函数来指定一个代码块给事件。这些代码处于他们自己内存的代码段当中,并且当显示对象被删除后依然存在。不过当显示对象被删除后,就没办法检测到新的事件,因为本地事件监听器(注意:是监听器,不是代码)已经被有效的删除,并不需要你显式地删除监听器。
另一方面,Runtime事件监听器需要在你用完之后手动删除。此外,他们将继续运行,因为全局Runtime事件是全局的。不仅仅会导致内存泄漏,而且如果函数继续存在于Runtime中,并引用了不再存在的对象,程序也会崩溃。
7 删除Event Listener
大多数监听器(Listener),除了“enterFrame”之类的Runtime监听器,在显示对象被删除(obj:removeSelf或display.remove(obj))时将会被自动删除。但是,你也可以不删除对象,而只手动删除事件监听器。
删除(摘除)一个事件监听器,需要依靠内建的removeEventListener对象方法。
这个函数调用方式和addEventListener差不多。比如,你想让一个按钮停止监听touch事件,就可以摘除其监听器,如下:
myButton:removeEventListener( "touch", myTouchListener )
删除关联的事件监听器,需要提供事件名和函数名,因为有可能一个对象连接了同一类对象的多个监听器。例如,你可以有两个或更多监听函数关联到myButton的touch事件。然而当调用removeEventListener()时,你需要指定你想要停止监听的事件的类型,以及之前关联到这个事件上的函数。