Andy Hulstkamp

about creating online experiences

26. February 2009

Flex 4 custom Knob Button - Part 1

In this and this post I had a look at skinning components in Flex 4. Here I had a go at creating a simple custom component in Flex 4 (Gumbo) from scratch. The goal was to create a simple knob-button like those found on many softsynths.

The user clicks the button and drags the mouse to adjust the value. The advantage of a knob compared to a slider is that it needs much less screen estate and could be more intuitive in specific contexts to control a value.

Flex Custom Knob Button Component

Demo

Creating a custom visual component in Flex 4 is similar to the procedure in Flex 3 but states and skins are handled differently (in a much better and cleaner way). Basically we need to do (some of) the following stuff:

  • Decide which component we want to inherit from
  • Define SkinParts
  • Define SkinState
  • Keep track of the state and leverage this into an overriden getCurrentSkinState() function
  • override partAdded() and partRemoved() to (un)register handlers to the skin parts and do setup/housecleaning code for the skinparts
  • Implement the behaviour (EventHandlers or whatever)
  • Keep track of the internal data and manage properties
  • Override Flex Component Lifecycle (as in HALO) methods if needed
  • Push relevant data down to the skin (or pull from the hostComponent inside the skin)
  • Define the skin class and the required SkinParts

The Spark UI Components in Flex 4 follow a lightweight approach (which in my opinion is the much better approach since there’s less overhead and usually better performance). So for the knob button I just wanted the following features (similar to the Slider-Component):

  • minValue
  • maxValue
  • minRotation
  • maxRotation

No additional features, like showing the value in a text field should be defined. Extended features can later be implemented by using composition or extending from this component. See part 2 for this.

Here are some excerpts that define the Spark specific parts, otherwise the procedure is similar to creating a component in flex 3. You can get the full source code here.

//--------------------------------------------------------------------------
//
//  States & Skin Parts
//
//--------------------------------------------------------------------------

 /**
 *  Up State of the Button
 */
[SkinState("up")]

/**
 *  Over State of the Button
 */
[SkinState("over")]

/**
 *  Down State of the Button
 */
[SkinState("down")]

/**
 *  Disabled State of the Button
 */
[SkinState("disabled")]

protected var isOver:Boolean;
protected var isDown:Boolean;

 /**
 *  A skin part that defines the knob.
 *  TODO: Use a strict type once the Skin is defined
 */
[SkinPart(required="true")]
public var knob:*;

 /**
 *  Add Handlers to the knob part
 */
override protected function partAdded(partName:String, instance:Object):void
{
    super.partAdded(partName, instance);
    if (partName == "knob") {
          addInitialHandlers();
    }
}

/**
 * Remove Handlers from the knob part
 * @param partName
 * @param instance
 *
 */
override protected function partRemoved(partName:String, instance:Object):void {
     super.partRemoved(partName, instance);
     if (partName == "knob") {
          removeInitialHandlers();
          removeMoveHandlers();
    }
}

/**
 * This is called by the Framework. We need to keep track of the state and
 * return it here. Called by Flex after a call to invalidateSkinState()
 * @return The current state of the Component
 *
 */
override protected function getCurrentSkinState():String
{
     if (!enabled)
        return "disabled";

    if (isDown)
        return "down";

    if (isOver)
        return "over";

    return "up";
}

The rest is just implementing the controller code (handlers) and keep track of the data and managing properties.

The skin for the simple knob has to define the requested SkinPart for the knob. It’s something similar to this:

<!-- states -->
<states>
    <State name="up" />
    <State name="over" />
    <State name="down" />
    <State name="disabled" />
</states>

<!-- The required Knob Skin Part -->
<Ellipse id="knob" width="{this.width}" height="{this.height}" horizontalCenter="0" verticalCenter="0">
     <stroke>
        <SolidColorStroke  weight="2" weight.over="3" weight.down="4" color="{c2}" color.over="{c2}" color.down="{c1}">

        </SolidColorStroke>
    </stroke>
    <fill>
        <LinearGradient >
                <entries>
                        <GradientEntry color="{c1}" ratio="0"  />
                         <GradientEntry color="{c2}" ratio=".35"  />
                         <GradientEntry color="{c3}" ratio=".4"  />
                          <GradientEntry color="{c4}" ratio="1"  />
                </entries>
            </LinearGradient>
    </fill>
</Ellipse>

<!-- Additional stuff to spice up the knob. Not Required -->
<Ellipse blendMode="overlay" width="{this.width}" height="{this.height}" horizontalCenter="0" verticalCenter="0">
    <fill>
        <RadialGradient focalPointRatio="-.5" rotation="45">
            <entries>
                <GradientEntry color="0xffffff" ratio="0"   />
                 <GradientEntry color="0xd0d0d0" ratio=".65"  />
                 <GradientEntry color="0xb0b0b0" ratio=".85"  />
                 <GradientEntry color="0xa0a0a0" ratio="1"  />
            </entries>
        </RadialGradient>

    </fill>
</Ellipse>

<!-- A filter to make it a little more interesting. Not required -->
<filters>
   <DropShadowFilter blurX="4" blurY="4" alpha="0.32" distance="3" distance.down="0" angle="45" knockout="false" />
</filters>

further reading

Creating FXG-Library Elements at runtime in Flex 4 (Gumbo)

creating library elements at runtime.

Creating a custom Spark GraphicElement

SkinnableComponents can get a little heavy. This post looks at the lightweight GraphicElements that can be used to create simple Components.