Most Corona developers understand the concepts behind tap and touch events, but the lines get a little blurry when dealing with more complex scenarios, for example overlapping objects with different types of event listeners. This tutorial explains exactly how Corona handles these events and which events are broadcast to which objects.
Let's quickly discuss the basics for users who may be new to Corona or mobile development in general. When the user touches the screen of a
tap — This event is represented by the user quickly touching the screen and lifting off at the same approximate point on the screen.
touch — These events provide for a much greater level of screen interactivity. Using touch events, you can detect when the user first touches the screen and when the touch is lifted off the screen. You can also track the motion of the touch as it moves around the screen.
This tutorial won't go into depth about the properties returned from these events — if you need to explore this topic further, please refer to the Tap/Touch/Multitouch guide.
Looking beyond the basic concept of tap vs. touch, let's explore how Corona handles these events. At the core level, it's important to understand that each type is regarded as distinct. You may think that "a screen touch is just a screen touch," but the difference will become more apparent as we examine more complex examples.
For purposes of this tutorial, let's quickly set up a test project consisting of two squares that overlap in the center. We'll use these squares to test different types of listeners and explore how/when the events are processed.
Open the Corona Simulator.
Click New Project from the welcome window or select
For the project/application name, type TapTouch
and ensure that the Blank template option is selected. Leave the other settings at default and click OK (Windows) or Next (Mac). This will create the basic files for the test project in the location (folder) that you specified.
Locate the project folder and open the main.lua
file in your chosen text editor. Inside, replace the existing lines with this code:
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 )
Working with this example, let's explore the most simple case of overlapping objects: one tap object overlapping another tap object. When you click/tap in the center section where the squares overlap and observe the output in the console, you'll notice that (by default) tap events transmit through — or "propagate" through — to underlying objects. In other words, the front (red) square doesn't block the tap event from reaching the back (blue) square, and the console reflects this:
Front Object TAP Back Object TAP
This is by design, but how do you prevent this from occurring when such behavior is not desired? The solution is to simply return true
tapListener()
. Doing so will prevent tap events on an object from propagating through to
To test this concept, add the following line to your code:
-- Tap listener function local function tapListener( event ) local object = event.target print( object.name .. " TAP" ) return true -- Prevent propagation to underlying tap objects end
Now refresh/reload the project and, once again, click/tap in the center section where the squares overlap. By inspecting the console, you'll notice that the tap now only reaches the front square:
Front Object TAP
As expected, the same principle applies to touch events. Let's return true
touchListener()
) exactly as we did for the tap listener function:
-- 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
In addition, let's change both squares to touch objects instead of tap objects and associate them with the touchListener()
function. This can be done on lines 26 and 29 by changing "tap"
to "touch"
and changing tapListener
to touchListener
.
-- Add event listener to back object backObject:addEventListener( "touch", touchListener ) -- Add event listener to front object frontObject:addEventListener( "touch", touchListener )
Refresh/reload the project and then click/drag on the front (red) square. While doing so, inspect the console and you'll notice output messages that look like this:
Front Object TOUCH (began) Front Object TOUCH (moved) Front Object TOUCH (moved) Front Object TOUCH (moved) Front Object TOUCH (moved) ...
Things get a little more complicated when you have objects with different listener types overlapping each other, but you still need to control the propagation of tap and touch events. For testing purposes, let's adjust the sample project so it becomes a tap object (red square) over a touch object (blue square). Do this by editing line 29, changing "touch"
to "tap"
and touchListener
to tapListener
:
-- Add event listener to back object backObject:addEventListener( "touch", touchListener ) -- Add event listener to front object frontObject:addEventListener( "tap", tapListener )
Now refresh/reload the project and click/tap in the center section where the squares overlap. In the console, the output messages may look similar to this:
Back Object TOUCH (began) Back Object TOUCH (ended) Front Object TAP
Notice that the back square still receives touch events despite the fact that we've added return true
tapListener()
and touchListener()
functions. This is why it's important to understand, as noted earlier, that tap and touch events are actually distinct from Corona's standpoint, despite some similarities from the user's standpoint.
The behavior is similar if we change the sample project to a touch object over a tap object. To test this, edit the code as follows:
"touch"
to "tap"
and touchListener
to tapListener
."tap"
to "touch"
and tapListener
to touchListener
.-- Add event listener to back object backObject:addEventListener( "tap", tapListener ) -- Add event listener to front object frontObject:addEventListener( "touch", touchListener )
Refresh/reload the project and, once again, click/tap in the center section where the squares overlap. In the console, the output messages may look similar to this:
Front Object TOUCH (began) Front Object TOUCH (ended) Back Object TAP
The above examples of overlapping objects with different listener types is fairly common in app development, so you'll need an approach to handle these situations. Some examples include:
Objects with tap listeners over a larger region that must detect touch events, for example basic
A draggable touch object that can be moved around over underlying tap objects which the user can tap on to collect/activate.
For both of these cases, and numerous others, there is a tactic to prevent the "wrong type" of event from propagating through to an underlying object with a different listener type, as follows:
"touch"
event listener being simply an anonymous function which returns 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 )
In addition, we can use the :setFocus()
method in the touchListener()
function to give focus to the background object. This works nicely because, since we effectively blocked touch propagation from passing through the front object, any touch object behind it will only receive focus if the touch point is outside of the front object's bounds.
-- 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
"tap"
event listener as an anonymous function which returns 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 )
Every project will vary slightly in the exact way that it should behave in regards to tap and touch, but understanding these core concepts is essential to the ultimate user experience. Experiment with the different types and phases, and always remember to test, test, and test again!