Saturnboy
 7.22

I’ve been building a lot of Flex 4 custom components lately, including a sliding drawer, a multiple content area container, and now SuperTextInput. Nor will this be that last, because I think I have a few more in me. I thought it would be useful to spend some time in the details, explaining The Flex 4 Way and how I try to walk the path.

SuperTextInput is a prompting, clearable TextInput extension in Flex 4. It’s just an enhanced version of the default TextInput control, and as such, it follows a fairly standard pattern of custom component creation.

Enhanced Component Pattern

It’s almost too stupid to call this a pattern, but it’s so common in custom component creation that I’ll run with it. Also, I’ve found it to be worthwhile to distinguish between adding new functionality to a component already present in the framework (aka an enhanced component) versus creating a truly custom component.

The enhanced component pattern is just two simple steps:

  1. Extend – extend some default component and add some new functionality
  2. Skin – make it look good

In my version of reality, these steps carry equal weight, because almost all worthwhile functionality in Flex touches the UI in some fashion, so the design and UX (the look-and-feel, it’s usability, the integration into the rest of the app, etc.) are critical. Don’t forget or skimp on step #2 because it’s all the client, team, customer ever sees.

A Prompting TextInput

Since SuperTextInput has two new pieces of functionality (the prompt and the clear button), I’ll split them apart, and consider each part separately. First, the prompt is merely the text you see when the TextInput is empty. It often becomes a space saving label, because it can be used to tell the user what goes into the TextInput without costing the UI any screen real estate.

Thinking more about the prompt, we want the prompt text to be visible initially, but it should disappear when the user clicks (or tabs) to the control, and only returns when the control loses focus and is still empty. So this tells us that we need to communicate both the prompt text and it’s visibility to our skin. The prompt text can just be a simple Label SkinPart, but it’s visibility is complicated enough that it makes sense to add a new prompting SkinState.

Here’s a functioning PromptingTextInput custom component (which is simply the prompting code lifted from SuperTextInput.as):

package components {
    import flash.events.FocusEvent;
    import mx.events.FlexEvent;
    import spark.components.Label;
    import spark.components.TextInput;
    import spark.events.TextOperationEvent;
 
    [SkinState("prompting")]
    public class PromptingTextInput extends TextInput {
 
        [SkinPart(required="false")]
        public var promptDisplay:Label;
 
        private var _prompt:String = '';
        private var _focused:Boolean = false;
 
        public function PromptingTextInput() {
            super();
 
            //watch for programmatic changes to text property
            this.addEventListener(FlexEvent.VALUE_COMMIT, textChangedHandler, false, 0, true);
 
            //watch for user changes (aka typing) to text property
            this.addEventListener(TextOperationEvent.CHANGE, textChangedHandler, false, 0, true);
        }
 
        [Bindable]
        public function get prompt():String {
            return _prompt;
        }
        public function set prompt(value:String):void {
            if (_prompt != value) {
                _prompt = value;
                if (promptDisplay != null) {
                    promptDisplay.text = value;
                }
            }
        }
 
        private function textChangedHandler(e:Event):void {
            invalidateSkinState();
        }
 
        override protected function focusInHandler(event:FocusEvent):void {
            super.focusInHandler(event);
            _focused = true;
            invalidateSkinState();
        }
        override protected function focusOutHandler(event:FocusEvent):void {
            super.focusOutHandler(event);
            _focused = false;
            invalidateSkinState();
        }
 
        override protected function partAdded(partName:String, instance:Object):void {
            super.partAdded(partName, instance);
 
            if (instance == promptDisplay) {
                promptDisplay.text = prompt;
            }
        }
 
        override protected function getCurrentSkinState():String {
            if (prompt.length > 0 && text.length == 0 && !_focused) {
                return 'prompting';
            }
            return super.getCurrentSkinState();
        }
    }
}

In addition to the promptDisplay SkinPart and the new prompting SkinState, there is a lot of other stuff going on in the above code. First, as is typical with data-driven SkinParts, we back the promptDisplay with a good old prompt property. The net is the fairly common pattern of: check if the SkinPart is not null, then do something to it. So in the prompt setter, we assign the incoming value to the private _prompt variable, then check if promptDisplay is available and if yes, set it’s text property. The setter does the job of updating the prompt, but only once everything is happily running. In order to get the data to the skin initially, we must use the partAdded() override to pass the local prompt to the promptDisplay‘s text property. And that’s it for the prompt text.

The prompt visibility part requires lots of event watching, and also SkinState stuff because we made the choice to push visibility via the prompting SkinState. First, we wire up both the programmatic text change events and the user text change events to a handler, textChangedHandler(), that does nothing more than invalidate the state. TextInput change events are a little wacky, but the code works fine. Next, instead of wiring the focus events to another handler (as seen in this prompting TextInput component by Andy McIntosh), we simply override the protected handlers in the parent and add our focus-tracking logic directly. Finally, we override getCurrentSkinState() to do the work of figuring out whether or not the prompt should be displayed.

A skin for PromptingTextInput is now trivial because our component does the work of pushing the important information to the skin. If we ignore all the pretty stuff, the skin is very simple:

<?xml version="1.0" encoding="utf-8"?>
<s:SparkSkin ...>
    ...
    <s:states>
        <s:State name="normal"/>
        <s:State name="prompting"/>
        <s:State name="disabled"/>
    </s:states>
 
    <s:RichEditableText id="textDisplay" ... />
    <s:Label id="promptDisplay" includeIn="prompting" ... />
</s:SparkSkin>

We add the prompting State to the list of states and also add the promptDisplay Label component. By using the standard inline state syntax, includeIn="prompting" our Label is shown only in the prompting state.

A Clearable TextInput

The second piece of SuperTextInput functionality is the clear button. The clear button appears when the TextInput has a value, and when clicked, it clears that value (which re-displays the prompt). Again, there are two pieces of information the need to be communicated to the skin to create the clear button functionality: the button itself and it’s visibility. In this case, since the visibility is so simple (on if TextInput has a value, otherwise off), we’ll just punt and manage it directly in the component. Therefore, the only a Button SkinPart for the clear button will be pushed to the skin.

Here’s a functioning ClearableTextInput custom component (which is simply the clear button code lifted from SuperTextInput.as):

package components {
    import flash.events.Event;
    import flash.events.MouseEvent;
    import mx.events.FlexEvent;
    import spark.components.Button;
    import spark.components.TextInput;
    import spark.events.TextOperationEvent;
 
    public class ClearableTextInput extends TextInput {
 
        [SkinPart(required="false")]
        public var clearButton:Button;
 
        public function ClearableTextInput() {
            super();
 
            //watch for programmatic changes to text property
            this.addEventListener(FlexEvent.VALUE_COMMIT, textChangedHandler, false, 0, true);
 
            //watch for user changes (aka typing) to text property
            this.addEventListener(TextOperationEvent.CHANGE, textChangedHandler, false, 0, true);
        }
 
        private function textChangedHandler(e:Event):void {
            if (clearButton) {
                clearButton.visible = (text.length > 0);
            }
        }
 
        private function clearClick(e:MouseEvent):void {
            text = '';
        }
 
        override protected function partAdded(partName:String, instance:Object):void {
            super.partAdded(partName, instance);
 
            if (instance == clearButton) {
                clearButton.addEventListener(MouseEvent.CLICK, clearClick);
                clearButton.visible = (text != null && text.length > 0);
            }
        }
 
        override protected function partRemoved(partName:String, instance:Object):void {
            super.partRemoved(partName, instance);
 
            if (instance == clearButton) {
                clearButton.removeEventListener(MouseEvent.CLICK, clearClick);
            }
        }
    }
}

After the PromptingTextInput, the ClearableTextInput is a little more straightforward. First, we have the clearButton SkinPart and it’s clearClick() event handler. Wiring the handler function to the button is done in the partAdded() override, and un-wiring in the partRemoved() override. Next, button visibility is managed by watching for both programmatic text change events and user text change events. The handler, textChangedHandler(), sets the button as visible when the control has text in it.

As I mentioned above, I decided against pushing the clearButton‘s visibility down to the skin via a SkinState, and instead chose to manage it inside the component by setting clearButton.visible directly. I tend to favor the SkinState method when more than one thing needs to change in the skin or if I need advanced visuals (like transitions). If I need to do just one thing and I don’t care about visuals, I’ll do it inside the component. The two examples here aren’t the best to illustrate the two options, but that’s my general thought process when building a custom component and custom skin.

A skin for ClearingTextInput is super trivial. Again, ignoring all the pretty stuff, the skin is:

<?xml version="1.0" encoding="utf-8"?>
<s:SparkSkin ...>
    ...
    <s:states>
        <s:State name="normal"/>
        <s:State name="disabled"/>
    </s:states>
 
    <s:RichEditableText id="textDisplay" ... />
    <s:Button id="clearButton" ... />
</s:SparkSkin>

Just add the clearButton Button and position it.

Fusion, Glorious Fusion

The fusion process of creating SuperTextInput from PromptingTextInput and ClearableTextInput is nothing more than copy and paste. SuperTextInput has lots of uses, but my favorite is to use it to capture text input to filter a list. It also works great as a search box, or in any smart form UI. Enjoy.

Here’s the finished product showing all three custom components skinned and ready for action (view source enabled):

Flash is required. Get it here!
Files

 7.15

Back in the days of Flex 3, if you wanted multiple content areas in your main application, you’d need to arrange some set of containers (Canvas, HBox, VBox) in the app and fill them with content. It was just your basic Flex 3 development process. The danger, of course, is that you are mixing content with presentation, aka bad separation of concerns. Today, with the power of Flex 4 skins, we can avoid this issue by moving the presentation layer into a skin (or set of skins). And thus, we can do a much better job achieving a happy level of separation of concerns.

The Flex 3 Way

To give a concrete example, I’ll build a blog layout (yes, another blog layout) with a header, footer, sidebar, and main content areas. But before we get started, let’s review the old Flex 3 way:

<mx:HBox id="header">
    <mx:Image source="@Embed('assets/logo.png')" />
</mx:HBox>
 
<mx:Canvas id="body" width="800">
    <mx:Text text="main content" width="600" />
 
    <mx:VBox id="sidebar" x="600" width="200">
        <mx:Text text="Sidebar" />
        <mx:Text text="sidebar content" width="100%" />
    </mx:VBox>
</mx:Canvas>
 
<mx:VBox id="footer">
    <mx:Text text="2010 saturnboy" styleName="footer" />
</mx:VBox>

The above code comes from a previous post, Designing in Flex 3, but has been modified to make sense here. You’ve got you basic blog design: a box for the header, footer, and body, where body is subsequently is divided into a main content area and a sidebar.

The 3-in-4 Way, aka The Wrong Way

The unfortunate next step in a Flex developer’s evolution is what I like to call the Flex 3-in-4 way. This is a the way of neanderthals, which is to say, it is an evolutionary dead end. If you ever have the bad luck to see 3-in-4 code, you can be sure you are dealing with a novice Flex 4 developer. In general, the 3-in-4 way consists of making the simple transcription: CanvasGroup, HBoxHGroup, VBoxVGroup. But the most damning tipoff of a 3-in-4 developer is the assertion that one is now a Flex 4 developer and the learning curve wasn’t all that bad. While I do think Flex 4 is more of an evolutionary release than a revolutionary release, it’s different enough. And it is particularly different on the design side of the framework, how it handles skins, layout, etc.

If we just transcribe the above example, we get some classic 3-in-4 code:

<s:HGroup id="header">
    <s:Label text="Multi Content Area Example" styleName="header" />
</s:HGroup>
 
<s:Group id="body" width="800">
    <s:Label text="main content" width="600" />
 
    <s:VGroup x="600" width="200" styleName="sidebarBox">
        <s:Label text="Sidebar" styleName="title" />
        <s:Label text="sidebar content" styleName="sidebar" />
    </s:VGroup>
</s:Group>
 
<s:VGroup id="footer">
    <s:Label text="2010 saturnboy" styleName="footer" />
</s:VGroup>

*Barf*, please not do this. This code has all the same issues as the Flex 3 code in the first example, and moreover it is a slap in the face of The Flex 4 Way and all of its improvements.

The Flex 4 Way

Yes, there is a Flex 4 Way and it looks like this.

First, we rewrite the main app using a custom container. Ignoring the specifics of the custom container for a moment, here is the re-written main app (minus some clutter):

<containers:headerContent>
    <s:Label text="Multi Content Area Example" styleName="header" />
</containers:headerContent>
 
<containers:sidebarContent>
    <s:RichText left="0" right="0" styleName="sidebar">
        <s:content>
            <s:p fontSize="20">Sidebar</s:p>
            <s:p>sidebar content</s:p>
        </s:content>
    </s:RichText>
</containers:sidebarContent>
 
<containers:footerContent>
    <s:Label text="2010 saturnboy" styleName="footer" />
</containers:footerContent>
 
<s:RichText left="0" right="0">
    <s:content>
        <s:p fontSize="30">Content</s:p>
        <s:p>main content</s:p>
    </s:content>
</s:RichText>

As you can see, the main app is now a nice set of semantic buckets, one for each of the content areas. Header stuff goes in the headerContent bucket, footer stuff goes in the footerContent bucket, etc.

Building a Multi Content Area Container

Second, we need to create a custom container with the nice set of semantic buckets used in the above code. This is achieved by following a straightforward formula:

  1. Extend SkinnableContainer – Extend SkinnableContainer or some child class. In our sample app, our custom container extends Application (which extends SkinnableContainer).
  2. Add Buckets – add some content buckets (in the form of xxxContent) as Arrays. These become the MXML tags used to bucket components together. Each content bucket has a public getter, but most importantly a public setter that accepts an incoming Array of IVisualElements and uses the magical mxmlContent property to assign it to the associated SkinPart.
  3. Add SkinParts – add some matching SkinParts (in the form of xxxGroup) as spark Groups. There are used in the custom skin to display the content. Also, I usually set required="false" to make everything optional.
  4. Add partAdded() & partRemoved() – override the new Flex 4 skinning lifecycle methods to wire the incoming content to the outgoing SkinPart.

The custom component code is actually easier to follow then the description. Here is a custom container with only one additional content bucket, sidebarContent, and its matching SkinPart, sidebarGroup:

package containers {
    import spark.components.Group;
    import spark.components.Application;
 
    public class MainApp extends Application {
        [SkinPart(required="false")]
        public var sidebarGroup:Group;
 
        private var _sidebarContent:Array = [];
 
        public function MainApp() {
            super();
        }
 
        [ArrayElementType("mx.core.IVisualElement")]
        public function get sidebarContent():Array {
            return _sidebarContent;
        }
        public function set sidebarContent(value:Array):void {
            _sidebarContent = value;
            if (sidebarGroup) {
                sidebarGroup.mxmlContent = value;
            }
        }
 
        override protected function partAdded(partName:String, instance:Object):void {
            super.partAdded(partName, instance);
 
            if (instance == sidebarGroup) {
                sidebarGroup.mxmlContent = _sidebarContent;
            }
        }
 
        override protected function partRemoved(partName:String, instance:Object):void {
            super.partRemoved(partName, instance);
 
            if (instance == sidebarGroup) {
                sidebarGroup.mxmlContent = null;
            }
        }
    }
}

Following the four steps: we extend Application, have a sidebarContent bucket and its associated sidebarGroup SkinPart, and override partAdded() and partRemoved() to wire everything together.

Skinning a Multi Content Area Container

Skinning in Flex 4 is awesome, and like everyone says, it’s easily one of the best new features in the framework. While I find the skinning process fairly straightforward, I would never call it trivial, mostly due to the depth and flexibility of the skinning system.

We need a custom skin for our custom multi content area component. This is probably the 10% case for skinning, but it’s also the coolest. In my experience, an average Flex 4 app has many Button skins (like 10 or even 20), a few default component skins (skins for List, DropDownList, TextInput, etc.), and maybe only two or three skins for custom components.

The skin itself is nothing special. To display our custom component’s SkinParts, we simply include a Group with the matching id attribute. For example, our skin will include a <s:Group id="sidebarGroup" /> to display the sidebarGroup SkinPart. Just rinse, wash, repeat, to add all of our custom content areas in the container to the skin.

Here is a trivial skin:

<s:Skin ... >
    <fx:Metadata>
        [HostComponent("containers.MainApp")]
    </fx:Metadata> 
 
    <s:states>
        <s:State name="normal" />
        <s:State name="disabled" />
    </s:states>
 
    <s:VGroup left="40" right="40" top="40" bottom="40" gap="20">
        <s:Group id="headerGroup" width="100%" />
        <s:Group id="contentGroup" width="100%" />
        <s:Group id="sidebarGroup" width="100%" />
        <s:Group id="footerGroup" width="100%" />
    </s:VGroup>
</s:Skin>

In this trivial skin, we just shove all the content groups (including SkinnableContainer‘s default Group, contentGroup) into a VGroup. Also note, we correctly set HostComponent to our custom container. If you are thinking, "Hey, this skin looks similar to the Flex 3 and 3-in-4 example code, just minus the content" that’s exactly the point.

Lastly, we wire out skin to our custom component via CSS:

containers|MainApp {
    skinClass:ClassReference('skins.TrivialAppSkin');
}

Using skinClass to wire a skin to a component is so 2009. The sample app has its CSS inline, but in any real app I’ll always put this in an external file.

Conclusion

After this, there’s really not much more to say. You can certainly create a more complicated arrangement of the multiple content areas by making a more complicated skin. I’ve done exactly this in the final sample, which includes three different skins and a skin switcher (click 1, 2, or 3 to switch skins).

» view MultiConentArea sample (view source enabled)

Files

 6.1

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):

Flash is required. Get it here!

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".

Files

 5.19

The AIR Install Badge is a very handy little flash application for delivering AIR applications to your users via the web. The badge allows your users to download and install both your application and the Adobe AIR runtime. Additionally, the install badge will automatically prompt users to upgrade if a previously installed version is detected. At Gorilla Logic, we use the AIR Install Badge on the FlexMonkey download page (free registration required).

Alas, flash is opaque to analytics. We have no idea what our users are doing inside the AIR Install Badge application. Are they installing? Or upgrading? No problem, we just need to write some code…

The Code

Using flash’s ExternalInterface, we can manually push the data out of flash and into javascript. Once we have the data in javascript, we have total control. One option is to use google analytics to store our badge data. In the case of FlexMonkey, we send the badge data along with the user’s credentials to our CRM platform, SalesForce.com.

Step 1: First, open AIRInstallBadge.as and add this to the top:

import flash.external.ExternalInterface;

Step 2: Next, add the ExternalInterface call to the top of the handleActinClick() function in AIRInstallBadge.as:

protected function handleActionClick(evt:MouseEvent):void {
    if (action == 'install' || action == 'upgrade') {
        //send data to js
        ExternalInterface.call('badgeJS',action);
    }
    ...
}

Since I only care about the install or upgrade actions, I’ll only send those out to javascript. Re-compile the badge and deploy.

Step 3: Last, add the badgeJS() javascript callback to the page containing the badge and do whatever you want with the incoming badge data:

function badgeJS(action) {
    //do metrics here...
    alert('badge action=' + action);
}

Conclusion

With an hour of effort, and a very small amount of code, we’ve managed to get the useful metrics of installs and upgrades out of the AIR Install Badge and into our analytics engine of choice. A job well done.


 5.10

Photoshop does this annoying thing where purely vertical gradients have some horizontal variation. Yes, it’s usually only plus or minus one bit of color, but it offends! I’ve battled Photoshop for a while on this, but I just can’t seem to get exactly what I want out of it. So to make a perfect gradient, I decided to write some code. The requirements are simple: given a starting color and a set of deltas, output a perfect gradient.

Here are some quick examples:

1
gradient1
2
gradient2
3
gradient3
#000000
4, 1, 0.25
#eeeeff
-2.2, -1, -0.3
#ff0099
-1, 0, 1
 

If we zoom in on example #1, which starts with black (#000000) and has deltas of 4, 1, 0.25, we see the following:

zoomed gradient
 

The diagram shows the first ten rows of the gradient. The delta values are accumulated with each row, and only the whole part of the resulting color value is used (aka I take the floor of each color bit). So in this example, using the fractional delta of 0.25 results in exactly one additional blue bit every four rows. Ahhh, perfect!

The Code

No need to use some fancy new language, I wrote a simple PHP program to handle commandline input and output a perfect PNG gradient. The interesting part is the function that generates and saves the gradient:

function build_image($filename, $w, $h, $color, $delta) {
  $img = imagecreatetruecolor($w, $h);
 
  $c = imagecolorallocate($img, $color[0], $color[1], $color[2]);
  $d = $delta;
 
  for ($y = 0; $y < $h; $y++) {
    imagefilledrectangle($img, 0, $y, $w - 1, $y + 1, $c);
 
    $c = imagecolorallocate($img,
      clamp(floor($color[0] + $d[0])),
      clamp(floor($color[1] + $d[1])),
      clamp(floor($color[2] + $d[2]))
    );
 
    $d = array($d[0] + $delta[0], $d[1] + $delta[1], $d[2] + $delta[2]);
  }
 
  imagepng($img, $filename);
  imagedestroy($img);
}

The code is straight forward. First, create the image via imagecreatetruecolor(). Then, starting with the starting color, draw a one pixel tall rectangle for each row of the image. The next row’s color is computed in each iteration by adding the accumulated delta to the starting color. Finally, output the image as a PNG via imagepng() and free the memory. The complete php source can be downloaded here.

Button Time

Once we have our perfect gradient engine in place, it’s time to make some perfect buttons. To achieve the standard glass button look-and-feel, I typically fuse two gradients together: light on the top, dark on the bottom.

Here are the two halves of a pretty red button, along with their starting color and deltas:

TOP
BOTTOM
top
bottom
#ff8080
-3,-3,-3
#d23c3c
-3,-3,-3
 

And the two commandline invocations of gradient.php to create the gradients:

php gradient.php 100x16 ff8080 -3,-3,-3 top.png
php gradient.php 100x16 d23c3c -3,-3,-3 bottom.png

If I want my buttons to be sexy, rounded corners are a must. My favorite photoshop trick to create multiple rounded buttons is to use a rounded alpha-transparent button with each gradient as a clipping mask. Using a clipping mask is a simple way to guarantee button geometry remains fixed while colors are changed.

Here is the layers pane showing the two gradients fused together and used as a clipping mask for the rounded alpha-transparent button:

clip mask
 

The result is a horizontally stretchable gradient button, that doesn’t look half bad. See for yourself:

button
 

Custom UIButton

The final button asset can be used as desired, but here is a simple Objective-C example since I’ve been in iPhone world lately:

UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setFrame:CGRectMake(20, 20, 140, 32)];
[btn setBackgroundImage:[[UIImage imageNamed:@"btn-red.png"]
      stretchableImageWithLeftCapWidth:10.0
      topCapHeight:0.0] forState:UIControlStateNormal];
[btn setTitle:@"BUTTON" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[btn.titleLabel setFont:[UIFont boldSystemFontOfSize:14]];

Create a new UIButton of type UIButtonTypeCustom and then set the button skin as the backgroundImage. The horizontal stretchability is due to the stretchableImageWithLeftCapWidth and topCapHeight.

Here is a screenshot from the iPhone simulator showing the button in action:

screenshot
 
Files
  • gradient.php – the perfect gradient engine
  • gradient-button.psd – the photoshop source for the red button image, including the rounded alpha-transparent button and fused red gradients
  • btn-red.png – the red button image

© 2010 saturnboy.com