Although you can use standard effects and composite effects to solve most of an application’s effects requirements, sometimes these off-the-shelf solutions won’t achieve the intended result. For those cases, the Flex framework allows you to create your own custom effects that you can use exactly as you would use other standard effects.
Creating custom effects requires a more thorough understanding of the effect framework structure. Because working with effects is so simple, it’s not necessary to look at the inner workings of the effect framework until you want to write a custom effect.
The effect framework consists of two basic types of classes:
effect factories and effect instances. When you create a new effect object using MXML or
ActionScript, you are working with an effect factory class. However, when
the effect is applied to a component, the actual object utilized is an
effect instance (one that is created automatically behind the scenes). The
effect objects that you create using MXML and/or ActionScript using
classes such as Move
or Resize
utilize a design pattern called the
Factory Method. The
Factory Method pattern means that the factory class is responsible for creating the
effect instances, which are what are applied to the components.
Next we’ll look at how to define factory and instance classes.
The effect instance class is the one used as the blueprint for the actual objects
that apply the effect to the components. You don’t directly create
instances of this class normally. That is handled by the factory class.
For example, when you use a move effect, the actual effect object that
is applied to a component is of type MoveInstance
. You don’t typically create a
MoveInstance
object directly. Rather,
that instance is automatically created by the Move
factory object. We’ll look at how to
create factories in the next section. First, let’s look at how to create
an effect instance class.
All effect instance classes must inherit from mx.effects.EffectInstance
, and at a minimum, all EffectInstance
subclasses must override the
play()
method, and the overridden play()
method must also call the super.play()
method. Additionally, all effect instance classes should have
constructors that accept one parameter typed as Object
. The parameter is the target for the
effect instance that is automatically passed to the constructor when it
is called by the factory. The constructor should call super()
and pass it the parameter. Example 13-11 is a simple example
that merely places a red dot in the upper-right corner of a
component.
Example 13-11. FlagInstance class as an example of a custom effect instance
package com.oreilly.programmingflex.effects { import mx.effects.EffectInstance; import flash.display.Shape; // The class must extend EffectInstance. public class FlagInstance extends EffectInstance { // Allow for configuration of the color. private var _color:Number; public function set color(value:Number):void { _color = value; } public function get color():Number { return _color; } // The constructor must accept a parameter and pass that // along to the super constructor. public function FlagInstance(newTarget:Object) { super(newTarget); } // All effect instances must override play(). override public function play():void { // Call the super.play() method. super.play(); // Create a shape with a red dot. var shape:Shape = new Shape(); shape.graphics.lineStyle(0, 0, 0); shape.graphics.beginFill(_color, 1); shape.graphics.drawCircle(0, 0, 5); shape.graphics.endFill(); // Move the shape to the upper-right corner of the component. shape.x = target.x + target.width; shape.y = target.y; // Add the shape to the display list. target.parent.rawChildren.addChild(shape); } } }
All effect factory classes must extend the mx.effects.Effect
class. When you subclass Effect
, you must override the getAffectedProperties()
and initInstance()
methods, and you must assign a
reference to the instanceClass
property.
The getAffectedProperties()
method should return an array of all the names of the properties
affected. If the effect doesn’t affect any properties, the method should
return an empty array.
The initInstance()
method
should accept one parameter of type IEffectInstance
. It should always call super.initInstance()
, and then it should set
any necessary properties of the instance. For example, if you want to
pass through any settings from the factory to the instance, you should
do so in the initInstance()
method.
The instanceClass
property is a property inherited from Effect
that determines what class is used by
the factory to create instances. You must set the instanceClass
property. Typically, you should
do this in the constructor.
Example 13-12 is a
simple factory class corresponding to the FlagInstance
class from the preceding
section.
Example 13-12. Flag class as an example of an effect
package com.oreilly.programmingflex.effects { import mx.effects.Effect; import mx.effects.IEffectInstance; // All factory classes must inherit from Effect. public class Flag extends Effect { // Allow for the configuration of the color. Use a default of red. private var _color:Number = 0xFF0000; public function set color(value:Number):void { _color = value; } public function get color():Number { return _color; } // The constructor must call the super constructor, and it should also // assign the instance class reference to instanceClass. public function Flag(newTarget:Object = null) { super(newTarget); instanceClass = FlagInstance; } // In this example there are no affected properties for the target. override public function getAffectedProperties():Array { return []; } override protected function initInstance(instance:IEffectInstance):void { super.initInstance(instance); // Since instance is typed as EffectInstance you must cast as FlagInstance // to set the color property. FlagInstance(instance).color = _color; } } }
Once you’ve created a custom effect, you can use it just as you would use a standard effect. The following example illustrates this by applying the flag effect to text input controls as the user moves focus:
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:pf="com.oreilly.programmingflex.effects.*"> <mx:VBox x="164" y="187"> <mx:TextInput focusOutEffect="{flagEffect}" /> <mx:TextInput focusOutEffect="{flagEffect}" /> <mx:TextInput focusOutEffect="{flagEffect}" /> <mx:TextInput focusOutEffect="{flagEffect}" /> </mx:VBox> <pf:Flag id="flagEffect" color="0xFFFFFF" /> </mx:Application>
Thus far, you’ve seen how to create a custom effect that essentially
has two states: off and on. For example, the flag effect that you saw in
the previous sections is either not applied or applied, but there’s no
transition between these two states. Yet sometimes an effect should take
place over a period of time. For example, the standard Flex move effect
takes place over time by changing the x
and y
properties of the target incrementally, until the target has reached the
destination over the allotted duration. When you want to create effects
that cause changes over time, you should create a tween effect. Tween
effect classes extend TweenEffect
and TweenEffectInstance
rather than Effect
and
EffectInstance
. We’ll talk more about
that in just a moment. First, we’ll look at how to use the mx.effects.Tween
class to create animated
changes.
The Tween
class
constructor requires that you pass it four parameters: a callback
object, a starting value, an ending value, and duration in milliseconds.
For example, the following creates a Tween
object that automatically sends
notifications at intervals for 5,000 milliseconds. Each notification
includes a value from 0 to 100. The progression of values is a linear
change from 0 to 100. The notifications are sent to the this
object.
new Tween(this, 0, 100, 5000);
Unlike most of the Flex framework, the Tween
class does not use the normal event
model. Instead, it uses a callback model in which the callback object
must define methods with specific names. Those methods are then called
in response to specific events. In the case of the Tween
class, the callback object can define
the onTweenUpdate()
and onTweenEnd()
methods. The onTweenUpdate()
method receives notifications
as the value changes over time. The onTweenEnd()
method receives notifications
when the tween has completed.
Once you construct a Tween
object, it automatically starts to run. It calls the methods on the
callback method at the appropriate intervals, sending the current value
of the range over which it is changing over time. For example, on the
first onTweenUpdate()
method call,
the Tween
object passes it a value of
0
based on the preceding example, but
the second call to onTweenUpdate()
will be a value slightly larger than 0
, with each successive call passing the
method a larger value. Once the value reaches the maximum value in the
range (100
in our example), the
Tween
object calls onTweenEnd()
.
Most of the standard effects, such as move, rotate, and blur, are
tween effects. Because TweenEffect
and TweenEffectInstance
extend
Effect
and EffectInstance
, respectively, implementing a
concrete subclass of each of these types is very similar to implementing
classes that directly extend Effect
and EffectInstance
. In fact, all the
rules discussed in the preceding sections are applicable to tween
effects as well. Apart from extending TweenEffect
, all tween effect factory classes
have the same rules as nontween effects. Tween effect instance classes,
however, must follow several rules.
TweenEffectInstance
subclasses
should construct a Tween
object in
the play()
method, and the Tween
object should use the this
object as the callback object.
Furthermore, the subclass must override the onTweenUpdate()
method at a minimum. The
onTweenUpdate()
method should accept
one parameter typed as Object
. And
the onTweenUpdate()
method should be
responsible for updating the property or properties of the
target.
In Example 13-13,
WobbleInstance
is a TweenEffectInstance
subclass that uses Tween
objects to cause the target to appear to wobble a specified number of
times.
Example 13-13. WobbleInstance class as an example of a tween effect instance
package com.oreilly.programmingflex.effects { import mx.effects.effectClasses.TweenEffectInstance; import mx.effects.Tween; // The class must extend TweenEffectInstance. public class WobbleInstance extends TweenEffectInstance { // The _wobbleRepeat property determines how many times the target should // wobble. The _wobbleCount property counts how many wobbles have occurred. private var _wobbleRepeat:uint; private var _wobbleCount:uint; public function set wobbleRepeat(value:uint):void { _wobbleRepeat = value; } public function get wobbleRepeat():uint { return _wobbleRepeat; } // The constructor looks very much like a regular Effect subclass. public function WobbleInstance(newTarget:Object) { super(newTarget); } // The play() method calls super.play(). Then it creates a new Tween object. // In this case the Tween object changes from 0 to 2 over the course of 100 // milliseconds. override public function play():void { super.play(); _wobbleCount = 0; new Tween(this, 0, 2, 100); } // The onTweenUpdate() method is required. In this case onTweenUpdate() simply // sets the rotation property of the target. override public function onTweenUpdate(value:Object):void { super.onTweenUpdate(value); target.rotation = value; } // The onTweenEnd() method is not strictly required. However, in this case we // need to override it so that it can create new Tween objects for as long as // the target is supposed to wobble. override public function onTweenEnd(value:Object):void { super.onTweenEnd(value); if(_wobbleCount < _wobbleRepeat) { new Tween(this, value, value == 2 ? −2 : 2, 200); } else if(_wobbleCount == _wobbleRepeat) { new Tween(this, value, 0, 100); } _wobbleCount++; } } }
Example 13-14 shows
the Wobble
factory class. Notice that
it looks very similar to a regular effect factory class.
Example 13-14. Wobble class as an example tween effect factory
package com.oreilly.programmingflex.effects { import mx.effects.TweenEffect; import mx.effects.IEffectInstance; public class Wobble extends TweenEffect { private var _wobbleRepeat:uint = 2; public function set wobbleRepeat(value:uint):void { _wobbleRepeat = value; } public function get wobbleRepeat():uint { return _wobbleRepeat; } public function Wobble(newTarget:Object = null) { super(newTarget); instanceClass = WobbleInstance; } override public function getAffectedProperties():Array { return ["rotation"]; } override protected function initInstance(instance:IEffectInstance):void { super.initInstance(instance); WobbleInstance(instance).wobbleRepeat = _wobbleRepeat; } } }
Example 13-15 shows the effect applied to components.
Example 13-15. Applying a custom tween effect
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:pf="com.oreilly.programmingflex.effects.*"> <mx:VBox x="164" y="187"> <mx:TextInput focusOutEffect="{wobbleEffect}" /> <mx:TextInput focusOutEffect="{wobbleEffect}" /> <mx:TextInput focusOutEffect="{wobbleEffect}" /> <mx:TextInput focusOutEffect="{wobbleEffect}" /> </mx:VBox> <pf:Wobble id="wobbleEffect" wobbleRepeat="10" /> </mx:Application>