Drawer Component in Flex 4
I needed a good way to have a large settings panel with a minimal visual impact. The obvious answer is to hide or minimize or collapse the settings panel when not in use. I thought about using Flexlib‘s WindowShade component (which I’ve dicussed in detail in Styling Flexlib’s WindowShade), but why reuse something when you can reinvent the wheel? Plus, it always helps to hone my Flex 4 custom component kung-fu. So, I chose to implement a simple sliding drawer component from scratch as a Flex 4 component.
The Drawer component is a vanilla container (it actually extends SkinnableContainer
), so it will happily take any spark component for its children. Before I dive into my implementation, let’s check out the finished drawer component in action (view source enabled):
Just click on the handle to open and close the drawer.
Extending SkinnableContainer
The Drawer component itself, is just pure AS3, but the demo above uses a few MXML skins to achieve the desired look-and-feel. This is a pretty standard pattern that I see during Flex 4 development, so expect it when you write your own custom components.
We’ll review the component implementation in two steps. First, we focus on the skin state management aspect of the drawer. Here’s the relevant code (taken from Drawer.as
):
[SkinState("opened")] public class Drawer extends SkinnableContainer { private var _opened:Boolean = false; public function get opened():Boolean { return _opened; } public function set opened(value:Boolean):void { if (_opened != value) { _opened = value; invalidateSkinState(); } } override protected function getCurrentSkinState():String { return (opened ? 'opened' : super.getCurrentSkinState()); } ... }
The Drawer
component can either be closed (the default) or opened. To model theses states, we use the normal
state from the superclass to represent the closed drawer, and add a new opened
SkinState
to represent the opened drawer. We just expose a simple opened
boolean property with a custom getter and setter, and then override the getCurrentSkinState()
method. It’s important to remember the states we are talking about are skin states, and not component states (see Flex 4 Component States vs. Skin States for the difference).
Second, we focus on the action of opening and closing the drawer. Here’s the relevant code (taken from Drawer.as
):
public class Drawer extends SkinnableContainer { ... [SkinPart(required="false")] public var openButton:Button; private function clickHandler(event:MouseEvent):void { opened = !opened; } override protected function partAdded(partName:String, instance:Object):void { super.partAdded(partName, instance); if (instance == openButton) { openButton.addEventListener(MouseEvent.CLICK, clickHandler); } } override protected function partRemoved(partName:String, instance:Object):void { super.partRemoved(partName, instance); if (instance == openButton) { openButton.removeEventListener(MouseEvent.CLICK, clickHandler); } } }
The Drawer
component includes a simple spark button, as an optional SkinPart
, that is used to initiate the state change. The partAdded()
and partRemoved()
methods are overridden to manage and adding and removing of the button’s click event handler. And lastly, the clickHandler()
method flips between skin states by toggling the opened
boolean property.
Usage
Using the Drawer
is the same as any container. In MXML, just put any child components you want between the container’s open and close tags:
<containers:Drawer ... skinClass="skins.DrawerSkin"> <!-- components go here --> </containers:Drawer>
Here we also apply the DrawerSkin
to our container.
Skins
The DrawerSkin
is responsible for creating the desired look-and-feel and generally making the Drawer
component look cool. Here are the interesting parts of the skin:
<s:Skin ...> <fx:Metadata> [HostComponent("containers.Drawer")] </fx:Metadata> <s:states> <s:State name="normal" /> <s:State name="opened" /> <s:State name="disabled" /> </s:states> ... <s:Button id="openButton" ... skinClass="skins.DrawerOpenButtonSkin" skinClass.opened="skins.DrawerCloseButtonSkin" /> <s:Group id="contentGroup" ... includeIn="opened" /> </s:Skin>
The DrawerSkin
has three skin states, normal
and disabled
are inherited from SkinnableContainer
, but opened
is our custom skin state. The skin also includes the optional openButton
SkinPart
, which itself uses two custom buttons skins, one for the open drawer and one for the closed drawer. Lastly, note that the container’s content is only displayed when the skin is in the opened
state via the newfangled inline state syntax: includeIn="opened"
.
steveb
7.20.2010
How could you adapt this for a drawer that opened horizontally across the screen?
justin
7.20.2010
@steveb: The Drawer component itself (Drawer.as) is obviously orientation independent. It is the skin that controls the orientation, which I hard-coded to open up from the bottom.
To change the drawer to open horizontally, you’d need to modify the placement of the components in the DrawerSkin (the background Rect, the openButton, and the contentGroup). Also, you’d need to modify the open and close buttons which I again hard-coded to an up arrow and down arrow.
Good luck.
Quentin
10.25.2010
Gotta say thanks, man.
Great article, we don’t see much like that out there. Cool to have someone do things in a clean and detailed fashion!
My future components will not look the same… At least on the code side of things.
Quentin
10.25.2010
Oh! And a quick question: where (and how) would you set a custom component’s default skin? I’ve Googled this a bit (and tried a few alternatives) but couldn’t get anything clean to work… Got tip?
justin
10.25.2010
@Quentin: That’s an easy one. The best way to set a “default” skin on a custom component is via CSS like this:
Then you can override with a simple styleName in MXML
<comps:MyComp styleName="cool" />
. And then you wire the overriding skin via CSS like this:Quentin
10.26.2010
Well, what you describe absolutely works but I was looking for a way to really default the skin class, that is, prevent the final user from having to set it at all (neither within a CSS file, nor via inline
skinClass
attribute)…I finally found that and I’ll share it there as I think this might interest everyone.
Note: this chunk of code goes in a static initializer (in a class called
ToggleBlock
) setting the defaultskinClass
toToggleBlockDefaultSkin
.Thoughts?
justin
10.26.2010
@Quentin: Solid. Using a static initializer is a good solution.
JC
12.9.2010
Great sample code. Thanks
I run into a interesting problem when I try to use this in a bigger scale. I have five drawer going, now the problem is since they are draw one of top of another, When the last one slides out it cover up all the other drawer button. Any idea how to make it so the buttons will never be covered if I have multiple of them? I have a feeling, I might even need some kind of manager class to keep track of different state for multiple drawer. Thanks for any idea in advance.
justin
12.9.2010
@JC: I’m not sure multiple drawers is the correct solution (or proper UX for that matter). I envision the drawer as a “config panel” that slides out only when you need it.
If you need to put lots of info in the drawer, but don’t want it to be really big, then I suggest using some other visual organizer kind of component (tabs or accordion). For example, you might have one drawer that comes up from the bottom with a horizontal accordion inside it.
JCSL
2.12.2011
Nice! Exactly what I was looking for, thanks!
Joel
2.18.2011
Love it. Using it. Thanks!
Beau N. Brewer
5.10.2011
Static initializer is a good solution, but if you have a library of components a better way would be to include a default.css in the /src folder of your library. This isn’t well documented, but works like a charm.
juniorFlex
10.27.2011
how to make drawer to right hand side and change the height of it. Couldn’t figure that out.
justin
10.27.2011
@juniorFlex: See my comment #2 above.
You’ll need to modify the placement of the components in the DrawerSkin and you’ll also need new open and close buttons (mine are hardcoded to be up and down arrows).
juniorFlex
10.27.2011
I played around for the arrows and is there a nice way to make the arrows on the left and right hand side. Again Thanks for the reply .. I did not see your message and posted the same question again .. Once again thank you.
Chris
11.12.2011
Great component! I wanted to be able to modify this so that the drawer button is at the top and when clicked it slides down with the contents. I can’t seem to figure out a way to do this with transitions. Any ideas?
justin
11.14.2011
@Chris: see comment #2 above.
You’ll need to to flip-flop the open close buttons, so down arrow is open and up arrow is close.
Morlek
1.16.2013
Can someone tell me how i do to make this drawer on top of everything wen i slide it and dont “push” my viewstack away? something like this http://kuwamoto.org/2006/04/10/new-flex-component-sliding-drawer-v-05/comment-page-1/#comment-473591
Clinton
7.1.2013
Please list the specifics on what needs to be modified to make the drawer slide in and out from the right. I just cant figure out what needs to be changed in Drawerskin.mxml. Thanks in advance!