Posted on by

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:

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() scrollView:removeSelf(); scrollView = nil; 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):

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:

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

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:

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:

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!


Posted by . Thanks for reading...

15 Responses to “Tutorial: Building a Sliding Menu”

  1. Mo

    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

    Reply
    • Rob Miracle

      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

      Reply
      • ali

        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

        Reply
  2. John

    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.

    Reply
  3. MO

    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.

    Reply
  4. Mo

    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

    Reply
    • ali

      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?

      Reply
      • Rob Miracle

        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

        Reply
  5. Dim

    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.

    Reply

Leave a Reply

  • (Will Not Be Published)