Widget button inside a scrollview doesn't work when I use the takeFocus method

Hi, I have some widget buttons in a scroll view. I am using the takeFocus method to continue scrolling if a button has touched. I am using the example code in http://docs.coronalabs.com/api/type/ScrollViewWidget/takeFocus.html

When the scrolling finishes the button doesn’t work. I tried to turn takeFocus from event to nil or to setFocus to event.target, when the phase in touch function ends, but I get an error. Do you know how I can fix this?

What is your error?

Thanks for your responses.

Sorry Bred, when I mean I get an error, it’s not an error in terminal. I have the same problem as Belunch has. In my scrollView I have a lot of widget buttons. When I call the takefocus method in within the “moved” phase of button’s touch listener, I touch a button and the scrollView works fine. After that If I touch the same button, then it doesn’t response to the button touch listener.

So I thought to disable the takeFocus in the “ended” phase of button’s touch listener, enabling the button again. But It doesn’t work.

I tried to call the button:setEnabled(false) instead takeFocus method in within the “moved” phase of button’s touch listener. I had the same problem.

If you put a print statement for event phase you will see that touch button event dissapears.

I think something happens in the scrollview object. Perhaps a conflict between the two events. I don’t know.

If you try to enable the button ( button:setEnabled(true)) in the scrollview listener, (in the event.limitReached, because the event.phase doesn’t work) nothing happens. 

So my first solution was to use an object as a button instead of widget button. I added a tap listener to the object and everything worked, except from a 2 sec delay when you tapped the object. With widget button it’s faster.

Hi guys,

I think I solved this for you via the useful “dispatchEvent()” function. This function speaks softly but carries a big stick… it has saved me in several instances, and comes to the rescue again.

Below is my working code, with 2 buttons (at least, it works as far as I can tell, but please let me know if you find any fixes). In this code, you basically just dispatch an “ended” phase to the buttons when the scrollView “takes focus”, thus treating them as they were un-clicked without the user actually doing so (again, the beauty of :dispatchEvent).

Note that you need to up-reference the buttons, so Lua knows them when you try to dispatch the event to them. You could definitely make this more elegant, by putting the buttons inside a table or something, and then looping through that table during the dispatch instead of my “hard-coded” method. So, this code needs some enhancement, but the basic idea should work.

[lua]

local widget = require( “widget” )

– Create the ScrollView

local scrollView = widget.newScrollView

{

    top = 100,

    left = 10,

    width = 300,

    height = 400,

    scrollWidth = 600,

    scrollHeight = 800,

    horizontalScrollDisabled = true

}

scrollView.hasFocusFlag = false

local button1

local button2

– The touch listener function for the button (created below)

local function handleButtonEvent( event )

    local phase = event.phase

    if ( phase == “began” ) then

        scrollView.hasFocusFlag = false

    elseif ( phase == “moved” ) then

        local dy = math.abs( ( event.y - event.yStart ) )

        – If the touch on the button has moved more than 10 pixels,

        – pass focus back to the scroll view so it can continue scrolling

        if ( dy > 10 ) then

            scrollView:takeFocus( event )

            scrollView.hasFocusFlag = true

            button1:dispatchEvent( { name=“touch”, target=button1, phase=“ended” } )

            button2:dispatchEvent( { name=“touch”, target=button2, phase=“ended” } )

        end

    

    elseif ( phase == “ended” ) then

        if ( scrollView.hasFocusFlag == false ) then

            print( event.target.id…" RELEASED!" )

        end

    end

    return true

end

– Create the button

button1 = widget.newButton

{

    left = 100,

    top = 200,

    id = “button1”,

    label = “BUTTON 1”,

    onEvent = handleButtonEvent

}

scrollView:insert( button1 )

button2 = widget.newButton

{

    left = 100,

    top = 250,

    id = “button2”,

    label = “BUTTON 2”,

    onEvent = handleButtonEvent

}

scrollView:insert( button2 )

[/lua]

Again, let me know what might not be working here. I tossed this example together pretty quickly, so I didn’t test for every potential use case.

Brent

Brent and Belunch the code works fine with default file or not. But if you use the same code in storyboard, then the button’s don’t work.

Here is the code in storyboard:

[lua] local storyboard = require( “storyboard” )

local widget = require( “widget” )

local scene = storyboard.newScene()

local W = display.contentWidth

local H = display.contentHeight

local scrollView,  button1, button2

– The touch listener function for the button (created below)

local function handleButtonEvent( event )

    local phase = event.phase

    local t = event.target

    if ( phase == “began” ) then

        scrollView.hasFocusFlag = false

    elseif ( phase == “moved” ) then

        local dy = math.abs( ( event.y - event.yStart ) )

        – If the touch on the button has moved more than 10 pixels,

        – pass focus back to the scroll view so it can continue scrolling

        if ( dy > 10 ) then

            scrollView:takeFocus( event )

            scrollView.hasFocusFlag = true

            t:dispatchEvent( { name=“touch”, target=t, phase=“ended” } )

        end

    

    elseif ( phase == “ended” ) then

        if ( scrollView.hasFocusFlag == false ) then

            print( event.target.id…" RELEASED!" )

        end

    end

    return true

end

– Called when the scene’s view does not exist:

function scene:createScene( event )

    local group = self.view

    – Create the ScrollView

    scrollView = widget.newScrollView

    {

        top = 100,

        left = 10,

        width = 300,

        height = 400,

        scrollWidth = 600,

        scrollHeight = 800,

        hideBackground = true,

        horizontalScrollDisabled = true

    }

    scrollView.hasFocusFlag = false

    button1 = widget.newButton{ defaultFile = “menuBtn.png”, overFile = “menuBtnOver.png”, width = 80, height = 80, id = “button1”, label = “BUTTON 1”, onEvent = handleButtonEvent}

button2 = widget.newButton{ defaultFile = “menuBtn.png”, overFile = “menuBtnOver.png”, width = 80, height = 80, id = “button2”, label = “BUTTON 2”, onEvent = handleButtonEvent}

    button1.x = W * 0.5; button1.y = H * 0.3;

    button2.x = W * 0.5; button2.y = H * 0.3 + button2.height * 1.1;

    

    scrollView:insert( button1 )

    scrollView:insert( button2 )

    group:insert(scrollView)

end

– Called BEFORE scene has moved onscreen:

function scene:willEnterScene( event )

        local group = self.view          

end

– Called immediately after scene has moved onscreen:

function scene:enterScene( event )

        local group = self.view

end

– Called when scene is about to move offscreen:

function scene:exitScene( event )

        local group = self.view

end

– Called AFTER scene has finished moving offscreen:

function scene:didExitScene( event )

        local group = self.view

end

– Called prior to the removal of scene’s “view” (display group)

function scene:destroyScene( event )

        local group = self.view

end

– Called if/when overlay scene is displayed via storyboard.showOverlay()

function scene:overlayBegan( event )

        local group = self.view

        local overlay_name = event.sceneName  – name of the overlay scene

end

– Called if/when overlay scene is hidden/removed via storyboard.hideOverlay()

function scene:overlayEnded( event )

        local group = self.view

        local overlay_name = event.sceneName  – name of the overlay scene

end


– END OF YOUR IMPLEMENTATION


– “createScene” event is dispatched if scene’s view does not exist

scene:addEventListener( “createScene”, scene )

– “willEnterScene” event is dispatched before scene transition begins

scene:addEventListener( “willEnterScene”, scene )

– “enterScene” event is dispatched whenever scene transition has finished

scene:addEventListener( “enterScene”, scene )

– “exitScene” event is dispatched before next scene’s transition begins

scene:addEventListener( “exitScene”, scene )

– “didExitScene” event is dispatched after scene has finished transitioning out

scene:addEventListener( “didExitScene”, scene )

– “destroyScene” event is dispatched before view is unloaded, which can be

– automatically unloaded in low memory situations, or explicitly via a call to

– storyboard.purgeScene() or storyboard.removeScene().

scene:addEventListener( “destroyScene”, scene )

– “overlayBegan” event is dispatched when an overlay scene is shown

scene:addEventListener( “overlayBegan”, scene )

– “overlayEnded” event is dispatched when an overlay scene is hidden/removed

scene:addEventListener( “overlayEnded”, scene )


return scene [/lua]

Hi guys,

The code works fine outside Storyboard (in my tests), so likely this is an issue in how you incorporated it inside Storyboard (not a bug). If you haven’t watched the full video on exactly how Storyboard works, and how it makes calls, then you really must do so… it’s a core aspect to understanding the library before you use it.

http://www.youtube.com/embed/2IDzu6qWRCM?rel=0

@belunch,

I duplicated the issue where the dispatch works for non-image buttons, but doesn’t work when an image background is there. I’ll have to investigate further.

Brent

Hi @belunch,

I’m definitely investigating the button thing you reported, since I could replicate it on my side.

As for the “takeFocus()” thing, I believe the dispatch method is logical, and that there’s not much we can do for takeFocus() to “revert” whatever you’ve done within the scrollView to whatever you had originally. What I mean is, the content that you place inside a scrollView is entirely your decision… it could be buttons, switches, animations, or practically anything else. So, restoring those objects to some original state is not practical within the takeFocus() call… that function is merely to pass the touch down to the scrollView so it can continue moving… it doesn’t (and realistically can’t) internally reverse-function and take focus away from whatever you just pressed on.

As an example, what if a user put their own touchable objects within the scrollView? Perhaps little animated objects that would animate when the user touched them, and then stopped animating when the touch (scroll) moved a certain degree? How would the scrollView know how to go back and stop the animation on those very custom-coded objects with their sprite sequences?

I guess my overall point here is that the scrollView and whatever you put inside it are not inherently “related” in any way… so we can’t add some kind of relational functionality to widget.newButton() in regards to takeFocus(), when the two processes are un-associated at the core level.

Hope this makes sense, have a good weekend!

Brent

Hi Belunch. I replace the widget button with an object and add them a tap (not touch) listener. It works fine.

Hi @belunch,

This bug is still on our list to solve. In the meantime, could you place a static image behind each button? I know it wouldn’t light up on the over phase, but it may be a passable solution until this bug is fixed, and once that happens, you’ll still have the widget buttons in place and could then give them the proper internal background (and remove the other static images).

Brent

What is your error?

Thanks for your responses.

Sorry Bred, when I mean I get an error, it’s not an error in terminal. I have the same problem as Belunch has. In my scrollView I have a lot of widget buttons. When I call the takefocus method in within the “moved” phase of button’s touch listener, I touch a button and the scrollView works fine. After that If I touch the same button, then it doesn’t response to the button touch listener.

So I thought to disable the takeFocus in the “ended” phase of button’s touch listener, enabling the button again. But It doesn’t work.

I tried to call the button:setEnabled(false) instead takeFocus method in within the “moved” phase of button’s touch listener. I had the same problem.

If you put a print statement for event phase you will see that touch button event dissapears.

I think something happens in the scrollview object. Perhaps a conflict between the two events. I don’t know.

If you try to enable the button ( button:setEnabled(true)) in the scrollview listener, (in the event.limitReached, because the event.phase doesn’t work) nothing happens. 

So my first solution was to use an object as a button instead of widget button. I added a tap listener to the object and everything worked, except from a 2 sec delay when you tapped the object. With widget button it’s faster.

Hi guys,

I think I solved this for you via the useful “dispatchEvent()” function. This function speaks softly but carries a big stick… it has saved me in several instances, and comes to the rescue again.

Below is my working code, with 2 buttons (at least, it works as far as I can tell, but please let me know if you find any fixes). In this code, you basically just dispatch an “ended” phase to the buttons when the scrollView “takes focus”, thus treating them as they were un-clicked without the user actually doing so (again, the beauty of :dispatchEvent).

Note that you need to up-reference the buttons, so Lua knows them when you try to dispatch the event to them. You could definitely make this more elegant, by putting the buttons inside a table or something, and then looping through that table during the dispatch instead of my “hard-coded” method. So, this code needs some enhancement, but the basic idea should work.

[lua]

local widget = require( “widget” )

– Create the ScrollView

local scrollView = widget.newScrollView

{

    top = 100,

    left = 10,

    width = 300,

    height = 400,

    scrollWidth = 600,

    scrollHeight = 800,

    horizontalScrollDisabled = true

}

scrollView.hasFocusFlag = false

local button1

local button2

– The touch listener function for the button (created below)

local function handleButtonEvent( event )

    local phase = event.phase

    if ( phase == “began” ) then

        scrollView.hasFocusFlag = false

    elseif ( phase == “moved” ) then

        local dy = math.abs( ( event.y - event.yStart ) )

        – If the touch on the button has moved more than 10 pixels,

        – pass focus back to the scroll view so it can continue scrolling

        if ( dy > 10 ) then

            scrollView:takeFocus( event )

            scrollView.hasFocusFlag = true

            button1:dispatchEvent( { name=“touch”, target=button1, phase=“ended” } )

            button2:dispatchEvent( { name=“touch”, target=button2, phase=“ended” } )

        end

    

    elseif ( phase == “ended” ) then

        if ( scrollView.hasFocusFlag == false ) then

            print( event.target.id…" RELEASED!" )

        end

    end

    return true

end

– Create the button

button1 = widget.newButton

{

    left = 100,

    top = 200,

    id = “button1”,

    label = “BUTTON 1”,

    onEvent = handleButtonEvent

}

scrollView:insert( button1 )

button2 = widget.newButton

{

    left = 100,

    top = 250,

    id = “button2”,

    label = “BUTTON 2”,

    onEvent = handleButtonEvent

}

scrollView:insert( button2 )

[/lua]

Again, let me know what might not be working here. I tossed this example together pretty quickly, so I didn’t test for every potential use case.

Brent

Brent and Belunch the code works fine with default file or not. But if you use the same code in storyboard, then the button’s don’t work.

Here is the code in storyboard:

[lua] local storyboard = require( “storyboard” )

local widget = require( “widget” )

local scene = storyboard.newScene()

local W = display.contentWidth

local H = display.contentHeight

local scrollView,  button1, button2

– The touch listener function for the button (created below)

local function handleButtonEvent( event )

    local phase = event.phase

    local t = event.target

    if ( phase == “began” ) then

        scrollView.hasFocusFlag = false

    elseif ( phase == “moved” ) then

        local dy = math.abs( ( event.y - event.yStart ) )

        – If the touch on the button has moved more than 10 pixels,

        – pass focus back to the scroll view so it can continue scrolling

        if ( dy > 10 ) then

            scrollView:takeFocus( event )

            scrollView.hasFocusFlag = true

            t:dispatchEvent( { name=“touch”, target=t, phase=“ended” } )

        end

    

    elseif ( phase == “ended” ) then

        if ( scrollView.hasFocusFlag == false ) then

            print( event.target.id…" RELEASED!" )

        end

    end

    return true

end

– Called when the scene’s view does not exist:

function scene:createScene( event )

    local group = self.view

    – Create the ScrollView

    scrollView = widget.newScrollView

    {

        top = 100,

        left = 10,

        width = 300,

        height = 400,

        scrollWidth = 600,

        scrollHeight = 800,

        hideBackground = true,

        horizontalScrollDisabled = true

    }

    scrollView.hasFocusFlag = false

    button1 = widget.newButton{ defaultFile = “menuBtn.png”, overFile = “menuBtnOver.png”, width = 80, height = 80, id = “button1”, label = “BUTTON 1”, onEvent = handleButtonEvent}

button2 = widget.newButton{ defaultFile = “menuBtn.png”, overFile = “menuBtnOver.png”, width = 80, height = 80, id = “button2”, label = “BUTTON 2”, onEvent = handleButtonEvent}

    button1.x = W * 0.5; button1.y = H * 0.3;

    button2.x = W * 0.5; button2.y = H * 0.3 + button2.height * 1.1;

    

    scrollView:insert( button1 )

    scrollView:insert( button2 )

    group:insert(scrollView)

end

– Called BEFORE scene has moved onscreen:

function scene:willEnterScene( event )

        local group = self.view          

end

– Called immediately after scene has moved onscreen:

function scene:enterScene( event )

        local group = self.view

end

– Called when scene is about to move offscreen:

function scene:exitScene( event )

        local group = self.view

end

– Called AFTER scene has finished moving offscreen:

function scene:didExitScene( event )

        local group = self.view

end

– Called prior to the removal of scene’s “view” (display group)

function scene:destroyScene( event )

        local group = self.view

end

– Called if/when overlay scene is displayed via storyboard.showOverlay()

function scene:overlayBegan( event )

        local group = self.view

        local overlay_name = event.sceneName  – name of the overlay scene

end

– Called if/when overlay scene is hidden/removed via storyboard.hideOverlay()

function scene:overlayEnded( event )

        local group = self.view

        local overlay_name = event.sceneName  – name of the overlay scene

end


– END OF YOUR IMPLEMENTATION


– “createScene” event is dispatched if scene’s view does not exist

scene:addEventListener( “createScene”, scene )

– “willEnterScene” event is dispatched before scene transition begins

scene:addEventListener( “willEnterScene”, scene )

– “enterScene” event is dispatched whenever scene transition has finished

scene:addEventListener( “enterScene”, scene )

– “exitScene” event is dispatched before next scene’s transition begins

scene:addEventListener( “exitScene”, scene )

– “didExitScene” event is dispatched after scene has finished transitioning out

scene:addEventListener( “didExitScene”, scene )

– “destroyScene” event is dispatched before view is unloaded, which can be

– automatically unloaded in low memory situations, or explicitly via a call to

– storyboard.purgeScene() or storyboard.removeScene().

scene:addEventListener( “destroyScene”, scene )

– “overlayBegan” event is dispatched when an overlay scene is shown

scene:addEventListener( “overlayBegan”, scene )

– “overlayEnded” event is dispatched when an overlay scene is hidden/removed

scene:addEventListener( “overlayEnded”, scene )


return scene [/lua]

Hi guys,

The code works fine outside Storyboard (in my tests), so likely this is an issue in how you incorporated it inside Storyboard (not a bug). If you haven’t watched the full video on exactly how Storyboard works, and how it makes calls, then you really must do so… it’s a core aspect to understanding the library before you use it.

http://www.youtube.com/embed/2IDzu6qWRCM?rel=0

@belunch,

I duplicated the issue where the dispatch works for non-image buttons, but doesn’t work when an image background is there. I’ll have to investigate further.

Brent

Hi @belunch,

I’m definitely investigating the button thing you reported, since I could replicate it on my side.

As for the “takeFocus()” thing, I believe the dispatch method is logical, and that there’s not much we can do for takeFocus() to “revert” whatever you’ve done within the scrollView to whatever you had originally. What I mean is, the content that you place inside a scrollView is entirely your decision… it could be buttons, switches, animations, or practically anything else. So, restoring those objects to some original state is not practical within the takeFocus() call… that function is merely to pass the touch down to the scrollView so it can continue moving… it doesn’t (and realistically can’t) internally reverse-function and take focus away from whatever you just pressed on.

As an example, what if a user put their own touchable objects within the scrollView? Perhaps little animated objects that would animate when the user touched them, and then stopped animating when the touch (scroll) moved a certain degree? How would the scrollView know how to go back and stop the animation on those very custom-coded objects with their sprite sequences?

I guess my overall point here is that the scrollView and whatever you put inside it are not inherently “related” in any way… so we can’t add some kind of relational functionality to widget.newButton() in regards to takeFocus(), when the two processes are un-associated at the core level.

Hope this makes sense, have a good weekend!

Brent

Hi Belunch. I replace the widget button with an object and add them a tap (not touch) listener. It works fine.

Hi @belunch,

This bug is still on our list to solve. In the meantime, could you place a static image behind each button? I know it wouldn’t light up on the over phase, but it may be a passable solution until this bug is fixed, and once that happens, you’ll still have the widget buttons in place and could then give them the proper internal background (and remove the other static images).

Brent

I am having a similar problem when using the tableView widget. However, there seems to be no takeFocus() for tableView. How might I prevent touch events from being recognized by my buttons in the tableView while I am trying instead to scroll?