Can an object have two event listeners?

In my game, the user has to solve a jumbled word. The jumbled word is on the top row, where each letter is a separate tile object, which is draggable and has to be placed in a space on the bottom row. I want the user to be able to tap, which will cause the tile to jump to the first space on the bottom row. The user can also tap and hold to drag a tile if they have misplaced a letter.

Here is the code I am using at the moment:

local function onTouch( event )  
 local t = event.target  
  
 local phase = event.phase  
 if "began" == phase then  
 -- Make target the top-most object  
 local parent = t.parent  
 parent:insert( t )  
 display.getCurrentStage():setFocus( t )  
  
 t.isFocus = true  
  
 -- Store initial position  
 t.x0 = event.x - t.x  
 t.y0 = event.y - t.y  
  
 t.x, t.y = posX[index] + (sizeX/2), posY + (sizeY/2);  
 index = index + 1  
  
 elseif t.isFocus then  
 if "moved" == phase then  
 -- Make object move (we subtract t.x0,t.y0 so that moves are  
 -- relative to initial grab point, rather than object "snapping").  
 t.xScale = 1.7  
 t.yScale = 1.7  
 t.x = event.x - t.x0  
 t.y = event.y - t.y0  
  
 --t1 = t1..t.value  
 elseif "ended" == phase or "cancelled" == phase then  
 if t.container then  
 containers[t.container].isOccupied = false  
 answer[t.container] = " "  
 t.container = nil  
 --answer[t.container] = " "  
 end  
 for i = 1, #posX do  
 if not containers[i].isOccupied then  
 if (//correct condition) then  
 t.x, t.y = posX[i] + (sizeX/2), posY + (sizeY/2);  
 containers[i].isOccupied = true  
 t.container = i  
 --check to see if answer is correct  
 --get string from answer array  
 --print ( 'table: '..table.concat( answer))  
 s3 = table.concat(answer)  
 print(s3)  
 elseif (//another condition) then  
 -- t.x, t.y = posX2 + (sizeX/2), posY2 + (sizeY/2);  
 end  
 end  
 end  
 t.xScale = 1  
 t.yScale = 1  
 display.getCurrentStage():setFocus( nil )  
 t.isFocus = false  
 end  
 end  
  
 -- Important to return true. This tells the system that the event  
 -- should not be propagated to listeners of any objects underneath.  
 return true  
 end   

I am trying to use the began phase as the tap and the rest acts as the dragging. It works to a certain degree on the simulator on Windows and on a Samsung s2, but testing on my Samsung s3 if the user taps the tile for more than a split second, the tile wont jump to the right position and becomes draggable in stead. Is there a better way to do this? Can I use two different listeners on the same object? [import]uid: 208811 topic_id: 36109 reply_id: 336109[/import]

Yes, an object can have many listeners, but it doesn’t not make sense to attach multiple listeners for the same event (I can see you’re not doing that, just wanted to highlight that.)

I think you need to work out your specific case user interaction because a began phase is not a tap. A tap (as shown by the tap event listener) is a began phase, followed by no moved phase, followed by an ended phase which is fired within a limited amount of time (about 350 milliseconds.)

Also, be aware that if you want a fairly forgiving touch listener function which handles both the touch and tap events (which is actually a wise choice) you might want to track movement made during the moved phase. The question to ask yourself is: Do you want to limit detection of a tap to literally no movement or can the touch move a little way? ie: If the moved phase is detected does that mean the event is definitely not a tap?

It might sound very pedantic, but you will rarely be able to generate a true tap event on a physical device without causing a little bit of a moved phase. This will be a bigger problem with the newer, more powerful and sensitive devices - which could explain the difference between the S2 and S3. Try ignoring a threshold amount of movement when detecting the difference between a tap and a touch. [import]uid: 8271 topic_id: 36109 reply_id: 143446[/import]

As Horacebury says, I would detect how many pixels the user has moved their finger and use this to decide what to do in the ‘ended’ phase (sorry the formatting is all over the place, I don’t have access to a code editor right now).

[lua]

local moving = false

local function onTouch( event )
local t = event.target

local phase = event.phase

if “began” == phase then

– Make target the top-most object
local parent = t.parent
parent:insert( t )
display.getCurrentStage():setFocus( t )
t.isFocus = true

– Store initial position
t.x0 = event.x - t.x
t.y0 = event.y - t.y

elseif t.isFocus then
if “moved” == phase then

local move = math.abs(event.x - event.xStart) + math.abs(event.y - event.yStart)

if move > 2 then moving = true; end

– Make object move (we subtract t.x0,t.y0 so that moves are
– relative to initial grab point, rather than object “snapping”).
t.xScale = 1.7
t.yScale = 1.7
t.x = event.x - t.x0
t.y = event.y - t.y0

–t1 = t1…t.value
elseif “ended” == phase or “cancelled” == phase then

if moving == true then

t.x, t.y = posX[index] + (sizeX/2), posY + (sizeY/2);
index = index + 1

else

if t.container then
containers[t.container].isOccupied = false
answer[t.container] = " "
t.container = nil
–answer[t.container] = " "
end

for i = 1, #posX do
if not containers[i].isOccupied then
if (//correct condition) then
t.x, t.y = posX[i] + (sizeX/2), posY + (sizeY/2);
containers[i].isOccupied = true
t.container = i
–check to see if answer is correct
–get string from answer array
–print ( 'table: '…table.concat( answer))
s3 = table.concat(answer)
print(s3)
elseif (//another condition) then
– t.x, t.y = posX2 + (sizeX/2), posY2 + (sizeY/2);
end
end
end

t.xScale = 1
t.yScale = 1
display.getCurrentStage():setFocus( nil )
t.isFocus = false
end

moving = false
end

– Important to return true. This tells the system that the event
– should not be propagated to listeners of any objects underneath.
return true
end

[/lua] [import]uid: 93133 topic_id: 36109 reply_id: 143449[/import]

Yes, exactly. What I do is to check the distance between the began location {x=e.xStart, y=e.yStart} and the ended location {x=e.x, y=e.y} and if it is too far then report a touch, otherwise report a tap.

The only kicker with this is that if you are handling taps with a touch listener you get all the began and moved phases for a tap event, rather than a single, clean tap event.

I have tried writing listener libraries in the past which aggregate all of that but you lose the ability to fire the event and have it cascade down the display object hierarchy. [import]uid: 8271 topic_id: 36109 reply_id: 143452[/import]

Thank you very much horacebury and nick_sherman! It is working exactly the way I want now. It is even working quite nicely on the s3. I love how much support the Corona community provides! [import]uid: 208811 topic_id: 36109 reply_id: 143454[/import]

No worries.

Personally, I really enjoy giving back; I’ve gotten so much support from the community myself over the years. [import]uid: 8271 topic_id: 36109 reply_id: 143455[/import]

I think I got ahead of myself when I said it worked exactly the way I want, after some testing I have realised that it is only hitting one condition in the if “moving == true” statement. I am printing out the state of the moving variable before and after I have moved a tile. This seems to be switching from false to true when a tile is moved and back to false if < 2. I have noticed that if the tile does not move at all the value returned is nil, not 0. Whatever I set local moving = false/true at the start of the function, that is state it stays with [import]uid: 208811 topic_id: 36109 reply_id: 143456[/import]

I think the ‘moving = false’ statement is in the wrong place, this should do it - tidied another possible error as well.

[lua]

local moving = false

local function onTouch( event )
local t = event.target

local phase = event.phase

if “began” == phase then

– Make target the top-most object
local parent = t.parent
parent:insert( t )
display.getCurrentStage():setFocus( t )
t.isFocus = true

– Store initial position
t.x0 = event.x - t.x
t.y0 = event.y - t.y

elseif t.isFocus then

if “moved” == phase then

local move = math.abs(event.x - event.xStart) + math.abs(event.y - event.yStart)

if move > 2 then moving = true; end

– Make object move (we subtract t.x0,t.y0 so that moves are
– relative to initial grab point, rather than object “snapping”).
t.xScale = 1.7
t.yScale = 1.7
t.x = event.x - t.x0
t.y = event.y - t.y0

–t1 = t1…t.value
elseif “ended” == phase or “cancelled” == phase then

if moving == true then

t.x, t.y = posX[index] + (sizeX/2), posY + (sizeY/2);
index = index + 1

else

if t.container then
containers[t.container].isOccupied = false
answer[t.container] = " "
t.container = nil
–answer[t.container] = " "
end

for i = 1, #posX do
if not containers[i].isOccupied then
if (//correct condition) then
t.x, t.y = posX[i] + (sizeX/2), posY + (sizeY/2);
containers[i].isOccupied = true
t.container = i
–check to see if answer is correct
–get string from answer array
–print ( 'table: '…table.concat( answer))
s3 = table.concat(answer)
print(s3)
elseif (//another condition) then
– t.x, t.y = posX2 + (sizeX/2), posY2 + (sizeY/2);
end
end
end

t.xScale = 1
t.yScale = 1
moving = false

end

display.getCurrentStage():setFocus( nil )
t.isFocus = false

end

– Important to return true. This tells the system that the event
– should not be propagated to listeners of any objects underneath.
return true
end

[/lua] [import]uid: 93133 topic_id: 36109 reply_id: 143457[/import]

Thanks nick_sherman, I had to do a bit more work on my side to get correct answers, but it seems to but working fine now. [import]uid: 208811 topic_id: 36109 reply_id: 143468[/import]

Yes, an object can have many listeners, but it doesn’t not make sense to attach multiple listeners for the same event (I can see you’re not doing that, just wanted to highlight that.)

I think you need to work out your specific case user interaction because a began phase is not a tap. A tap (as shown by the tap event listener) is a began phase, followed by no moved phase, followed by an ended phase which is fired within a limited amount of time (about 350 milliseconds.)

Also, be aware that if you want a fairly forgiving touch listener function which handles both the touch and tap events (which is actually a wise choice) you might want to track movement made during the moved phase. The question to ask yourself is: Do you want to limit detection of a tap to literally no movement or can the touch move a little way? ie: If the moved phase is detected does that mean the event is definitely not a tap?

It might sound very pedantic, but you will rarely be able to generate a true tap event on a physical device without causing a little bit of a moved phase. This will be a bigger problem with the newer, more powerful and sensitive devices - which could explain the difference between the S2 and S3. Try ignoring a threshold amount of movement when detecting the difference between a tap and a touch. [import]uid: 8271 topic_id: 36109 reply_id: 143446[/import]

As Horacebury says, I would detect how many pixels the user has moved their finger and use this to decide what to do in the ‘ended’ phase (sorry the formatting is all over the place, I don’t have access to a code editor right now).

[lua]

local moving = false

local function onTouch( event )
local t = event.target

local phase = event.phase

if “began” == phase then

– Make target the top-most object
local parent = t.parent
parent:insert( t )
display.getCurrentStage():setFocus( t )
t.isFocus = true

– Store initial position
t.x0 = event.x - t.x
t.y0 = event.y - t.y

elseif t.isFocus then
if “moved” == phase then

local move = math.abs(event.x - event.xStart) + math.abs(event.y - event.yStart)

if move > 2 then moving = true; end

– Make object move (we subtract t.x0,t.y0 so that moves are
– relative to initial grab point, rather than object “snapping”).
t.xScale = 1.7
t.yScale = 1.7
t.x = event.x - t.x0
t.y = event.y - t.y0

–t1 = t1…t.value
elseif “ended” == phase or “cancelled” == phase then

if moving == true then

t.x, t.y = posX[index] + (sizeX/2), posY + (sizeY/2);
index = index + 1

else

if t.container then
containers[t.container].isOccupied = false
answer[t.container] = " "
t.container = nil
–answer[t.container] = " "
end

for i = 1, #posX do
if not containers[i].isOccupied then
if (//correct condition) then
t.x, t.y = posX[i] + (sizeX/2), posY + (sizeY/2);
containers[i].isOccupied = true
t.container = i
–check to see if answer is correct
–get string from answer array
–print ( 'table: '…table.concat( answer))
s3 = table.concat(answer)
print(s3)
elseif (//another condition) then
– t.x, t.y = posX2 + (sizeX/2), posY2 + (sizeY/2);
end
end
end

t.xScale = 1
t.yScale = 1
display.getCurrentStage():setFocus( nil )
t.isFocus = false
end

moving = false
end

– Important to return true. This tells the system that the event
– should not be propagated to listeners of any objects underneath.
return true
end

[/lua] [import]uid: 93133 topic_id: 36109 reply_id: 143449[/import]

Yes, exactly. What I do is to check the distance between the began location {x=e.xStart, y=e.yStart} and the ended location {x=e.x, y=e.y} and if it is too far then report a touch, otherwise report a tap.

The only kicker with this is that if you are handling taps with a touch listener you get all the began and moved phases for a tap event, rather than a single, clean tap event.

I have tried writing listener libraries in the past which aggregate all of that but you lose the ability to fire the event and have it cascade down the display object hierarchy. [import]uid: 8271 topic_id: 36109 reply_id: 143452[/import]

Thank you very much horacebury and nick_sherman! It is working exactly the way I want now. It is even working quite nicely on the s3. I love how much support the Corona community provides! [import]uid: 208811 topic_id: 36109 reply_id: 143454[/import]

No worries.

Personally, I really enjoy giving back; I’ve gotten so much support from the community myself over the years. [import]uid: 8271 topic_id: 36109 reply_id: 143455[/import]

I think I got ahead of myself when I said it worked exactly the way I want, after some testing I have realised that it is only hitting one condition in the if “moving == true” statement. I am printing out the state of the moving variable before and after I have moved a tile. This seems to be switching from false to true when a tile is moved and back to false if < 2. I have noticed that if the tile does not move at all the value returned is nil, not 0. Whatever I set local moving = false/true at the start of the function, that is state it stays with [import]uid: 208811 topic_id: 36109 reply_id: 143456[/import]

I think the ‘moving = false’ statement is in the wrong place, this should do it - tidied another possible error as well.

[lua]

local moving = false

local function onTouch( event )
local t = event.target

local phase = event.phase

if “began” == phase then

– Make target the top-most object
local parent = t.parent
parent:insert( t )
display.getCurrentStage():setFocus( t )
t.isFocus = true

– Store initial position
t.x0 = event.x - t.x
t.y0 = event.y - t.y

elseif t.isFocus then

if “moved” == phase then

local move = math.abs(event.x - event.xStart) + math.abs(event.y - event.yStart)

if move > 2 then moving = true; end

– Make object move (we subtract t.x0,t.y0 so that moves are
– relative to initial grab point, rather than object “snapping”).
t.xScale = 1.7
t.yScale = 1.7
t.x = event.x - t.x0
t.y = event.y - t.y0

–t1 = t1…t.value
elseif “ended” == phase or “cancelled” == phase then

if moving == true then

t.x, t.y = posX[index] + (sizeX/2), posY + (sizeY/2);
index = index + 1

else

if t.container then
containers[t.container].isOccupied = false
answer[t.container] = " "
t.container = nil
–answer[t.container] = " "
end

for i = 1, #posX do
if not containers[i].isOccupied then
if (//correct condition) then
t.x, t.y = posX[i] + (sizeX/2), posY + (sizeY/2);
containers[i].isOccupied = true
t.container = i
–check to see if answer is correct
–get string from answer array
–print ( 'table: '…table.concat( answer))
s3 = table.concat(answer)
print(s3)
elseif (//another condition) then
– t.x, t.y = posX2 + (sizeX/2), posY2 + (sizeY/2);
end
end
end

t.xScale = 1
t.yScale = 1
moving = false

end

display.getCurrentStage():setFocus( nil )
t.isFocus = false

end

– Important to return true. This tells the system that the event
– should not be propagated to listeners of any objects underneath.
return true
end

[/lua] [import]uid: 93133 topic_id: 36109 reply_id: 143457[/import]

Thanks nick_sherman, I had to do a bit more work on my side to get correct answers, but it seems to but working fine now. [import]uid: 208811 topic_id: 36109 reply_id: 143468[/import]

Yes, an object can have many listeners, but it doesn’t not make sense to attach multiple listeners for the same event (I can see you’re not doing that, just wanted to highlight that.)

I think you need to work out your specific case user interaction because a began phase is not a tap. A tap (as shown by the tap event listener) is a began phase, followed by no moved phase, followed by an ended phase which is fired within a limited amount of time (about 350 milliseconds.)

Also, be aware that if you want a fairly forgiving touch listener function which handles both the touch and tap events (which is actually a wise choice) you might want to track movement made during the moved phase. The question to ask yourself is: Do you want to limit detection of a tap to literally no movement or can the touch move a little way? ie: If the moved phase is detected does that mean the event is definitely not a tap?

It might sound very pedantic, but you will rarely be able to generate a true tap event on a physical device without causing a little bit of a moved phase. This will be a bigger problem with the newer, more powerful and sensitive devices - which could explain the difference between the S2 and S3. Try ignoring a threshold amount of movement when detecting the difference between a tap and a touch. [import]uid: 8271 topic_id: 36109 reply_id: 143446[/import]

As Horacebury says, I would detect how many pixels the user has moved their finger and use this to decide what to do in the ‘ended’ phase (sorry the formatting is all over the place, I don’t have access to a code editor right now).

[lua]

local moving = false

local function onTouch( event )
local t = event.target

local phase = event.phase

if “began” == phase then

– Make target the top-most object
local parent = t.parent
parent:insert( t )
display.getCurrentStage():setFocus( t )
t.isFocus = true

– Store initial position
t.x0 = event.x - t.x
t.y0 = event.y - t.y

elseif t.isFocus then
if “moved” == phase then

local move = math.abs(event.x - event.xStart) + math.abs(event.y - event.yStart)

if move > 2 then moving = true; end

– Make object move (we subtract t.x0,t.y0 so that moves are
– relative to initial grab point, rather than object “snapping”).
t.xScale = 1.7
t.yScale = 1.7
t.x = event.x - t.x0
t.y = event.y - t.y0

–t1 = t1…t.value
elseif “ended” == phase or “cancelled” == phase then

if moving == true then

t.x, t.y = posX[index] + (sizeX/2), posY + (sizeY/2);
index = index + 1

else

if t.container then
containers[t.container].isOccupied = false
answer[t.container] = " "
t.container = nil
–answer[t.container] = " "
end

for i = 1, #posX do
if not containers[i].isOccupied then
if (//correct condition) then
t.x, t.y = posX[i] + (sizeX/2), posY + (sizeY/2);
containers[i].isOccupied = true
t.container = i
–check to see if answer is correct
–get string from answer array
–print ( 'table: '…table.concat( answer))
s3 = table.concat(answer)
print(s3)
elseif (//another condition) then
– t.x, t.y = posX2 + (sizeX/2), posY2 + (sizeY/2);
end
end
end

t.xScale = 1
t.yScale = 1
display.getCurrentStage():setFocus( nil )
t.isFocus = false
end

moving = false
end

– Important to return true. This tells the system that the event
– should not be propagated to listeners of any objects underneath.
return true
end

[/lua] [import]uid: 93133 topic_id: 36109 reply_id: 143449[/import]

Yes, exactly. What I do is to check the distance between the began location {x=e.xStart, y=e.yStart} and the ended location {x=e.x, y=e.y} and if it is too far then report a touch, otherwise report a tap.

The only kicker with this is that if you are handling taps with a touch listener you get all the began and moved phases for a tap event, rather than a single, clean tap event.

I have tried writing listener libraries in the past which aggregate all of that but you lose the ability to fire the event and have it cascade down the display object hierarchy. [import]uid: 8271 topic_id: 36109 reply_id: 143452[/import]