点击/触摸机制

大多数 Corona 开发者都理解 **点击** 和 **触摸** 事件背后的概念,但在处理更复杂的场景时,例如具有不同类型事件监听器的重叠对象,界限会变得有些模糊。本教程将详细解释 Corona 如何处理这些事件,以及哪些事件会广播到哪些对象。

点击与触摸

让我们快速讨论一下 Corona 或移动开发新手的基本知识。当用户触摸触控设备的屏幕时,此操作被视为以下两种方式之一:

本教程不会深入探讨这些事件返回的属性 — 如果您需要进一步探讨此主题,请参阅 点击/触摸/多点触控 指南。

事件区分

除了点击与触摸的基本概念之外,让我们探讨 Corona 如何处理这些事件。在核心层面,理解每种类型都被视为 **不同** 的事件非常重要。您可能认为“屏幕触摸只是屏幕触摸”,但随着我们研究更复杂的示例,差异将变得更加明显。

测试项目设置

为了本教程的目的,让我们快速设置一个测试项目,该项目由两个在中心重叠的正方形组成。我们将使用这些正方形来测试不同类型的监听器,并探索事件的处理方式和时间。

  1. 打开 Corona 模拟器。

  2. 从欢迎窗口中点击 **新建项目**,或从 **文件** 菜单中选择新建项目…

  3. 对于项目/应用程序名称,键入 `TapTouch` 并确保选中 **空白** 模板选项。将其他设置保留为默认值,然后点击 **确定** (Windows) 或 **下一步** (Mac)。这将在您指定的位置(文件夹)中创建测试项目的基本文件。

  4. 找到项目文件夹,并在您选择的文本编辑器中打开 `main.lua` 文件。在文件中,将现有行 **替换** 为以下代码:

local backObject = display.newRect( 130, 130, 150, 150 )
backObject:setFillColor( 0.2, 0.4, 0.8 )
backObject.alpha = 0.75
backObject.name = "Back Object"

local frontObject = display.newRect( 190, 190, 150, 150 )
frontObject:setFillColor( 1, 0.2, 0.3 )
frontObject.alpha = 0.75
frontObject.name = "Front Object"

-- Tap listener function
local function tapListener( event )
    local object = event.target
    print( object.name .. " TAP" )
end

-- Touch listener function
local function touchListener( event )
    local object = event.target
    print( object.name .. " TOUCH (" .. event.phase .. ")" )
end

-- Add event listener to back object
backObject:addEventListener( "tap", tapListener )

-- Add event listener to front object
frontObject:addEventListener( "tap", tapListener )

点击叠加点击

使用此示例,让我们探讨重叠对象的最简单情况:一个 **点击** 对象与另一个 **点击** 对象重叠。当您在正方形 **重叠** 的中心区域点击/触摸并观察控制台中的输出时,您会注意到(默认情况下)点击事件会传递 — 或“传播” — 到下面的对象。换句话说,前面(红色)的正方形不会阻止点击事件到达后面(蓝色)的正方形,控制台会反映这一点:

Front Object TAP
Back Object TAP

这是设计使然,但是当不需要这种行为时,如何阻止它发生呢?解决方案是在关联对象的监听器函数(在本例中为 `tapListener()`)的末尾简单地返回 true这样做将阻止对象上的点击事件传播到它后面的可点击对象。

要测试此概念,请将以下行添加到您的代码中:

-- Tap listener function
local function tapListener( event )
    local object = event.target
    print( object.name .. " TAP" )
    return true  -- Prevent propagation to underlying tap objects
end

现在刷新/重新加载项目,然后再次在正方形 **重叠** 的中心区域点击/触摸。通过检查控制台,您会注意到点击现在只到达 **前面** 的正方形:

Front Object TAP

触摸叠加触摸

正如预期的那样,相同的原则也适用于 **触摸** 事件。让我们在触摸事件监听器函数 (`touchListener()`) 的末尾添加返回 true就像我们对点击监听器函数所做的那样。

-- Touch listener function
local function touchListener( event )
    local object = event.target
    print( object.name .. " TOUCH (" .. event.phase .. ")" )
    return true  -- Prevent propagation to underlying touch objects
end

此外,让我们将两个正方形都更改为 **触摸** 对象而不是点击对象,并将它们与 `touchListener()` 函数关联。这可以通过在第 26 行和第 29 行将 `"tap"` 更改为 `"touch"`,并将 `tapListener` 更改为 `touchListener` 来完成。

-- Add event listener to back object
backObject:addEventListener( "touch", touchListener )

-- Add event listener to front object
frontObject:addEventListener( "touch", touchListener )

刷新/重新加载项目,然后在前面(红色)的正方形上点击/拖动。执行此操作时,请检查控制台,您会注意到如下所示的输出消息:

Front Object TOUCH (began)
Front Object TOUCH (moved)
Front Object TOUCH (moved)
Front Object TOUCH (moved)
Front Object TOUCH (moved)
...

点击/触摸叠加触摸/点击

当具有不同监听器类型的对象相互重叠,但您仍然需要控制点击和触摸事件的传播时,事情会变得更加复杂。为了测试目的,让我们调整示例项目,使其成为 **点击** 对象(红色正方形)位于 **触摸** 对象(蓝色正方形)之上。通过编辑第 29 行,将 `"touch"` 更改为 `"tap"`,并将 `touchListener` 更改为 `tapListener` 来完成此操作。

-- Add event listener to back object
backObject:addEventListener( "touch", touchListener )

-- Add event listener to front object
frontObject:addEventListener( "tap", tapListener )

现在刷新/重新加载项目,并在正方形 **重叠** 的中心区域点击/触摸。在控制台中,输出消息可能类似于以下内容:

Back Object TOUCH (began)
Back Object TOUCH (ended)
Front Object TAP

请注意,尽管我们在 `tapListener()` 和 `touchListener()` 函数中都添加了返回 true后面的正方形 **仍然会接收触摸事件**。这就是为什么理解点击和触摸事件实际上是 **不同** 的(从 Corona 的角度来看)非常重要的原因,尽管从用户的角度来看它们有一些相似之处(如前所述)。

如果我们将示例项目更改为 **触摸** 对象位于 **点击** 对象之上,则行为类似。要测试这一点,请编辑代码如下:

  1. 在第 26 行,将 `"touch"` 更改为 `"tap"`,并将 `touchListener` 更改为 `tapListener`。
  2. 在第 29 行,将 `"tap"` 更改为 `"touch"`,并将 `tapListener` 更改为 `touchListener`。
-- Add event listener to back object
backObject:addEventListener( "tap", tapListener )

-- Add event listener to front object
frontObject:addEventListener( "touch", touchListener )

刷新/重新加载项目,然后再次在正方形 **重叠** 的中心区域点击/触摸。在控制台中,输出消息可能类似于以下内容:

Front Object TOUCH (began)
Front Object TOUCH (ended)
Back Object TAP

处理重叠

上述具有不同监听器类型的重叠对象的示例在应用程序开发中相当常见,因此您需要一种方法来处理这些情况。一些示例包括:

对于这两种情况以及许多其他情况,都有一种策略可以防止“错误类型”的事件传播到具有不同监听器类型的底层对象,如下所示:

  1. 如果前面的对象应表现为 **点击** 对象,而后面的对象是 **触摸** 对象,我们可以向前面的对象添加 **两种** 类型(点击和触摸)的监听器,其中 `"touch"` 事件监听器只是一个返回 `true` 的匿名函数。
-- Add event listener to back object
backObject:addEventListener( "touch", touchListener )

-- Add two event listeners to front object
frontObject:addEventListener( "tap", tapListener )
frontObject:addEventListener( "touch", function() return true; end )

此外,我们可以在 `touchListener()` 函数中使用 `:setFocus()` 方法将焦点赋予背景对象。这很有效,因为由于我们有效地阻止了触摸传播穿过前面的对象,因此它后面的任何触摸对象只有在触摸点位于前面对象的 **边界之外** 时才会获得焦点。

-- Touch listener function
local function touchListener( event )
    local object = event.target

    if ( event.phase == "began" ) then
        display.getCurrentStage():setFocus( object )
    elseif ( event.phase == "ended" or event.phase == "cancelled" ) then
        display.getCurrentStage():setFocus( nil )
    end

    print( object.name .. " TOUCH (" .. event.phase .. ")" )
    return true  -- Prevent propagation to underlying touch objects
end
  1. 如果前面的对象应表现为 **触摸** 对象,而后面的对象是 **点击** 对象,则应用相同的概念。只需向前面的对象添加两种类型的监听器,并将其 `"tap"` 事件监听器定义为返回 `true` 的匿名函数。
-- Add event listener to back object
backObject:addEventListener( "tap", tapListener )

-- Add two event listeners to front object
frontObject:addEventListener( "touch", touchListener )
frontObject:addEventListener( "tap", function() return true; end )

总结

每个项目在点击和触摸方面的行为方式都会略有不同,但理解这些核心概念对于最终用户体验至关重要。尝试不同的类型和阶段,并始终记住测试、测试,再测试!