//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package mx.effects { import flash.events.EventDispatcher; import flash.events.IEventDispatcher; import flash.events.TimerEvent; import flash.utils.Timer; import flash.utils.getTimer; import mx.core.UIComponentGlobals; import mx.core.mx_internal; import mx.events.TweenEvent; use namespace mx_internal; /** * Tween is the underlying animation class for the effects in Flex 3. As of Flex 4, the Spark * effects use the spark.effects.animation.Animation class to provide similar functionality. */ [Alternative(replacement="spark.effects.animation.Animation", since="4.0")] /** * The Tween class defines a tween, a property animation performed * on a target object over a period of time. * That animation can be a change in position, such as performed by * the Move effect; a change in size, as performed by the Resize or * Zoom effects; a change in visibility, as performed by the Fade or * Dissolve effects; or other types of animations. * *
When defining tween effects, you typically create an instance
* of the Tween class within your override of the
* EffectInstance.play()
method.
* A Tween instance accepts the startValue
,
* endValue
, and duration
properties,
* and an optional easing function to define the animation.
The Tween object invokes the
* mx.effects.effectClasses.TweenEffectInstance.onTweenUpdate()
* callback function on a regular interval on the effect instance
* for the duration of the effect, passing to the
* onTweenUpdate()
method an interpolated value
* between the startValue
and endValue
.
* Typically, the callback function updates some property of the target object,
* causing that object to animate over the duration of the effect.
When the effect ends, the Tween objects invokes the
* mx.effects.effectClasses.TweenEffectInstance.onTweenEnd()
* callback function, if you defined one.
When the constructor is called, the animation automatically * starts playing.
* * @param listener Object that is notified at each interval * of the animation. You typically pass thethis
* keyword as the value.
* The listener
must define the
* onTweenUpdate()
method and optionally the
* onTweenEnd()
method.
* The former method is invoked for each interval of the animation,
* and the latter is invoked just after the animation finishes.
*
* @param startValue Initial value(s) of the animation.
* Either a number or an array of numbers.
* If a number is passed, the Tween interpolates
* between this number and the number passed
* in the endValue
parameter.
* If an array of numbers is passed,
* each number in the array is interpolated.
*
* @param endValue Final value(s) of the animation.
* The type of this argument must match the startValue
* parameter.
*
* @param duration Duration of the animation, expressed in milliseconds.
*
* @param minFps Minimum number of times that the
* onTweenUpdate()
method should be called every second.
* The tween code tries to call the onTweenUpdate()
* method as frequently as possible (up to 100 times per second).
* However, if the frequency falls below minFps
,
* the duration of the animation automatically increases.
* As a result, an animation that temporarily freezes
* (because it is not getting any CPU cycles) begins again
* where it left off, instead of suddenly jumping ahead.
*
* @param updateFunction Specifies an alternative update callback
* function to be used instead of listener.OnTweenUpdate()
*
* @param endFunction Specifies an alternative end callback function
* to be used instead of listener.OnTweenEnd()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function Tween(listener:Object,
startValue:Object, endValue:Object,
duration:Number = -1, minFps:Number = -1,
updateFunction:Function = null,
endFunction:Function = null)
{
super();
if (!listener)
return;
if (startValue is Array)
arrayMode = true;
this.listener = listener;
this.startValue = startValue;
this.endValue = endValue;
if (!isNaN(duration) && duration != -1)
this.duration = duration;
// If user has specified a minimum number of frames per second,
// remember the maximum allowable milliseconds between frames.
if (!isNaN(minFps) && minFps != -1)
maxDelay = 1000 / minFps;
this.updateFunction = updateFunction;
this.endFunction = endFunction;
if (duration == 0)
{
id = -1; // use -1 to indicate that this tween was never added
endTween();
}
else
Tween.addTween(this);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
mx_internal var needToLayout:Boolean = false;
/**
* @private
*/
private var id:int;
/**
* @private
*/
private var maxDelay:Number = 87.5; // Min frames per second is 12
/**
* @private
*/
private var arrayMode:Boolean;
/**
* @private
*/
private var _doSeek:Boolean = false;
/**
* @private
*/
private var _isPlaying:Boolean = true;
/**
* @private
*/
private var _doReverse:Boolean = false;
/**
* @private
*/
mx_internal var startTime:Number;
/**
* @private
*/
private var previousUpdateTime:Number;
/**
* @private
*/
private var userEquation:Function = defaultEasingFunction;
/**
* @private
*/
private var updateFunction:Function;
/**
* @private
*/
private var endFunction:Function;
/**
* @private
* Final value(s) of the animation.
* This can be a Number of an Array of Numbers.
*/
private var endValue:Object;
/**
* @private
* Initial value(s) of the animation.
* This can be a Number of an Array of Numbers.
*/
private var startValue:Object;
/**
* @private
*/
private var started:Boolean = false;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// duration
//----------------------------------
/**
* Duration of the animation, in milliseconds.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var duration:Number = 3000;
//----------------------------------
// listener
//----------------------------------
/**
* Object that is notified at each interval of the animation.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var listener:Object;
//----------------------------------
// playheadTime
//----------------------------------
/**
* @private
* Storage for the playheadTime property.
*/
private var _playheadTime:Number = 0;
/**
* @private
* The current millisecond position in the tween.
* This value is between 0 and duration.
* Use the seek() method to change the position of the tween.
*/
mx_internal function get playheadTime():Number
{
return _playheadTime;
}
//----------------------------------
// playReversed
//----------------------------------
/**
* @private
* Storage for the playReversed property.
*/
private var _invertValues:Boolean = false;
/**
* @private
* Starts playing reversed from start of tween.
* Setting this property to true
* inverts the values returned by the tween.
* Using reverse inverts the values and only plays
* for as much time that has already elapsed.
*/
mx_internal function get playReversed():Boolean
{
return _invertValues;
}
/**
* @private
*/
mx_internal function set playReversed(value:Boolean):void
{
_invertValues = value;
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* By default, the Tween class invokes the
* mx.effects.effectClasses.TweenEffectInstance.onTweenUpdate()
* callback function on a regular interval on the effect instance
* for the duration of the effect, and the optional
* mx.effects.effectClasses.TweenEffectInstance.onTweenEnd()
* callback function at the end of the effect duration.
*
* This method lets you specify different methods * as the update and the end callback functions.
* * @param updateFunction Specifies the update callback function. * * @param endFunction Specifies the end callback function. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function setTweenHandlers(updateFunction:Function, endFunction:Function):void { this.updateFunction = updateFunction; this.endFunction = endFunction; } /** * Sets the easing function for the animation. * The easing function is used to interpolate between * thestartValue
value and the endValue
.
* A trivial easing function does linear interpolation,
* but more sophisticated easing functions create the illusion of
* acceleration and deceleration, which makes the animation seem
* more natural.
*
* If no easing function is specified, an easing function based
* on the Math.sin()
method is used.
The easing function follows the function signature * popularized by Robert Penner. * The function accepts four arguments. * The first argument is the "current time", * where the animation start time is 0. * The second argument is a the initial value * at the beginning of the animation (a Number). * The third argument is the ending value * minus the initial value. * The fourth argument is the duration of the animation. * The return value is the interpolated value for the current time * (usually a value between the initial value and the ending value).
* *Flex includes a set of easing functions * in the mx.effects.easing package.
* * @param easingFunction Function that implements the easing equation. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function set easingFunction(value:Function):void { userEquation = value; } /** * Interrupt the tween, jump immediately to the end of the tween, * and invoke theonTweenEnd()
callback function.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function endTween():void
{
var event:TweenEvent = new TweenEvent(TweenEvent.TWEEN_END);
var value:Object = getCurrentValue(duration);
event.value = value;
dispatchEvent(event);
if (endFunction != null)
endFunction(value);
else
listener.onTweenEnd(value);
// If tween has been added, id >= 0
// but if duration = 0, this might not be the case.
if (id >= 0)
Tween.removeTweenAt(id);
}
/**
* @private
* Returns true if the tween has ended.
*/
mx_internal function doInterval():Boolean
{
var tweenEnded:Boolean = false;
// If user specified a minimum frames per second, we can't guarantee
// that we'll be called often enough to satisfy that request.
// However, we can avoid skipping over part of the animation.
// If this callback arrives too late, adjust the animation startTime,
// so that the animation starts up 'maxDelay' milliseconds
// after its last update.
/*
if (intervalTime - previousUpdateTime > maxDelay)
{
startTime += intervalTime - previousUpdateTime - maxDelay;
}
*/
previousUpdateTime = intervalTime;
if (_isPlaying || _doSeek)
{
var currentTime:Number = intervalTime - startTime;
_playheadTime = currentTime;
var currentValue:Object =
getCurrentValue(currentTime);
if (currentTime >= duration && !_doSeek)
{
endTween();
tweenEnded = true;
}
else
{
if (!started)
{
var startEvent:TweenEvent = new TweenEvent(TweenEvent.TWEEN_START);
dispatchEvent(startEvent);
started = true;
}
var event:TweenEvent =
new TweenEvent(TweenEvent.TWEEN_UPDATE);
event.value = currentValue;
dispatchEvent(event);
if (updateFunction != null)
updateFunction(currentValue);
else
listener.onTweenUpdate(currentValue);
}
_doSeek = false;
}
return tweenEnded;
}
/**
* @private
*/
mx_internal function getCurrentValue(currentTime:Number):Object
{
if (duration == 0)
{
return endValue;
}
if (_invertValues)
currentTime = duration - currentTime;
if (arrayMode)
{
var returnArray:Array = [];
var n:int = startValue.length;
for (var i:int = 0; i < n; i++)
{
returnArray[i] = userEquation(currentTime, startValue[i],
endValue[i] - startValue[i],
duration);
}
return returnArray;
}
else
{
return userEquation(currentTime, startValue,
Number(endValue) - Number(startValue),
duration);
}
}
/**
* @private
*/
private function defaultEasingFunction(t:Number, b:Number,
c:Number, d:Number):Number
{
return c / 2 * (Math.sin(Math.PI * (t / d - 0.5)) + 1) + b;
}
/**
* Advances the tween effect to the specified position.
*
* @param playheadTime The position, in milliseconds, between 0
* and the value of the duration
property.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function seek(playheadTime:Number):void
{
// Set value between 0 and duration
//playheadTime = Math.min(Math.max(playheadTime, 0), duration);
var clockTime:Number = intervalTime;
// Reset the previous update time
previousUpdateTime = clockTime;
// Reset the start time
startTime = clockTime - playheadTime;
_doSeek = true;
doInterval();
}
/**
* Plays the effect in reverse,
* starting from the current position of the effect.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function reverse():void
{
if (_isPlaying)
{
_doReverse = false;
seek(duration - _playheadTime);
_invertValues = !_invertValues;
}
else
{
_doReverse = !_doReverse;
}
}
/**
* Pauses the effect until you call the resume()
method.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function pause():void
{
_isPlaying = false;
}
/**
* Stops the tween, ending it without dispatching an event or calling
* the Tween's endFunction or onTweenEnd()
.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function stop():void
{
if (id >= 0)
Tween.removeTweenAt(id);
}
/**
* Resumes the effect after it has been paused
* by a call to the pause()
method.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function resume():void
{
_isPlaying = true;
startTime = intervalTime - _playheadTime;
if (_doReverse)
{
reverse();
_doReverse = false;
}
}
}
}