Posted on by

Today’s tutorial demonstrates how to create a sliding panel that has many uses, ranging from games to business applications. You might build an adventure game where the player’s inventory needs to slide on the screen, then hide itself when the user is done. Or you might build a menu that slides in when the player taps the infamous “hamburger” menu icon. Yet another possibility is sliding notices in and out.

As with the previous UI tutorials on navigation panels and text fields, this tutorial will extend the widget library, creating a new widget called widget.newPanel(). Let’s look at the entire function:

function widget.newPanel( options )
    local customOptions = options or {}
    local opt = {}

    opt.location = customOptions.location or "top"

    local default_width, default_height
    if ( opt.location == "top" or opt.location == "bottom" ) then
        default_width = display.contentWidth
        default_height = display.contentHeight * 0.33
    else
        default_width = display.contentWidth * 0.33
        default_height = display.contentHeight
    end

    opt.width = customOptions.width or default_width
    opt.height = customOptions.height or default_height

    opt.speed = customOptions.speed or 500
    opt.inEasing = customOptions.inEasing or easing.linear
    opt.outEasing = customOptions.outEasing or easing.linear

    if ( customOptions.onComplete and type(customOptions.onComplete) == "function" ) then
        opt.listener = customOptions.onComplete
    else 
        opt.listener = nil
    end

    local container = display.newContainer( opt.width, opt.height )

    if ( opt.location == "left" ) then
        container.anchorX = 1.0
        container.x = display.screenOriginX
        container.anchorY = 0.5
        container.y = display.contentCenterY
    elseif ( opt.location == "right" ) then
        container.anchorX = 0.0
        container.x = display.actualContentWidth
        container.anchorY = 0.5
        container.y = display.contentCenterY
    elseif ( opt.location == "top" ) then
        container.anchorX = 0.5
        container.x = display.contentCenterX
        container.anchorY = 1.0
        container.y = display.screenOriginY
    else
        container.anchorX = 0.5
        container.x = display.contentCenterX
        container.anchorY = 0.0
        container.y = display.actualContentHeight
    end

    function container:show()
        local options = {
            time = opt.speed,
            transition = opt.inEasing
        }
        if ( opt.listener ) then
            options.onComplete = opt.listener
            self.completeState = "shown"
        end
        if ( opt.location == "top" ) then
            options.y = display.screenOriginY + opt.height
        elseif ( opt.location == "bottom" ) then
            options.y = display.actualContentHeight - opt.height
        elseif ( opt.location == "left" ) then
            options.x = display.screenOriginX + opt.width
        else
            options.x = display.actualContentWidth - opt.width
        end 
        transition.to( self, options )
    end

    function container:hide()
        local options = {
            time = opt.speed,
            transition = opt.outEasing
        }
        if ( opt.listener ) then
            options.onComplete = opt.listener
            self.completeState = "hidden"
        end
        if ( opt.location == "top" ) then
            options.y = display.screenOriginY
        elseif ( opt.location == "bottom" ) then
            options.y = display.actualContentHeight
        elseif ( opt.location == "left" ) then
            options.x = display.screenOriginX
        else
            options.x = display.actualContentWidth
        end 
        transition.to( self, options )
    end

    return container
end

For consistency, this new widget will follow the coding standard of the other widgets in the Corona SDK open source widget library. Like other widgets, we begin by passing in a table of parameters necessary to create the panel (lines 5 through 27). These include:

  • location — where the panel originates from (left, right, top, or bottom).
  • width — the width of the panel.
  • height — the height of the panel.
  • speed — how fast the panel shows and hides (slides in or out).
  • inEasing — the transition easing method used when the panel shows.
  • outEasing — the transition easing method used when the panel hides.
  • onComplete — a function to execute when the panel completes showing and hiding.

All parameters are optional and are set to reasonable defaults. The location parameter is a string value of either "left", "right", "top" or "bottom" that defaults to "top". The speed parameter defaults to 500 milliseconds. inEasing and outEasing are linear by default but can be set to any of the standard easing methods.

onComplete is optional and defaults to nil. In the above example, we actually create two completion listeners, one for each of the actions the panel supports (panel:show() and panel:hide()).

Code Notes

Let’s step through the function code and inspect some important aspects:

  • When adding the code to define the listener (lines 23-27), it’s helpful to make sure that a parameter was passed, but also that it’s a function and not some other variable type.
  • If a panel slides in from the left or right, it’s reasonable to take up the full height of the visible screen, but the width would need to be specific to the app. Panels coming in from the top or bottom may take up the full width of the device but be limited to 1/3 of the screen.
  • Line 29 creates a display.newContainer(). This is similar to display.newGroup(), except that its bounds are automatically clipped to the defined width and height. This container object is returned on line 95 to the calling function.
  • The panel is positioned off screen based on the location specified. Since there are many ways to set up the size and scale in config.lua, this function uses display.screenOriginX and display.screenOriginY to reference the left and top of the screen. Likewise, display.actualContentWidth and display.actualContentHeight represents the right and bottom edges of the screen. By setting the anchor point to the side of the container that faces the content area, we can use these values to position it. The other value will simply center the container along that edge.
  • To show and hide the panel, two methods are included named show() and hide(). Lines 50-93 implement these two functions. The panels will be shown or hidden using a simple transition.to call, but first we need to determine where to move the panel to. If we’re sliding the panel in/out from the top or bottom, we need to transition the y value. If we’re sliding it in/out from either side, we need to use the x value. Again, we use the defined points for the sides of the screen and either add or subtract the width or height of the panel.

Using the “widget.newPanel()”

To use the panel, simply create a new object, in this case panel, and above that, the optional listener function that’s specified as the onComplete parameter:

local function panelTransDone( target )
    native.showAlert( "Panel", "Complete", { "Okay" } )
    if ( target.completeState ) then
        print( "PANEL STATE IS: "..target.completeState )
    end
end

local panel = widget.newPanel{
    location = "top",
    onComplete = panelTransDone,
    width = display.contentWidth * 0.8,
    height = display.contentHeight * 0.8,
    speed = 250,
    inEasing = easing.outBack,
    outEasing = easing.outCubic
}

When done, panel will be the container object to which we can add whatever content desired using the object:insert() call. To keep things more organized, display objects can optionally be added directly to the panel object:

panel.background = display.newRect( 0, 0, panel.width, panel.height )
panel.background:setFillColor( 0, 0.25, 0.5 )
panel:insert( panel.background )

panel.title = display.newText( "menu", 0, 0, native.systemFontBold, 18 )
panel.title:setFillColor( 1, 1, 1 )
panel:insert( panel.title )

Showing/Hiding

When desired, we can show or hide the panel by simply calling the proper method:

panel:show()
panel:hide()

Composer Notes

If you’re using Composer, you may not want to insert the panel into the Composer group because the panel is generally intended to slide over anything else in the scene. However, if you choose to add it to the scene’s view, it should be the last thing inserted into the scene, or else you should call :toFront() to move it to the top of the scene’s view.

On the general topic of Composer, you may ask “shouldn’t this be done with an overlay?”. While that is a valid approach, overlays are individual scenes and it takes more effort to construct and deconstruct a scene. In contrast, widget.newPanel() yields a simple slide-in/out unit which can be used with or without Composer.

In Summary

This tutorial should get you started with implementing basic sliding panels in your app. With some clever adjustments to the various parameters, you can easily implement a wide variety of panels that appear from different sides of the screen and utilize unique easing transitions.

You can download the code for this tutorial from our GitHub repository.


Posted by . Thanks for reading...

16 Responses to “Widgets — Creating a Sliding Panel”

    • Rob Miracle

      Hi Harry. I believe all the techniques here are available to all subscriber tiers.

      Rob

      Reply
  1. Kerem

    Excellent work! Thank you very much! Any luck for this new widget to be included in the core and supported going forward?

    Reply
    • Rob Miracle

      Sometimes we add new features to the core and write tutorials to help you understand how to use them. Other times like this, it’s a matter of showing you how to do things and give you an ah-ha moment and empower you to build the features you need.

      We are in the process of adding it to the community code and to our GitHub repository for those who want to fork the project and customize it to their needs.

      Reply
  2. Dave baxter

    These type of tutorials could do with some images or a video, so we can see the finished result. Then we can decide if it something that we would find useful.

    Dave

    Reply
  3. Martin

    Hi,

    Could you add an example implementation? As a newbie it is very difficult to get this example working…..

    Martin

    Reply
  4. Jeff Leigh

    How about a screen shot or two in these post Rob?

    Can’t tell if this is immediately useful to me without having to try it.

    Picture speaks a thousand words :-)

    Reply

Leave a Reply

  • (Will Not Be Published)