Understanding Custom Flex Component Development

I’ve been refining my custom Flex component development for a while but I still smile when I come across someone’s definition of the basics that makes what I’ve already learned even more understandable. I came across Mike Nimer’s post about slides from the Flex Camp in Chicago and after reading through them I figured I’d post a summary of the contents on my own blog just to have a reference post for my own use.

 

Understanding the Component Lifecycle

The component lifcycle is a series of methods called by the Flex Framework that every component experiences. These methods include:

  • The component’s constructor
  • The commitProperties() method
  • The createChildren() method
  • The measure() method
  • The updateDisplayList() method

Breaking it all down:

Constructor
Inside the component’s constructor you will setup the initial properties and do tasks which need to be done regardless if the component is ever shown on the screen. You will NOT do things like creating child components or positioning items because at this stage you do not have enough information to do this. The constructor is called whenever the component is created but the other lifecycle methods do not occur until a component is added to a container:

 

var blah:Button = new Button(); // invokes the constructor
container.addChild(blah);         // adds the component to a container,
                                              // triggering the other lifecycle methods

 

createChildren()
This method creates any visual children of the component. It only creates them but does NOT size or position them because at this point your component does not know how much screen space it has. You cannot tell your children how big they can be when you do not know how much space you have to work with. The same logic applies to positioning them.

measure()
To determine how much screen space a component requires, it calls its measure() method. (NOTE: The measure() method is not called on components that you explicitly set the width or height on; this makes sense because if you explicitly set the size of the component, you obviously do not care what that component thinks its size should be; you are making the decision for that component so its measure() method results are irrelevant; thanks to Maulik for pointing this out). The measure method is responsible for setting four properties of the component:

  • measuredMinWidth
  • measuredMinHeight
  • measuredWidth
  • measuredHeight

These values form the basis of a ‘suggestion’ provided by the component to its parent about how big it would like to be. This is where the updateDisplayList() method comes in.
The measure method also involves the layout manager. The layout manager is responsible for, among other things, ensuring that the process of sizing your component is started. The layout manager always starts on the outer most component. To know how much space a component needs, you need to know how much space its children need.

updateDisplayList()
When updateDisplayList() is called on a given component, it is passed an unscaledWidth parameter and an unscaledHeight parameter. Basically, it is told ‘Regardless of what you requested, here is what you get’. While Flex containers never choose to size one of their children smaller than the minimum size, you can do whatever you want. You are free to ignore a component’s suggestion and make it any size you would like and when a Flex container realizes it cannot fit all of the components into the allotted space, it will add scrollbars. updateDisplayList() is also responsible for positioning its children. The method in which the Flex containers and components position their children is implicit in their type (ie. VBox-vertical, HBox-horizontal, DateField-Horizontal). For a custom component, the method in which it arranges its children can be based on an equation that is most suitable.

commitProperties()
The commitProperties() method is responsible for coordinating property modifications. Why would you want to coordinate properties? Because there are times when you need to wait to act until more than one property upon which you are dependent is set/ready. There are also times when you do not want to do something complex every single time a change occurs. Finally, commitProperties() method allows you to defer some of these things until the screen needs to be redrawn because commitProperties() (just like updateDisplayList()) is ‘scheduled’ and called by the Flex framework when needed.

How do you tell the framework that they are needed? By calling the invalidate methods:

  • invalidateDisplayList()
  • invalidateProperties()
  • invalidateSize()

These methods tell the framework to ensure the method is called at the next appropriate time. Notice that there is no invalidateChildren(). This is because you only create children once and you do not recreate them for every property change.

So where does this leave us? Well, basically you have to remember these point:

  • Constructor() – set things up but do not create children or position things
  • createChildren() – create the children but do not position or size them
  • measure() – determines the required screen space; here you set measuredMinWidth, measuredMinHeight, measuredWidth, measuredHeight (these suggest how big the component should be)
  • invalidateSize() – tells Flex to re-measure things
  • updateDisplayList() – receives unscaledWidth and unscaledHeight, you can use or ignore this, and also position children
  • invalidateDisplayList() – tells Flex to call updateDisplayList()
  • commitProperties() – allows you to coordinate property modifications
  • invalidateProperties() – tells Flex to call invalidateProperties()

To show an example of how this all fits together, I’ll take a look at the Carousel example that was mentioned in the slide show.

The component’s lifecycle is as follows:

1. The user changes the selectedIndex/selectedChild property and we in turn set some internal values and ask for a commitProperties call.

 

[Bindable("change")]
public function get selectedIndex():int
{
    return _selectedIndex;
}
 
public function set selectedIndex( value:int ):void
{
    if ( _selectedIndex == value )
    {
        return;
    }
    _selectedIndex = value;
    selectedIndexChanged = true;
    dispatchEvent( new Event( 'change' ) );
    this.invalidateProperties();
}

 

2. When commitProperties() is called we determine the difference between the newly selected item is and where it needs to be and we animate through the values from the currentPosition to the selectedIndex:

 

/** commitProperties starts the majority of the heavy lifting here. It is called by use when the selectedIndex or selectedChild
 *  changes. We create a new animation effect that moves us from the current selected index to the newly selectedIndex.
 *  Sometimes it is easier to move around the circle to the right, and sometimes to the left, to acheive this result. The
 *  commitProperties makes this determination and sets up the proper values and starts the effect playing.
 *  selectedChild changes
 **/
override protected function commitProperties():void
{
    super.commitProperties();
 
    if ( selectedIndexChanged )
    {
        selectedIndexChanged = false;
 
        if ( !animateProperty )
        {
            animateProperty = new AnimateProperty( this );
        }
 
        if ( !animateProperty.isPlaying )
        {
            //Current position can get bigger and bigger, let's ensure we stay real and realize that it is just a factor
            //of the number of children
            currentPosition %= numChildren;
 
            if ( currentPosition < 0 )
            {
                currentPosition = numChildren + currentPosition;
            }
        }
 
        //Determine if it is easier to move right or left around the circle to get to our new position
        var newPosition:Number;
        var moveVector:Number = selectedIndex-currentPosition;
        var moveScalar:Number = Math.abs( moveVector );
 
        if ( moveScalar > ( numChildren/2 ) )
        {
            if ( moveVector < 0 )
            {
                newPosition = numChildren + selectedIndex;
            }
            else
            {
                newPosition = ( selectedIndex - numChildren );
            }
        }
        else
        {
            newPosition = selectedIndex;
        }
 
        animateProperty.property = "currentPosition";
        animateProperty.fromValue = currentPosition;
        animateProperty.toValue = newPosition;
        /*The duration of this effect is based on how far we must move. This way a one position move is not painfully slow*/
        animateProperty.duration = 700 * Math.abs( currentPosition-newPosition );
        animateProperty.easingFunction = Quadratic.easeInOut;
        animateProperty.suspendBackgroundProcessing = true;
        animateProperty.play();
    }
}

 

3. A the setting of the currentPosition property triggers an invalidateDisplayList() call:

 

/** When animating the change of position, current position contains the location (in radians) of the current position. This will likely be
 *  a numeric number between the integer selectedIndex values. So, when animating between selectedIndex 1 and 2, the currentPosition will
 *  move in tenths of a radian between those two values over a fixed amount of time. **/
[Bindable("currentPositionChanged")]
public function get currentPosition():Number
{
    return _currentPosition;
}
 
public function set currentPosition(value:Number):void
{
    _currentPosition = value;
 
    dispatchEvent( new Event( 'currentPositionChanged' ) );
 
    invalidateDisplayList();
 
    if ( currentPosition == selectedIndex )
    {
        dispatchEvent( new Event( 'animationComplete' ) );
    }
}

 4. Finally, the updateDisplayList() method uses the currentPosition property to determine where each image should be moved and how it should be scaled:

 

/** updateDisplayList does the animation time heavy lifting. It determines the size of the circle that can be drawn.
 *  The padding between each child around the circle and then calls method to size, determine the position, scale and
 *  blur of each of the children. Those values are then applied. Note, updateDisplay list uses our orderedChildren
 *  array to ensure the original order of the children is preserved despite our manipulations of their order.
 **/
override protected function updateDisplayList( unscaledWidth:Number, unscaledHeight:Number ):void
{
    super.updateDisplayList( unscaledWidth, unscaledHeight );
 
    var centerOfCircle:Point = new Point( unscaledWidth/2, unscaledHeight/2 ); 
 
    //We resize the shadowContainer to the same size as our component.
    //This ensures that the x and y positions of the components moving around the carousel
    //track with the x and y of the shadows moving on this container
    shadowContainer.setActualSize( unscaledWidth, unscaledHeight );
 
    var maxChildWidth:Number = 0;
    var child:UIComponent;
    for ( var i:int=0; i<orderedchildren.length;>
    {
        child = orderedChildren[ i ] as UIComponent;
        child.setActualSize( child.getExplicitOrMeasuredWidth(), child.getExplicitOrMeasuredHeight() );
        maxChildWidth = Math.max( maxChildWidth, child.width );
    }
 
    radius = ( unscaledWidth /2 ) - ( maxChildWidth / 2 ) ;
 
    /** In a circle, we have a total of 2pi radians. So, in this case, we take the number of children present
     * and divide up those sizes that everything spaces evenly **/
    var childPaddingInRadians:Number;
    var childDistanceInRadians:Number;
    childPaddingInRadians = (2*Math.PI)/(numChildren);
 
    for ( var j:int=0; j<orderedchildren.length;>
    {
        child = orderedChildren[ j ] as UIComponent;
 
        childDistanceInRadians = (j*childPaddingInRadians) + radianOffset - ( currentPosition * childPaddingInRadians ) ;
        var newPoint:Point = getPointOnCircle( centerOfCircle, radius, childDistanceInRadians );
 
        //Set the appropriate scale of the children as the move backward in 3d space.
        var scale:Number =  getScale( centerOfCircle, radius, newPoint.y );
        child.scaleX = scale;
        child.scaleY = scale;
 
        //Reapply the blur to the child. The base amount is determined by a style
        var blur:Number = getBlur( centerOfCircle, radius, newPoint.y );
        ( blurEffectDictionary[ child ] as BlurFilter ).blurX = blur;
        ( blurEffectDictionary[ child ] as BlurFilter ).blurY = blur;
        ( blurEffectDictionary[ child ] as BlurFilter ).quality = 1;
        child.filters = [ blurEffectDictionary[ child ] ];
 
        childCenterMove( child, newPoint );
 
        //Position the hanging shadow below the child. The base distance is determined by a style
        //This only works as the shadowContainer is mapped to the same size as this component so the
        //x and y positions correspond 1 to 1
        var shadow:HangingShadow;
        shadow = ( shadowDictionary[ child ] as HangingShadow );
        shadow.setActualSize( child.width, getStyle("baseShadowHeight") );
        shadow.move( child.x, child.y + child.height + getShadowDistance( centerOfCircle, radius, newPoint.y ) );
        shadow.scaleY = scale;
    }
}

 

原文链接:http://www.billdwhite.com/wordpress/?p=21

 

另:http://help.adobe.com/en_US/flex/using/WS460ee381960520ad-2811830c121e9107ecb-7ff9.html

http://www.slideshare.net/ivanhoe/aaron-pedersen-james-polanco-flex-4-component-life-cycle-best-practices

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值