Event被分发到listener上。Event Listener既可是函数,也可以时对象(参看前面的 Function vs Table Listeners)。两种情况下,当事件发生,listener将被调用,并且得到一个描述event的table。所有的event将有一个属性name,来识别是哪一种事件。
注册事件
你用Corona库创建的一些对象是都是事件监听器。这包括所有的display object和全局Runtime对象。你可以使用下列对象方法为event添加和删除listener:
• object:addEventListener( )
• object:removeEventListener( )
在下面的例子里,一个image display object注册要接受触摸事件。触摸事件并不是全局广播的。只有注册了这个事件的,并且在手指范围之下的对象可以接受到触摸。参看Touch Events 以获得更多信息。
Function Listeners
Table Listeners
local button =
display.newImage("button.png")
local function listener(event)
print(event.name.."occurred")
return true
end
button:addEventListener(
"touch", listener )
local button =
display.newImage("button.png")
local function button:touch(event)
print(event.name.."occurred")
return true
end
button:addEventListener(
"touch", button )
相反,Runtime Event是被系统分发的。他们广播给所有的listener。下面的一个例子就是注册一个enterFrame事件:
Function Listeners
Table Listeners
local function listener(event)
print(event.name.."occurred")
end
Runtime:addEventListener(
"enterFrame", listener )
local listener = {}
local function listener:touch(event)
print(event.name.."occurred")
end
Runtime:addEventListener(
"enterFrame", listener )
Function vs Table Listeners
Listener既可以是函数,也可以是table对象。
当一个函数listener被调用时,有一个表示这个event的table被传入这个函数:
local myListener = function( event )
print( "Listener called with event of type " .. event.name )
end
Runtime:addEventListener( "touch", myListener )
Runtime:addEventListener( "enterFrame", myListener )
有时候一个函数listener并不方便,因为当listener被触发(调用)时,一些变量不在作用域里。这时候,就应该使用对象listener。对象 listener必须有一个实例方法,其方法名要与事件名一致:
-- 假设 MyClass 和 MyClass:new() 已经存在
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 )
Runtime Events
因为他们没有指定目标,Runtime Event只能被发送给全局的Runtime。相反,他们被广播到所有注册的listener。这些事件都有一个字符串名称:
enterFrame
enterFrame事件发生在应用程序帧的间隔。他们只会被发送到全局的Runtime对象。
下面属性在event中是有效的:
• event.name 就是字符串 "enterFrame"。
• event.time 从应用程序开始经过的毫秒数。
system
System事件被分发来提醒应用程序的扩展事件,例如当设备因为接到一个来电需要挂起应用程序时。这些事件,只回发给全局Runtime对象。
下面属性在event中是有效的:
• event.name 就是字符串 "system"。
• event.type 是一个字符串,表示event的类型。这个字符串的值可以时:
• "applicationStart" 当应用程序被启动时发生,此时main.lua中所有代码被执行。
• "applicationExit" 当用户退出应用程序时发生。
• "applicationSuspend" 当设备需要挂起应用程序时发生,比如接到一个来电,或者手机从激活状态转入休眠。在模拟器里,这对应于模拟器在后台运行。 挂起期间,没有event(甚至没有 enterFrame事件)被发送给应用程序,所以如果你有基于时间的代码,你应该把应用程序挂起期间丢失的时间都算进去。
• "applicationResume" 当应用程序挂起后又恢复时发生。在电话上,如果应用程序因为一个来电而被挂起,就会发生。在模拟器上,当模拟器从后台变成前台应用程序时发生。
orientation
Orientation events在设备方向发生改变时发生。只有在具备加速计支持的设备上,才有这个事件。他门只能发送给全局的Runtime对象。
下面属性在event中是有效的:
• event.name 是字符串 "orientation"。
• event.type 标识方向的一个字符串:
• "portrait"
• "landscapeLeft"
• "portraitUpsideDown"
• "landscapeRight"
• "faceUp"
• "faceDown"
• event.delta 在开始方向和结束方向之间的一个角度差。如果两个方向在不同平面为0。
accelerometer
Accelerometer event 让我们检测到瞬间运动,以及确定设备相对于重力的方向。这些事件只会被分发到支持加速计的设备上。他们只能发送给全局Runtime对象。
下面属性在event中是有效的:
• event.name 是字符串 "accelerometer".
• event.xGravity 在x方向上基于重力的加速度
• event.yGravity 在y方向上基于重力的加速度
• event.zGravity 在z方向上基于重力的加速度
• event.xInstant x方向上的瞬间加速度
• event.yInstant y方向上的瞬间加速度
• event.zInstant z方向上的瞬间加速度
• event.isShake 当用户晃动设备时,为 true
location (GPS)
通过GPS硬件生成的位置事件。 他们只能发送给全局Runtime对象。
• event.name 是字符串 "location"。
• event.latitude 是纬度。
• event.longitude 是经度。
• event.altitude 以米计算的高空高度。
• event.accuracy 以米计算的位置的精度。如果是负数,那么经度和纬度是无效的。
• event.speed 以米每秒计算的设备的瞬时速度。
• event.direction 从正北方向开始顺时针旋转的角度,表示设备的方向。
• event.time 位置事件的UTC时间戳。
当有错误发生时,下面属性将为非nil:
• event.errorMessage 是一个错误描述的字符串。当错误发生时,这个属性才存在。可以使用本地化技术,让它倚赖用户的语言设置。
• event.errorCode 无语言倚赖的平台指定错误的整数数字。这个属性只有当错误发生才会存在。
heading (指南针)
这是 指南针硬件生成的heading事件,如果在设备上是有效的话。注意:Android上只支持 event.magnetic, 却不支持 event.geographic (参看下面)。这些事件只被发送给全局Runtime对象。
下面属性在event中是有效的:
• event.name 是字符串 "heading".
• event.geographic 表示heading角度(顺时针),相对于地理北极,有时也叫正北。
• event.magnetic 表示heading角度(顺时针),相对于磁场北极。
memoryWarning (iOS)
iOS低内存警告作为一个叫做“memoryWarning”的Corona事件,发送给全局Runtime对象。这个event没有字段。
当这个事件发生,OS将有权在五秒内强行关闭应用程序(尽管它也可能不这么做)。Apple建议开发者监听这个警告,并且收到这个事件时,尽量释放尽可能多的内存,可以使应用程序免于被强行关闭。
这是一个关于这个事件的listener的例子:
local function handleLowMemory( event )
print( "memory warning received!" )
end
Runtime:addEventListener( "memoryWarning", handleLowMemory )
注意Android没有于此等价的事件,所以目前这只是iOS的特性。
Targeted Events
targeted event是不广播的。他们被发送给一个单独的目标(一个函数或table的listener)。
completion
Completion event标志着一个交互的完成。 通常这个被分发到模态交互上(使代码阻塞的操作),例如使用摄像头或发出一个本地警告框。
这个event一般有下列通用属性:
• event.name 是字符串 "completion"。
根据模态交互一些额外的属性也被给出( media.playVideo, media.show, 和 native.showAlert).
timer
Timer events 被用于和timer库结合。
下面属性在event中是有效的:
• event.name 是字符串 "timer".
• event.source 对应注册发送这个事件的 timer
• event.count 这个timer激活这个 listener 的次数。如果你注册这个timer并多次激活,这个字段就很有用。
• event.time 从应用程序开始起,过去的毫秒数。
urlRequest
URL请求被分发到用 native.webPopup() 注册的listener上。当web弹出窗口即将请求一个url,以及当url加载失败时,他们会被发送。
下面属性在event中是有效的:
• event.name 是字符串 "urlRequest".
• event.url 是请求的绝对URL。
• event.errorMessage 是一个错误描述的字符串。当错误发生时,这个属性才存在。可以使用本地化技术,让它倚赖用户的语言设置。
• event.errorCode 无语言倚赖的平台指定错误的整数数字。这个属性只有当错误发生才会存在。
Touch Events
当用户的手指触摸到屏幕,一个hit event就会被生成,并分发到显示层次中的display object上。只有那些和hit位置交叉 (手指在屏幕上的位置) 的对象才可以收到这个事件。
这个事件在这些对象中以一个特定的顺序传播。显示层次上第一个收到事件的对象是和hit位置相交,且最顶层的display object;下一个是和hit位置相交的下一个顶层对象,以此类推。
Hit事件往下被传播,直到这个事件被处理。你可以通过告诉系统这个事件已经被处理,来阻止事件向下一个对象传播(当前对象的所有listener也都可以得到事件)。其实就是简单地,让一个listener返回true。如果当前对象的listener中至少有一个listener返回true,event的传播就会结束;下一个对象将不会得到事件。如果这个事件在整个传播过程中一直没有被处理,它就会作为一个全局事件广播给全局Runtime对象。
hit事件其实是一个本地事件和全局事件的混合体。它一次只能分发给一个单独的display object,但是任何注册了listener的display object都会收到这个事件。
某些情况下,把随后的事件指向一开始处理过触控事件(一个用户的手指最初接触屏幕)的同一对象,是很方便的。为了完成这件事,你可以用一个指定的方法 display.getCurrentStage():setFocus( object )。当用户的手指离开,你可以再次调用这个方法,并且传nil参数,来恢复默认行为。
touch (single touch)
触摸事件是一种特殊的hit事件。当用户的手指接触到屏幕,他们就开始一个touch事件的序列,每个不同阶段。
• event.name 是字符串 "touch".
• event.x 触摸到屏幕坐标的x位置。
• event.y 触摸到屏幕坐标的x位置。
• event.xStart 触摸序列中的“开始”阶段的x位置。
• event.yStart 触摸序列中的“开始”阶段的x位置。
• event.phase 在触摸序列中标识发生事件的阶段的标识字符串:
• "began" 手指刚接触到屏幕
• "moved" 手指在屏幕上移动
• "ended" 手指刚从屏幕上离开
• "cancelled" 系统取消触摸跟踪
touch (multitouch)
为了说明下面的文档,我们提供两个新的范例工程:
• "FollowMeMultitouch" (“GettingStarted"里)
• "MultitouchButton" (“Interface"里)
此外,我们对ui.lua做下小修改,以支持多点触控。如果激活多点触控,并计划使用ui.lua,你应该把这个新版本包括在你的工程里。这新的库也应该兼容非多点的情况。
Corona中的多点触控
当多点触控被启用,多点触控会被象单独的touch event一样发送,同时发生时其每个看起来和单点一样。我们也加入了一个time属性,以便在编写手势识别代码时,你可以检测同时发生的触摸事件。最后我们添加一个id属性,允许你跟踪哪个触控事件来自同一个手指。
注意:多点触控在一些android设备上工作并不正常(例如 NexusOne, HTC Incredible等等)。这用 Drag Me Multitouch例子程序来测试。这是目前android平台的限制,而不是Corona的。
无论多点触控是否被启用,touch event将具备下面标准属性:
• event.x 触摸到屏幕坐标的x位置。
• event.y 触摸到屏幕坐标的y位置。
• event.xStart 触摸序列中的“开始”阶段的x位置。
• event.yStart 触摸序列中的“开始”阶段的y位置。.
• event.phase 在触摸序列中标识发生事件的阶段的标识字符串:
• "began" 手指刚接触到屏幕。
• "moved" 手指在屏幕上移动。
• "stationary" 一个手指触摸在屏幕上,但是从前一个事件开始到现在并没移动
• "ended" 手指刚从屏幕上离开。
• "cancelled" 系统取消触摸跟踪。
• event.id 一个唯一的选择触摸的标识符,以便你可以跨事件处理多点触控。 (i.e. 不同事件在多个listener调用间发送). 这个id唯一标识一个给定的触摸在屏幕上的手指,当触摸状态改变,生成新的触控事件。
• event.time 是一个时间戳。就是和 system.getTimer()返回值,具备相同的单位。
使用多点触控
如前所述,你要收到多点触控事件之前,你必须显式地激活多点触控。
system.activate( "multitouch" )
一旦完成这个调用,你将可以象单独的单点触控事件一样,接收到多点触控事件。例如,如果两个手指同时移动,Corona将分别hit-test每个触控,并把他们传递给各自的listener;这两个事件,都具备相同的时间戳。
Multiple Focus, Buttons and Delivery Precedence
在单点触控的世界里,我们有能力通过stage:setFocus(object) 方法把touch event指向一个具体对象,其导致将来所有的触控事件被直接发送到那个对象的touch listener上。
• 这个对象检测到开始的触摸,所以这个触控事件被分发到对象的touch listener。
• 所有将来触控的检测(全局)被对象的touch listener禁用,因其调用 stage:setFocus(object)。这导致将来的触控事件,被交付到那个对象上,而不是对其他对象遍历检测。
• 当触摸结束(或被取消),通常检测可以通过调用 stage:setFocus( nil ) 来修复。
在多点触控的世界里,这个模型就不行了,因为假设一次只有一个唯一的触控。结果,两个按钮不能同时被按下。
因此我们必须添加一个能力,基于每个对象发送一个触控事件。为了做到这一点,我们扩展一个参数,唯一的和给定屏幕上手指关联的标识符。
• stage:setFocus( object [,touchID] )
以前只有一个参数(object)的调用和旧的全局焦点行为一样:所有的触控都将被交送给同一个对象。
带可选参数(touchID)来调用前面的方法,意味着指定触控聚焦在那个对象上,但是其他触控却不一定。使用这个API,就可能创建一个拥有第一次它得到的触控的对象,在那个触控的生命周期里,多个对象同时获得他们自己聚焦的触控。参看 “GettingStarted/FollowMeMultitouch” 范例代码。
为了关闭每个对象焦点,可以为touchID参数传入nil:
• stage:setFocus( object, nil )
注意:为了向后兼容,当多点触控关闭时stage:setFocus()恢复为老的setFocus的行为:
• stage:setFocus( obj, nil ) 相当于 stage:setFocus( nil )
• stage:setFocus( obj, id ) 相当于 stage:setFocus( obj )
结果,最新的ui.lua库代码,在beta 6或更高版本分发,无论在多点触控还是非多点模式下,都应该按照预期方式工作。
支持手势
参看 "Multitouch"范例代码(iPad范例代码目录)来学习一下如何实现手势捏拉。
我们打算提供额外的框架库,来简化手势识别,但我们也鼓励你与此同时实验下多点触控。
自定义事件
在Corona中,你可以为display object和全局Runtime对象,注册自定义的事件。两种情况,你都必须手动分发你自己的事件,用如下方法:
object:dispatchEvent( event )
分发event到object。event参数必须是一个table,包含一个name字符串属性标识event的类型。这个object参数必须支持display object和全局Runtime对象。如果object有一个注册的listener来接受name事件。我们推荐你也让event包含target属性,以便你的listener可以知道哪个object接收到event。
-- Create an object that listens to events
local image = display.newImage( "image.png" )
-- Setup listener
local myListener = function( event )
print( "Event " .. event.name )
print( "Target has width: " .. event.target.stageWidth )
end
image:addEventListener( "myEventType", myListener )
-- Sometime later, create an event and dispatch it
local event = { name="myEventType", target=image }
image:dispatchEvent( event )
Timer
有时,立即调用某个函数,可能不如过一段时间再调用更恰当。timer库提供你一些基本函数,让你来实现这一点。
timer.performWithDelay( delay, listener [, iterations] )
经过delay毫秒后调用listener;返回一个句柄,你可以传它给timer.cancel()来再其调用listener之前取消它。
这个listener既可以是一个函数listener,也可以是一个table listener。如果listener是一个table,它必须有一个timer方法,因为 timer events 会发给这个listener。可选参数 iterations指定listener被调用的次数。默认是1;如果你希望无限循环下去,就传0。
Function Listener
Table Listener
local function listener( event )
print( "listener called" )
end
timer.performWithDelay(
1000, listener )
local listener = {}
function listener:timer( event )
print( "listener called" )
end
timer.performWithDelay(
1000, listener )
timer.cancel( timerId )
用关联的timerId来取消timer操作:
1
2
3
4
5
6
7
8
9
10
11
12
local t = {}
function t:timer( event )
local count = event.count
print( "Table listener called " .. count .. " time(s)" )
if count >= 3 then
timer.cancel( event.source ) -- after 3rd invocation, cancel timer
end
end
-- Register to call t's timer method an infinite number of times
timer.performWithDelay( 1000, t, 0 )