08 April 2014
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:
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
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 usesdisplay.screenOriginX
anddisplay.screenOriginY
to reference the left and top of the screen. Likewise,display.actualContentWidth
anddisplay.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()
andhide()
. Lines 53-93 implement these two functions. The panels will be shown or hidden using a simpletransition.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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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:
1 2 3 4 5 6 7 |
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:
1 2 3 |
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.
Harry Tran
Posted at 18:45h, 08 AprilThis looks awesome, is this available now for all users including the ones using the Basic Edition?
Rob Miracle
Posted at 15:07h, 09 AprilHi Harry. I believe all the techniques here are available to all subscriber tiers.
Rob
Kerem
Posted at 22:12h, 08 AprilExcellent 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 AprilSometimes 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 AprilHey 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 AprilThese 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 AprilHi,
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 AprilCould 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 AprilOkay here’s an example: /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 AugustThat 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 AugustThat tutorial is out of date and we’ve taken it down.
Mahdi
Posted at 21:50h, 16 Aprilwow, this is great! Keep up the good work #teamCorona!
Jeff Leigh
Posted at 08:26h, 17 AprilHow 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 AprilHi 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.
Greg Richardson
Posted at 13:39h, 21 AprilRob – thanks for the sample / inspiration! Drove my to finally get a little project of mine wrapped up. For those looking for screenshots / demo, I put a couple seconds of video together to get an idea of the Sliding Panel.
http://gregrichardson.me/2014/04/21/51987254/corona-sliding-panel-example/
App I just released for Android users (pending iOS): https://play.google.com/store/apps/details?id=com.apcension.WalletMon
Thx again Rob for all your contributions around here 🙂
Rob Miracle
Posted at 18:22h, 21 AprilGlad to contribute.
l
Poh
Posted at 07:22h, 05 AugustHi 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 AugustI’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 SeptemberNice 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 SeptemberSome 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 JulyHi 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 JulyThe 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 JanuaryCan I use display.new BitmapText instead display.newText?
Miro
Posted at 04:14h, 22 JanuaryI 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 JanuaryWe 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 JulyRob,
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 JulyEDIT: 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 JulyIn the code that slides the panel on, it can try and hide the text field(s) before you transition the panel on.