Tutorial: Building a Sliding Menu

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:

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):

If this value, assigned to the local variable dx, exceeds 5, we pass focus to the scroll view:

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):

For a scroll view that can move in both directions, we can check for movement along both axes:

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:

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!

Tags:
Rob Miracle
[email protected]

Rob is the Developer Relations Manager for Corona Labs. Besides being passionate about helping other developers make great games using Corona, he is also enjoys making games in his spare time. Rob has been coding games since 1979 from personal computers to mainframes. He has over 16 years professional experience in the gaming industry.

27 Comments
  • Erich Grüttner Díaz
    Posted at 18:39h, 19 August

    Just when I need it!
    Thank you very much, Rob!!!

  • Mo
    Posted at 21:32h, 19 August

    WOW!!!!!!!!!!!!!!!! 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 August

      Just add

      Runtime:addEventListener(“touch”, showSlidingMenu)

      at the end

      • apathy_docko
        Posted at 16:02h, 04 June

        you need to make it with a space after the listener

        so past Runtime:addEventListener (“touch”, showSliding Menu)

    • Rob Miracle
      Posted at 05:13h, 20 August

      You 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 August

        Hi, 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 August

          The 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 August

            Thanks 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 December

          Just 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 August

    Awesome ! Thanks a lot for sharing this, Rob.

  • John
    Posted at 08:20h, 20 August

    Wow, 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 August

    Duh!! 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 January

      Does anyone know how to load a scene when I tap the circles? Thanks

  • Mo
    Posted at 10:01h, 21 August

    Hello 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 August

      Hi 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 August

        At 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 August

        Ali, even if the line looks like its the same, try with this one:

        Runtime:addEventListener(“touch”,showSlidingMenu)

  • Dim
    Posted at 06:32h, 18 September

    Thanks 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 December

    Hi, 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 December

      Ok 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 December

        typo – *much

  • Nick
    Posted at 03:22h, 11 March

    Hi 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 March

      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)

  • jose
    Posted at 11:54h, 17 May

    Hi 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.