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.