The best way to understand how to write good custom component is to go through some of them. Recently I was just going thorough the Spark Button source code and found it quite interesting to share.
This is not the full and exhaustive treatment or line-by-line explanation of source code. Rather I will try to highlight the main coding snippets and some very good practices we can follow up. If you like this post please let me know so that I can bring more such posts.
So at the top level Spark Button extends ButtonBase class (which contains much of the magic ;) ) and implements IButton interface.
Spark Button class extends ButtonBase and implements IButton interface. The class ButtonBase is the base class for all Spark button components and has all the logic. The Button (current class) and ToggleButtonBase classes are subclasses of ButtonBase. The CheckBox and RadioButton are subclasses of ToggleButtonBase.
This is not the full and exhaustive treatment or line-by-line explanation of source code. Rather I will try to highlight the main coding snippets and some very good practices we can follow up. If you like this post please let me know so that I can bring more such posts.
So at the top level Spark Button extends ButtonBase class (which contains much of the magic ;) ) and implements IButton interface.
public class Button extends ButtonBase implements IButton
{
public function Button()
{
super();
}
…..
}Spark Button class extends ButtonBase and implements IButton interface. The class ButtonBase is the base class for all Spark button components and has all the logic. The Button (current class) and ToggleButtonBase classes are subclasses of ButtonBase. The CheckBox and RadioButton are subclasses of ToggleButtonBase.
public class ButtonBase extends
SkinnableComponent implements IFocusManagerComponent
{
public function ButtonBase()
{
super();
// DisplayObjectContainer properties.
// Setting mouseChildren to false
ensures that mouse events
// are dispatched from the Button
itself,
// not from its skins, icons, or
TextField.
// One reason for doing this is that
if you press the mouse button
// while over the TextField and
release the mouse button while over
// a skin or icon, we want the player
to dispatch a "click" event.
// Another is that if mouseChildren
were true and someone uses
// Sprites rather than Shapes for the
skins or icons,
// then we we wouldn't get a click
because the current skin or icon
// changes between the mouseDown and
the mouseUp.
// (This doesn't happen even when mouseChildren
is true if the skins
// and icons are Shapes, because
Shapes never dispatch mouse events;
// they are dispatched from the Button
in this case.)
mouseChildren = false;
// add event listeners to the button
addHandlers();
}
protected function addHandlers():void
{
addEventListener(MouseEvent.ROLL_OVER,
mouseEventHandler);
addEventListener(MouseEvent.ROLL_OUT,
mouseEventHandler);
addEventListener(MouseEvent.MOUSE_DOWN,
mouseEventHandler);
addEventListener(MouseEvent.MOUSE_UP,
mouseEventHandler);
addEventListener(MouseEvent.CLICK,
mouseEventHandler);
}
protected function
mouseEventHandler(event:Event):void
{
var mouseEvent:MouseEvent = event as MouseEvent;
switch (event.type)
{
case
MouseEvent.ROLL_OVER:
{
// if the user
rolls over while holding the mouse button
if
(mouseEvent.buttonDown && !mouseCaptured)
return;
hovered = true;
break;
}
case
MouseEvent.ROLL_OUT:
{
hovered = false;
break;
}
case MouseEvent.MOUSE_DOWN:
{
// When the button is down we need to listen for mouse //events
outside the button so that we update the state //appropriately on mouse
up. Whenever mouseCaptured //changes to
false,it will take care to remove those //handlers.
addSystemMouseHandlers();
mouseCaptured = true;
break;
}
case
MouseEvent.MOUSE_UP:
{
// Call
buttonReleased() if we mouse up on the button and if
// the mouse was captured before.
if (event.target
== this)
{
hovered = true;
if (mouseCaptured)
{
buttonReleased();
mouseCaptured = false;
}
}
break;
}
// Prevent the propagation of
click from a disabled Button.
// This is conceptually a
higher-level event and
// developers will expect their
click handlers not to fire
// if the Button is disabled.
case
MouseEvent.CLICK:
{
if (!enabled)
event.stopImmediatePropagation();
else
clickHandler(MouseEvent(event));
return;
}
}
if (mouseEvent)
mouseEvent.updateAfterEvent();
}
…..
}
Here the method addHandlers() in the constructor adds various mouse related events and the corresponding listener mouseEventHandler. The listener method mouseEventHandler checks out event type and then takes proper action. In simple terms for roll-over it sets the hovered variable to true (which is further used by getCurrentSkinState to return proper state), roll-out makes hovered to false, for mouse down some of the system-mouse-handlers are added and when mouse is out we call up respective methods. If button is disabled then we want to prevent mouse-click event and so is done by event.StopImmediatePropagation() method.
Here we have mouse event we want the update to happen at this particular render cycle itself and dont want to wait for next render event, that is done by this method call (updateAfterEvent).
The
Here we have mouse event we want the update to happen at this particular render cycle itself and dont want to wait for next render event, that is done by this method call (updateAfterEvent).
IButton interface is more of a marker
interface and it has only one property emphasized
and one function callLater.
The IFocusManagerComponent interface
defines the interface that focusable components must implement in order to
receive focus from the FocusManager. The base implementations of this interface are
in the UIComponent
class, but UIComponent does not implement the full IFocusManagerComponent interface
since some UIComponents are not intended to receive focus. Therefore, to
make a UIComponent-derived
component be a valid focusable component, we simply add "implements IFocusManagerComponent" to
the class definition.
The SkinnableComponent class defines the base class for skinnable
components. The skins used by a SkinnableComponent class are typically child classes of the Skin class.
When
the label for the button is set, it actually sets the value of content:
public function set
label(value:String):void
{
// label property is just a proxy to
the content.
// The content setter will dispatch
the event.
content = value;
}
public function get
label():String
{
return (content != null) ?
content.toString() : "";
}
private var _content:*;
[Bindable("contentChange")]
public function get
content():Object
{
return _content;
}
public function set content(value:Object):void
{
_content = value;
// Push to the optional labelDisplay
skin part
if (labelDisplay)
labelDisplay.text = label;
dispatchEvent(new Event("contentChange"));
}
content
property lets us pass an arbitrary object to be
used in a custom skin of the button. When a skin defines the optional part labelDisplay
then a string representation of content
will be pushed down to that part's text
property. The default skin uses this mechanism to
render the content
as the
button label.
[SkinPart(required="false")]
/**
* A
skin part that defines the label of the button.
*
*
@langversion 3.0
*
@playerversion Flash 10
*
@playerversion AIR 1.5
*
@productversion Flex 4
*/
public var labelDisplay:TextBase;
As
labelDisplay is a skin part,
we also have partAdded and partRemoved function.
override protected function
partAdded(partName:String, instance:Object):void
{
super.partAdded(partName,
instance);
if (instance == labelDisplay)
{
labelDisplay.addEventListener("isTruncatedChanged",
labelDisplay_isTruncatedChangedHandler);
// Push down to the part only if the
label was explicitly set
if (_content !==
undefined)
labelDisplay.text = label;
}
}
override protected function
partRemoved(partName:String,instance:Object):void
{
super.partRemoved(partName, instance);
if (instance == labelDisplay)
{
labelDisplay.removeEventListener("isTruncatedChanged",
labelDisplay_isTruncatedChangedHandler);
}
}
This was a brief introduction and I hope you like it. Some further good references are:
Useful Links
Spark and MX Components relationship: http://www-958.ibm.com/software/data/cognos/manyeyes/visualizations/spark-and-mx-components-relationsh
Simple Custom Component (Sortable List)
5 comments:
Very good post. I was looking for something like that since long.
Good to know that you found what you were looking for :) Even I was not able to find any post that can help me learn the source code, that why I wrote a simple post like this :)
Thanx boss , for posting it really nice ......i have been wandering around abt how to create your own components ? Can u suggest some tips of how can we really master this ..
your welcome sids. What exactly you want to know? Well there are many things you take care of:
1. Never start from the scratch until you really need it.
2. Start with some simple customization it will boost your confidence.
3. Check out simple custom components example.
4. If you extend any Spark components (most of them extend SkinnableComponent), so get idea of base component and skin contract.
Even I was not able to discover any publish that can help me understand the resource value, that why I had written a easy publish like this i have been roaming around abt how to make your own elements ? Can u recommend some guidelines of how can we really expert this ..
Post a Comment