Corona中文站

强大、易学的跨平台(iOS/Android)开发框架。QQ群1:74390406(满) 群2:221929599

导航

五分钟学会Corona(二十五) - Event 和 Listener
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 )

<< 五分钟学会Corona(二十四) - 交互性和事件检测五分钟学会Corona(二十六) - 本地 UI >>

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

最近发表

Powered By Z-Blog 1.8 Walle Build 100427 Copyright 2011-2015 BuildApp.Net. All Rights Reserved.