Widgets — Creating a sliding panel

Widgets — Creating a sliding panel

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 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 53-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 and 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 object: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.

To use this widget in your own projects, please download the code from our GitHub repository.


Tags:
, , ,
Rob Miracle
rob@coronalabs.com

Rob Miracle creates mobile apps for his own enjoyment and the amusement of others. He serves the Corona Community in the forums, on the blog, and at local events.

30 Comments
  • Harry Tran
    Posted at 18:45h, 08 April

    This looks awesome, is this available now for all users including the ones using the Basic Edition?

    • Rob Miracle
      Posted at 15:07h, 09 April

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

      Rob

  • Kerem
    Posted at 22:12h, 08 April

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

    • Peter
      Posted at 09:28h, 09 April

      +1

    • Rob Miracle
      Posted at 15:22h, 09 April

      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.

  • Mario Roberti
    Posted at 09:31h, 09 April

    Hey I rolled my own similar to this, but this one has some much more elegant solutions than mine. *YOINK*! Thanks a ton!

  • Dave baxter
    Posted at 03:03h, 12 April

    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

  • Martin
    Posted at 02:24h, 13 April

    Hi,

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

    Martin

  • Leonardo Póvoa
    Posted at 08:30h, 15 April

    Could add a full example with story board??

  • Tutorial: Creating a Sharing Panel | Corona Labs
    Posted at 13:30h, 15 April

    […] Corona doesn’t offer a pre-built sharing panel. As such, expanding upon last week’s widget.newPanel() tutorial, let’s walk through the steps to construct a sharing […]

  • Rob Miracle
    Posted at 16:29h, 15 April

    Okay here’s an example: https://coronalabs.com/blog/2014/04/15/tutorial-creating-a-sharing-panel/

    Not a fully functional one with Storyboard, but you should be able to drop this into any project. The thing with Storyboard and Composer is you probably do not want to insert it into the view group since it’s supposed to be on top of everything. But then you become responsible for removing it.

    • J. A. Whye
      Posted at 01:21h, 14 August

      That link goes to a 404 page. Is that tutorial (creating a sharing panel) still somewhere on the site? My searches failed me.

      • Rob Miracle
        Posted at 11:57h, 14 August

        That tutorial is out of date and we’ve taken it down.

  • Mahdi
    Posted at 21:50h, 16 April

    wow, this is great! Keep up the good work #teamCorona!

  • Jeff Leigh
    Posted at 08:26h, 17 April

    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 🙂

    • Rob Miracle
      Posted at 16:41h, 17 April

      Hi Jeff. I understand the desire for this, which is one reason we did this week’s “sharing panel” tutorial (referenced two posts above), so you could see it in action (and there is a screen shot with it. To demoed anything more would have required a tutorial on it’s own, ergo this week’s sharing panel.

  • Poh
    Posted at 07:22h, 05 August

    Hi Rob,

    Can you change the location of entry on the fly? Meaning is it possible to say rotate the entry from left clockwise to bottom each time panel:show() is called by setting the location dynamically?

    Thanks.

    • Rob Miracle
      Posted at 17:44h, 05 August

      I’m not sure I follow what you want to do. But the show code is pretty straight forward, so you can modify it to do what you want.

  • david
    Posted at 06:38h, 14 September

    Nice tutorial, it would be nice to block clicks/etc of the parent scene where slide panel shows up at the top of the scene.

    • Rob Miracle
      Posted at 10:25h, 14 September

      Some programmers might want to have background items touchable. It’s easy to fix that. Had we done a Composer overlay scene, you would have had a chance to set the .isModal flag. You can expand this to include your own version of .isModal by having a full screen invisible rectangle that has it’s .isHitTestable property set to true and then add a touch and tap handler to it that consumes them.

      Rob

      • Sang
        Posted at 13:52h, 10 July

        Hi Rob

        I am not sure where should I put “.isModal = true” above code to make the background items untouchable?

        Please, advise.

        Thank you.

        Sang.

        • Rob Miracle
          Posted at 14:12h, 11 July

          The sliding panel as written does not support .isModal. That’s a feature using Composer. If you want to implement something similar to .isModal, simply put a touch and tap handler on the panels’s background that throws the touch or tap away.

  • Miro
    Posted at 03:56h, 22 January

    Can I use display.new BitmapText instead display.newText?

    • Miro
      Posted at 04:14h, 22 January

      I answer myself … yes 🙂

      panel.title = display.newBitmapText(“Old text”, screenCenterX, screenCenterY, “lobster0”, 42)
      panel:insert( panel.title )

      ….

      panel.title:setText(“New text”)

      Works 🙂

    • Rob Miracle
      Posted at 08:27h, 22 January

      We don’t provide a display.newBitmapText API, but if you have one, no reason you can’t use it.

  • ycl
    Posted at 20:16h, 05 July

    Rob,
    Thanks for creating this widget! I implemented it on my app, and I was wondering if you had any solution to using this sliding panel on a composer scene with native text fields? My initial solution was simply to textfield:removeSelf() when the panel:show() was called, but that obviously gives me a proble once the panel is hidden because I cant simply unremove the text field (or hide it to begin with).
    Any help is greatly appreciated!

    • ycl
      Posted at 20:58h, 05 July

      EDIT: just realized there is a textfield.isVisible option. Any recommendations on how to make it be visible with a delay? So it doesnt immediately show up once the panel is hidden

    • Rob Miracle
      Posted at 11:41h, 07 July

      In the code that slides the panel on, it can try and hide the text field(s) before you transition the panel on.