19 August 2014
Tutorial: Building a Sliding Menu
Sometimes apps will need to display a menu of items or other information that exceeds the amount of space available for display. Some example include an inventory system in a business app or a list of power-ups in a game.
Fortunately, Corona makes this fairly easy. You simply need a widget.newScrollView(), some items to insert into the view, a method to launch the slider, and a function to handle the objects that are touched.
Basic Code
Here’s the basic code required for this tutorial:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
local widget = require( "widget" ) local scrollView local icons = {} local function iconListener( event ) local id = event.target.id if ( event.phase == "moved" ) then local dx = math.abs( event.x - event.xStart ) if ( dx > 5 ) then scrollView:takeFocus( event ) end elseif ( event.phase == "ended" ) then --take action if an object was touched print( "object", id, "was touched" ) timer.performWithDelay( 10, function() if scrollView then scrollView:removeSelf() scrollView = nil end end ) end return true end local function showSlidingMenu( event ) if ( "ended" == event.phase ) then scrollView = widget.newScrollView { width = 460, height = 100, scrollWidth = 1200, scrollHeight = 100, verticalScrollDisabled = true } scrollView.x = display.contentCenterX scrollView.y = display.contentCenterY local scrollViewBackground = display.newRect( 600, 50, 1200, 100 ) scrollViewBackground:setFillColor( 0, 0, 0.2 ) scrollView:insert( scrollViewBackground ) --generate icons for i = 1, 20 do icons[i] = display.newCircle( i * 56, 50, 22 ) icons[i]:setFillColor( math.random(), math.random(), math.random() ) scrollView:insert( icons[i] ) icons[i].id = i icons[i]:addEventListener( "touch", iconListener ) end end return true end |
Handling Scroll Movement
Because we need to accept touches on the objects (icons) in the slider, they must each have a touch handler. However, if the user touches one while scrolling, we typically want scrolling to continue instead of triggering a completed touch response.
To accomplish this, we monitor the "moved"
phase in the touch handler function. If there’s an almost imperceptible amount of movement from the point where the user’s touch began, we do not “pass” the touch to the scroll view. However, if the user drags the touch point past a certain distance, we can assume that scrolling of the slide view is desired, and we give focus to the scroll view via scrollView:takeFocus().
Calculating the distance which should be considered for movement of the scroll view is simple: it’s just the absolute value of the difference between the current touch position (event.x
) and where the user’s touch began (event.xStart
):
1 |
local dx = math.abs( event.x - event.xStart ) |
If this value, assigned to the local variable dx
, exceeds 5
, we pass focus to the scroll view:
1 2 3 4 |
if ( dx > 5 ) then scrollView:takeFocus( event ) end |
In this tutorial, the scroll view is restricted to horizontal scrolling (verticalScrollDisabled=true
). Thus, we only test for movement on the x axis. However, it’s easy to test for y movement if using a vertically-restricted scroll view (horizontalScrollDisabled=true
):
1 2 3 4 |
local dy = math.abs( event.y - event.yStart ) if ( dy > 5 ) then scrollView:takeFocus( event ) end |
For a scroll view that can move in both directions, we can check for movement along both axes:
1 2 3 4 5 |
local dx = math.abs( event.x - event.xStart ) local dy = math.abs( event.y - event.yStart ) if ( dx > 5 or dy > 5 ) then scrollView:takeFocus( event ) end |
In all of these examples, the premise is that the user’s touch has moved more than 5 pixels from the starting location, at which point we pass focus to the scroll view and allow it to continue scrolling. Depending on the app content size and the device screen size, 5 pixels may not feel “natural,” so it might be necessary to adjust this value slightly.
Handling Icon Touches
If an icon gets an "ended"
phase, we can assume that it received a full touch cycle and that focus was never passed to the scroll view. Inside this conditional block, we perform whatever action is suitable — in this case, we dismiss (remove) the slider after a very short timer delay:
1 |
timer.performWithDelay( 10, function() scrollView:removeSelf(); scrollView = nil; end ) |
Slider Setup
The setup for the scroll view is simple: we just configure a widget.newScrollView() and insert our objects into it. Additionally, we add a touch handler on each which references the iconListener
touch handler function outlined above.
In Conclusion
The applications for this module are extensive. It could be used to show a list of other apps for people to buy. It could also be used to show various worlds for players to select, similar to Angry Birds. In fact, when combined with the previous tutorial on level selection, this slider could hold options to play levels 1-20 as one icon in the view, levels 21-40 as another icon, etc. Then, each icon could launch the level selector for that world. You’re only limited by your imagination!
Erich Grüttner Díaz
Posted at 18:39h, 19 AugustJust when I need it!
Thank you very much, Rob!!!
Mo
Posted at 21:32h, 19 AugustWOW!!!!!!!!!!!!!!!! That’s a fantastic Rob! THANK YOU. By the way is this tutorial code ready to run as is? I quickly tried it and got nothing on the screen. It is probably I was rushing to run it. I will need to sit down and play with it.
In any event thank you so much. That’s one of the things that I always envied apps that has it and never was able to figure out how to do correctly.
Cheers.
Mo
SImpleex
Posted at 03:06h, 20 AugustJust add
Runtime:addEventListener(“touch”, showSlidingMenu)
at the end
apathy_docko
Posted at 16:02h, 04 Juneyou need to make it with a space after the listener
so past Runtime:addEventListener (“touch”, showSliding Menu)
Rob Miracle
Posted at 05:13h, 20 AugustYou need to provide some way to call the function, be it a button, something that happens automatically inside of a composer:show() function or however you want to invoke it. Perhaps just add a widget.newButtion() and use the function for the button’s onEvent handler.
Rob
ali
Posted at 08:45h, 21 AugustHi, thanks so much for sharing this tutorial. I’m relatively new to Corona (and coding for that matter). I’m not quite sure what I need to do to get this to work. Is there any way you could include the code for it to run with the widget.newButtion() you suggested in the comment above? I’d really appreciate it! Ali
Rob Miracle
Posted at 20:20h, 21 AugustThe first example code in the documentation:
http://docs.coronalabs.com/api/library/widget/newButton.html
Would do just want you want. Just change the “handleButtonEvent” with “showSlidingMenu”
ali
Posted at 10:07h, 22 AugustThanks for your reply Rob. I’m still not having any luck getting it to work. Is the demo project available to download by any chance?
lhwang
Posted at 04:11h, 17 DecemberJust paste this part :
local button1 = widget.newButton
{
left = 100,
top = 200,
id = “button1”,
label = “Default”,
fontSize = 50,
onEvent = showSlidingMenu
}
at the last line of the code above, and it will work.
(This code from api library – widget new Button)
Vibhu Bhola
Posted at 00:25h, 20 AugustAwesome ! Thanks a lot for sharing this, Rob.
John
Posted at 08:20h, 20 AugustWow, we just put this type of menu in our app the other day… Except we used a static background and just grouped the background and scrollview.
MO
Posted at 17:37h, 20 AugustDuh!! So sorry guys. I was tired that day. THANK YOU, it works beautifully. I was wondering how to change the code to make it snap to place when scrolling. Often in games, I see that when i swap left of right, the items snap in place.
In any event this little code is going to be used often from now on. Thank you!
Mo.
BenCoder
Posted at 16:07h, 20 JanuaryDoes anyone know how to load a scene when I tap the circles? Thanks
Mo
Posted at 10:01h, 21 AugustHello Ali,
Just add this at the end of the code as Simplex suggested
Runtime:addEventListener(“touch”, showSlidingMenu)
You will need to touch the screen to see it (works on the simulator)
This is great stuff!!!
Mo
ali
Posted at 04:27h, 22 AugustHi Mo, thanks for your reply! I put the event listener at the end, but still won’t work :/ Not sure why it’s not working for me, I literally copied and pasted the code and added the runtime listener so it should be working fine right?
Rob Miracle
Posted at 14:11h, 22 AugustAt this point I would recommend taking this to the Forums where we can have you post your code. The blog comments doesn’t handle code well.
ROb
undecode
Posted at 21:40h, 24 AugustAli, even if the line looks like its the same, try with this one:
Runtime:addEventListener(“touch”,showSlidingMenu)
Dim
Posted at 06:32h, 18 SeptemberThanks Rob, great explanation.
Same as Mo though, i would love to see it snap. I tried a few options with transition to set points and/or scrollToPosition put could never get it to look perfect and bug free. If by any chance you want to push this tutorial a bit further, that would be fantastic.
Cheers.
lhwang
Posted at 23:13h, 17 DecemberHi, this menu is the horizontal menu, would like to know how it could become the vertical menu (just like facebook sidebar menu)?
Tried many times but no luck yet 🙁
Greatly appreciate any helps.
lhwang
Posted at 12:34h, 18 DecemberOk finally I can make it become the vertical menu, just change the value of width and height and the horizontal become vertical will do the tricks.
Thanks so muhc for this tutorial, it saves my times!! 😀
lhwang
Posted at 12:35h, 18 Decembertypo – *much
Nick
Posted at 03:22h, 11 MarchHi Rob,
Firstly, Thanks for the tutorial. Great to get me started.
I have been playing around with the code for a little bit to look at using a scroll view for a menu system. However in my trials i cannot get the scroll to work the full width of the scrollWidth. For whatever reason the ‘menu’ only gets to about 2.70 x display.contentWidth. Not the full 3 x display.contentWidth.
Is there a maximum width or something else that might be causing this? Am i missing something, or is it a bug. Im running build 2511 on mac
cheers
Nick
heres my code:
local widget = require( "widget" )
local scrollView
local icons = {}
local numMenuScreens = 3
local circleRadius = 200
local cW = display.contentWidth
local cH = display.contentHeight
local function iconListener( event )
local id = event.target.id
local object = event.target
if ( event.phase == "moved" ) then
local dx = math.abs( event.x - event.xStart )
if ( dx > 5 ) then
scrollView:takeFocus( event )
end
elseif ( event.phase == "ended" ) then
--take action if an object was touched
end
return true
end
local function myShowSlidingMenu( )
-- if ( "ended" == event.phase ) then
scrollView = widget.newScrollView
{
width = cW,
height = cH,
scrollWidth = cW * numMenuScreens,
scrollHeight = 100,
verticalScrollDisabled = true, -- horizontal scrolling only
isBounceEnabled = false
}
scrollView.x = display.contentCenterX
scrollView.y = display.contentCenterY
--generate icons
for i = 1, numMenuScreens do
icons[i] = display.newCircle( (i - 0.5 ) * cW, display.contentCenterY, circleRadius )
print( "Debug comment: icons[i].x" , icons[i].x )
icons[i]:setFillColor( math.random(), math.random(), math.random() )
scrollView:insert( icons[i] )
icons[i].id = i
icons[i]:addEventListener( "touch", iconListener )
end
-- end
return true
end
myShowSlidingMenu()
print( "Debug comment: contentWidth" , cW ,"x3" , cW*3)
Nick
Posted at 04:03h, 11 Marchlocal widget = require( “widget” )
local scrollView
local icons = {}
local numMenuScreens = 3
local circleRadius = 200
local cW = display.contentWidth
local cH = display.contentHeight
local function iconListener( event )
local id = event.target.id
local object = event.target
if ( event.phase == “moved” ) then
local dx = math.abs( event.x – event.xStart )
if ( dx > 5 ) then
scrollView:takeFocus( event )
end
elseif ( event.phase == “ended” ) then
–take action if an object was touched
end
return true
end
local function myShowSlidingMenu( )
— if ( “ended” == event.phase ) then
scrollView = widget.newScrollView
{
width = cW,
height = cH,
scrollWidth = cW * numMenuScreens,
scrollHeight = 100,
verticalScrollDisabled = true, — horizontal scrolling only
isBounceEnabled = false
}
scrollView.x = display.contentCenterX
scrollView.y = display.contentCenterY
–generate icons
for i = 1, numMenuScreens do
icons[i] = display.newCircle( (i – 0.5 ) * cW, display.contentCenterY, circleRadius )
print( “Debug comment: icons[i].x” , icons[i].x )
icons[i]:setFillColor( math.random(), math.random(), math.random() )
scrollView:insert( icons[i] )
icons[i].id = i
icons[i]:addEventListener( “touch”, iconListener )
end
— end
return true
end
myShowSlidingMenu()
print( “Debug comment: contentWidth” , cW ,”x3″ , cW*3)
Rob Miracle
Posted at 14:58h, 11 MarchCan you post this in the forums please? Comments is not good for posting code snippets.
Thanks
Rob
Nick
Posted at 16:21h, 11 MarchThanks Rob, I have reposted here
http://forums.coronalabs.com/topic/39165-scrollwidth-and-scrollheight-in-scrollview-not-working/?p=286907
jose
Posted at 11:54h, 17 MayHi sorry for the hassle I wanted to see if they could help me on how to do that an image fits with different devices, since when you change to different device image or rather button hides a part of somebody I can explain how to adjust them.
Rob Miracle
Posted at 20:08h, 17 MayThis is a topic better addressed in the Forums. Please ask there: http://forums.coronalabs.com