1//////////////////////////////////////////////////////////////////////////////// 2// 3// ADOBE SYSTEMS INCORPORATED 4// Copyright 2008 Adobe Systems Incorporated 5// All Rights Reserved. 6// 7// NOTICE: Adobe permits you to use, modify, and distribute this file 8// in accordance with the terms of the license agreement accompanying it. 9// 10//////////////////////////////////////////////////////////////////////////////// 11package spark.effects 12{ 13import mx.core.mx_internal; 14import mx.effects.Effect; 15import mx.effects.IEffectInstance; 16import mx.events.EffectEvent; 17 18import spark.effects.animation.Animation; 19import spark.effects.animation.MotionPath; 20import spark.effects.animation.RepeatBehavior; 21import spark.effects.easing.IEaser; 22import spark.effects.easing.Sine; 23import spark.effects.interpolation.IInterpolator; 24import spark.effects.supportClasses.AnimateInstance; 25 26use namespace mx_internal; 27 28//-------------------------------------- 29// Excluded APIs 30//-------------------------------------- 31 32// Exclude suspendBackgroundProcessing for now because the Flex 4 33// effects depend on the layout validation work that the flag suppresses 34[Exclude(name="suspendBackgroundProcessing", kind="property")] 35 36 37[DefaultProperty("motionPaths")] 38 39/** 40 * Dispatched every time the effect updates the target. 41 * 42 * @eventType mx.events.EffectEvent.EFFECT_UPDATE 43 * 44 * @langversion 3.0 45 * @playerversion Flash 10 46 * @playerversion AIR 1.5 47 * @productversion Flex 4 48 */ 49[Event(name="effectUpdate", type="mx.events.EffectEvent")] 50 51/** 52 * Dispatched when the effect begins a new repetition, for 53 * any effect that is repeated more than once. 54 * Flex also dispatches an <code>effectUpdate</code> event 55 * for the effect at the same time. 56 * 57 * @eventType mx.events.EffectEvent.EFFECT_REPEAT 58 * 59 * @langversion 3.0 60 * @playerversion Flash 10 61 * @playerversion AIR 1.5 62 * @productversion Flex 4 63 */ 64[Event(name="effectRepeat", type="mx.events.EffectEvent")] 65 66 67/** 68 * This Animate effect animates an arbitrary set of properties between values. 69 * Specify the properties and values to animate by setting the <code>motionPaths</code> property. 70 * 71 * @mxml 72 * 73 * <p>The <code><s:Animate></code> tag 74 * inherits all of the tag attributes of its superclass, 75 * and adds the following tag attributes:</p> 76 * 77 * <pre> 78 * <s:Animate 79 * <b>Properties</b> 80 * id="ID" 81 * disableLayout="false" 82 * easer="{spark.effects.easing.Sine(.5)}" 83 * interpolator="NumberInterpolator" 84 * motionPaths="no default" 85 * repeatBehavior="loop" 86 * /> 87 * </pre> 88 * 89 * @see spark.effects.supportClasses.AnimateInstance 90 * 91 * @includeExample examples/AnimateEffectExample.mxml 92 * 93 * @langversion 3.0 94 * @playerversion Flash 10 95 * @playerversion AIR 1.5 96 * @productversion Flex 4 97 */ 98public class Animate extends Effect 99{ 100 include "../core/Version.as"; 101 102 //-------------------------------------------------------------------------- 103 // 104 // Constructor 105 // 106 //-------------------------------------------------------------------------- 107 108 /** 109 * Constructor. 110 * 111 * @param target The Object to animate with this effect. 112 * 113 * @langversion 3.0 114 * @playerversion Flash 10 115 * @playerversion AIR 1.5 116 * @productversion Flex 4 117 */ 118 public function Animate(target:Object = null) 119 { 120 super(target); 121 122 instanceClass = AnimateInstance; 123 } 124 125 //-------------------------------------------------------------------------- 126 // 127 // Variables 128 // 129 //-------------------------------------------------------------------------- 130 131 // Cached version of the affected properties. By default, we simply return 132 // the list of properties specified in the motionPaths Vector. 133 // Subclasses should override getAffectedProperties() if they wish to 134 // specify a different set. 135 private var affectedProperties:Array = null; 136 137 // Cached version of the relevant styles. By default, we simply return 138 // the list of properties specified in the motionPaths Vector. 139 // Subclasses should override relevantStyles() if they wish to 140 // specify a different set. 141 private var _relevantStyles:Array = null; 142 143 // Cached default easer. We only need one of these, so we cache this static 144 // object to be reused by any Animate instances that do not specify 145 // a custom easer. 146 private static var defaultEaser:IEaser = new Sine(.5); 147 148 // Used to optimize event dispatching: only send out updated events if 149 // there is someone listening 150 private var numUpdateListeners:int = 0; 151 152 153 //-------------------------------------------------------------------------- 154 // 155 // Properties 156 // 157 //-------------------------------------------------------------------------- 158 159 //---------------------------------- 160 // motionPaths 161 //---------------------------------- 162 /** 163 * @private 164 * Storage for the motionPaths property. 165 */ 166 private var _motionPaths:Vector.<MotionPath>; 167 /** 168 * A Vector of MotionPath objects, each of which holds the 169 * name of a property being animated and the values that the property 170 * takes during the animation. 171 * This Vector takes precedence over 172 * any properties declared in subclasses of Animate. 173 * For example, if this Array is set directly on a Move effect, 174 * then any properties of the Move effect, such as <code>xFrom</code>, are ignored. 175 * 176 * @langversion 3.0 177 * @playerversion Flash 10 178 * @playerversion AIR 1.5 179 * @productversion Flex 4 180 */ 181 public function get motionPaths():Vector.<MotionPath> 182 { 183 return _motionPaths; 184 } 185 /** 186 * @private 187 */ 188 public function set motionPaths(value:Vector.<MotionPath>):void 189 { 190 _motionPaths = value; 191 } 192 193 //---------------------------------- 194 // easer 195 //---------------------------------- 196 /** 197 * @private 198 * Storage for the easer property. 199 */ 200 private var _easer:IEaser = defaultEaser; 201 /** 202 * The easing behavior for this effect. 203 * This IEaser object is used to convert the elapsed fraction of 204 * the animation into an eased fraction, which is then used to 205 * calculate the value at that eased elapsed fraction. 206 * 207 * <p>Note that it is possible to have easing at both the effect 208 * level and the Keyframe level (where Keyframes hold the values/times 209 * used in the MotionPath structures). 210 * These easing behaviors build on each other. 211 * The <code>easer</code> controls the easing of the overall effect. 212 * The Keyframe controls the easing in any particular interval of the animation. 213 * By default, the easing for Animate is non-linear (Sine(.5)). 214 * The easing for Keyframes is linear. If you desire an effect with easing 215 * at the keyframe level instead, you can set the easing of the 216 * effect to linear, and then set the easing specifically on the Keyframes.</p> 217 * 218 * @default spark.effects.easing.Sine(.5) 219 * 220 * @see spark.effects.easing.Sine 221 * 222 * @langversion 3.0 223 * @playerversion Flash 10 224 * @playerversion AIR 1.5 225 * @productversion Flex 4 226 */ 227 public function get easer():IEaser 228 { 229 return _easer; 230 } 231 /** 232 * @private 233 */ 234 public function set easer(value:IEaser):void 235 { 236 _easer = value; 237 } 238 239 //---------------------------------- 240 // interpolator 241 //---------------------------------- 242 /** 243 * @private 244 * Storage for the interpolator property. 245 */ 246 private var _interpolator:IInterpolator = null; 247 /** 248 * The interpolator used by this effect to calculate values between 249 * the start and end values of a property. 250 * By default, the NumberInterpolator class handles interpolation 251 * or, in the case of the start 252 * and end values being Arrays or Vectors, by the 253 * MultiValueInterpolator class. 254 * Interpolation of other types, or of Numbers that should be interpolated 255 * differently, such as <code>uint</code> values that hold color 256 * channel information, can be handled by supplying a different 257 * interpolator. 258 * 259 * @see spark.effects.interpolation.NumberInterpolator 260 * @see spark.effects.interpolation.MultiValueInterpolator 261 * 262 * @langversion 3.0 263 * @playerversion Flash 10 264 * @playerversion AIR 1.5 265 * @productversion Flex 4 266 */ 267 public function get interpolator():IInterpolator 268 { 269 return _interpolator; 270 } 271 /** 272 * @private 273 */ 274 public function set interpolator(value:IInterpolator):void 275 { 276 _interpolator = value; 277 } 278 279 //---------------------------------- 280 // repeatBehavior 281 //---------------------------------- 282 /** 283 * @private 284 * Storage for the repeatBehavior property. 285 */ 286 private var _repeatBehavior:String = RepeatBehavior.LOOP; 287 288 [Inspectable(category="General", enumeration="loop,reverse", defaultValue="loop" )] 289 290 /** 291 * The behavior of a repeating effect, which means an effect 292 * with <code>repeatCount</code> equal to either 0 or > 1. This 293 * value should be either <code>RepeatBehavior.LOOP</code>, which means the animation 294 * repeats in the same order each time, or <code>RepeatBehavior.REVERSE</code>, 295 * which means the animation reverses direction on each iteration. 296 * 297 * @default RepeatBehavior.LOOP 298 * 299 * @langversion 3.0 300 * @playerversion Flash 10 301 * @playerversion AIR 1.5 302 * @productversion Flex 4 303 */ 304 public function get repeatBehavior():String 305 { 306 return _repeatBehavior; 307 } 308 /** 309 * @private 310 */ 311 public function set repeatBehavior(value:String):void 312 { 313 _repeatBehavior = value; 314 } 315 316 //---------------------------------- 317 // disableLayout 318 //---------------------------------- 319 /** 320 * @private 321 * Storage for the disableLayout property. 322 */ 323 private var _disableLayout:Boolean = false; 324 /** 325 * If <code>true</code>, the effect disables layout on its 326 * targets' parent containers, setting the containers <code>autoLayout</code> 327 * property to false, and also disables any layout constraints on the 328 * target objects. These properties are restored when the effect 329 * finishes. 330 * 331 * @default false 332 * 333 * @langversion 3.0 334 * @playerversion Flash 10 335 * @playerversion AIR 1.5 336 * @productversion Flex 4 337 */ 338 public function get disableLayout():Boolean 339 { 340 return _disableLayout; 341 } 342 /** 343 * @private 344 */ 345 public function set disableLayout(value:Boolean):void 346 { 347 _disableLayout = value; 348 } 349 350 //-------------------------------------------------------------------------- 351 // 352 // Methods 353 // 354 //-------------------------------------------------------------------------- 355 356 /** 357 * @private 358 */ 359 override public function getAffectedProperties():Array /* of String */ 360 { 361 if (!affectedProperties) 362 { 363 if (motionPaths) 364 { 365 var horizontalMove:Boolean; 366 var verticalMove:Boolean; 367 affectedProperties = new Array(); 368 for (var i:int = 0; i < motionPaths.length; ++i) 369 { 370 var effectHolder:MotionPath = MotionPath(motionPaths[i]); 371 affectedProperties.push(effectHolder.property); 372 // Some properties side-affect others: add them to the list 373 switch (effectHolder.property) 374 { 375 // left/right/top/bottom side-effect x/y/width/height 376 case "left": 377 case "right": 378 case "horizontalCenter": 379 horizontalMove = true; 380 break; 381 case "top": 382 case "bottom": 383 case "verticalCenter": 384 verticalMove = true; 385 break; 386 // The next two let us capture the explicit values, so that we 387 // can restore them if necessary when the effect ends 388 case "width": 389 if (affectedProperties.indexOf("explicitWidth") < 0) 390 affectedProperties.push("explicitWidth"); 391 if (affectedProperties.indexOf("percentWidth") < 0) 392 affectedProperties.push("percentWidth"); 393 break; 394 case "height": 395 if (affectedProperties.indexOf("explicitHeight") < 0) 396 affectedProperties.push("explicitHeight"); 397 if (affectedProperties.indexOf("percentHeight") < 0) 398 affectedProperties.push("percentHeight"); 399 break; 400 } 401 } 402 if (horizontalMove) 403 { 404 if (affectedProperties.indexOf("x") < 0) 405 affectedProperties.push("x"); 406 if (affectedProperties.indexOf("width") < 0) 407 affectedProperties.push("width"); 408 if (affectedProperties.indexOf("explicitWidth") < 0) 409 affectedProperties.push("explicitWidth"); 410 } 411 if (verticalMove) 412 { 413 if (affectedProperties.indexOf("y") < 0) 414 affectedProperties.push("y"); 415 if (affectedProperties.indexOf("height") < 0) 416 affectedProperties.push("height"); 417 if (affectedProperties.indexOf("explicitHeight") < 0) 418 affectedProperties.push("explicitHeight"); 419 } 420 } 421 else 422 { 423 affectedProperties = []; 424 } 425 } 426 return affectedProperties; 427 } 428 429 /** 430 * @private 431 */ 432 override public function get relevantStyles():Array /* of String */ 433 { 434 if (!_relevantStyles) 435 { 436 if (motionPaths) 437 { 438 _relevantStyles = new Array(motionPaths.length); 439 for (var i:int = 0; i < motionPaths.length; ++i) 440 { 441 var effectHolder:MotionPath = MotionPath(motionPaths[i]); 442 _relevantStyles[i] = effectHolder.property; 443 } 444 } 445 else 446 { 447 _relevantStyles = []; 448 } 449 } 450 return _relevantStyles; 451 } 452 453 /** 454 * @private 455 */ 456 override protected function initInstance(instance:IEffectInstance):void 457 { 458 super.initInstance(instance); 459 460 var animateInstance:AnimateInstance = AnimateInstance(instance); 461 462 animateInstance.addEventListener(EffectEvent.EFFECT_REPEAT, animationEventHandler); 463 // Optimization: don't bother listening for update events if we don't have 464 // any listeners for that event 465 if (numUpdateListeners > 0) 466 animateInstance.addEventListener(EffectEvent.EFFECT_UPDATE, animationEventHandler); 467 468 if (easer) 469 animateInstance.easer = easer; 470 471 if (interpolator) 472 animateInstance.interpolator = interpolator; 473 474 if (isNaN(repeatCount)) 475 animateInstance.repeatCount = repeatCount; 476 477 animateInstance.repeatBehavior = repeatBehavior; 478 animateInstance.disableLayout = disableLayout; 479 480 if (motionPaths) 481 { 482 animateInstance.motionPaths = new Vector.<MotionPath>(); 483 for (var i:int = 0; i < motionPaths.length; ++i) 484 animateInstance.motionPaths[i] = motionPaths[i].clone(); 485 } 486 } 487 488 489 /** 490 * @private 491 */ 492 override protected function applyValueToTarget(target:Object, property:String, 493 value:*, props:Object):void 494 { 495 if (property in target) 496 { 497 // The "property in target" test only tells if the property exists 498 // in the target, but does not distinguish between read-only and 499 // read-write properties. Put a try/catch around the setter and 500 // ignore any errors. 501 try 502 { 503 target[property] = value; 504 } 505 catch(e:Error) 506 { 507 // Ignore errors 508 } 509 } 510 } 511 512 /** 513 * @private 514 * Track number of listeners to update event for optimization purposes 515 */ 516 override public function addEventListener(type:String, listener:Function, 517 useCapture:Boolean=false, priority:int=0, 518 useWeakReference:Boolean=false):void 519 { 520 super.addEventListener(type, listener, useCapture, priority, 521 useWeakReference); 522 if (type == EffectEvent.EFFECT_UPDATE) 523 ++numUpdateListeners; 524 } 525 526 /** 527 * @private 528 * Track number of listeners to update event for optimization purposes 529 */ 530 override public function removeEventListener(type:String, listener:Function, 531 useCapture:Boolean=false):void 532 { 533 super.removeEventListener(type, listener, useCapture); 534 if (type == EffectEvent.EFFECT_UPDATE) 535 --numUpdateListeners; 536 } 537 538 /** 539 * @private 540 * Called when the AnimateInstance object dispatches an EffectEvent. 541 * 542 * @param event An event object of type EffectEvent. 543 * 544 * @langversion 3.0 545 * @playerversion Flash 10 546 * @playerversion AIR 1.5 547 * @productversion Flex 4 548 */ 549 private function animationEventHandler(event:EffectEvent):void 550 { 551 dispatchEvent(event); 552 } 553 554 /** 555 * @private 556 * Tell the propertyChanges array to keep all values, unchanged or not. 557 * This enables us to check later, when the effect is finished, whether 558 * we need to restore explicit height/width values. 559 */ 560 override mx_internal function captureValues(propChanges:Array, 561 setStartValues:Boolean, 562 targetsToCapture:Array = null):Array 563 { 564 var propertyChanges:Array = 565 super.captureValues(propChanges, setStartValues, targetsToCapture); 566 567 // If we're capturing explicitWidth/Height values, don't strip unchanging 568 // values from propertyChanges; we want to know whether these values should 569 // be restored when the effect ends 570 var explicitValuesCaptured:Boolean = 571 getAffectedProperties().indexOf("explicitWidth") >= 0 || 572 getAffectedProperties().indexOf("explicitHeight") >= 0; 573 if (explicitValuesCaptured && setStartValues) 574 { 575 var n:int = propertyChanges.length; 576 for (var i:int = 0; i < n; i++) 577 { 578 if (targetsToCapture == null || targetsToCapture.length == 0 || 579 targetsToCapture.indexOf(propertyChanges[i].target) >= 0) 580 { 581 propertyChanges[i].stripUnchangedValues = false; 582 } 583 } 584 } 585 return propertyChanges; 586 } 587 588 /** 589 * @private 590 * After applying start values, check to see whether the values 591 * contain percentWidth/percentHeight, which should be applied 592 * after any width/height/explicitWidth/explicitHeight values. 593 */ 594 override mx_internal function applyStartValues(propChanges:Array, 595 targets:Array):void 596 { 597 super.applyStartValues(propChanges, targets); 598 // Special case for percentWidth/Height properties. If we are watching 599 // width/explicitWidth or height/explicitHeight values and also 600 // percentWidth/Height values, we have to make sure to apply the 601 // percent values last to avoid having them get clobbered by 602 // applying the width/height values. We could either sort the 603 // propChanges array or just make sure the re-apply the percent 604 // values here, after we're done with the rest. 605 if (propChanges) 606 { 607 var n:int = propChanges.length; 608 for (var i:int = 0; i < n; i++) 609 { 610 var target:Object = propChanges[i].target; 611 if (propChanges[i].start["percentWidth"] !== undefined && 612 "percentWidth" in target) 613 { 614 target.percentWidth = propChanges[i].start["percentWidth"]; 615 } 616 if (propChanges[i].start["percentHeight"] !== undefined && 617 "percentHeight" in target) 618 { 619 target.percentHeight = propChanges[i].start["percentHeight"]; 620 } 621 } 622 } 623 } 624 625 /** 626 * @private 627 * When we're done, check to see whether explicitWidth/Height values 628 * for the target are NaN in the end state. If so, we should restore 629 * them to that value. This ensures that the target will be sized by 630 * its layout manager instead of by the width/height set during 631 * the effect. 632 */ 633 override mx_internal function applyEndValues(propChanges:Array, 634 targets:Array):void 635 { 636 super.applyEndValues(propChanges, targets); 637 // Special case for animating width/height during a transition, because 638 // we may have clobbered the explicitWidth/Height values which otherwise 639 // would not have been set. We need to restore these values plus any 640 // associated layout constraint values (percentWidth/Height) 641 if (propChanges) 642 { 643 var n:int = propChanges.length; 644 for (var i:int = 0; i < n; i++) 645 { 646 var target:Object = propChanges[i].target; 647 if (propChanges[i].end["explicitWidth"] !== undefined) 648 { 649 if (isNaN(propChanges[i].end["explicitWidth"]) && 650 "explicitWidth" in target) 651 { 652 target.explicitWidth = NaN; 653 if (propChanges[i].end["percentWidth"] !== undefined && 654 "percentWidth" in target) 655 { 656 target.percentWidth = propChanges[i].end["percentWidth"]; 657 } 658 } 659 } 660 if (propChanges[i].end["explicitHeight"] !== undefined) 661 { 662 if (isNaN(propChanges[i].end["explicitHeight"]) && 663 "explicitHeight" in target) 664 { 665 target.explicitHeight = NaN; 666 if (propChanges[i].end["percentHeight"] !== undefined && 667 "percentHeight" in target) 668 { 669 target.percentHeight = propChanges[i].end["percentHeight"]; 670 } 671 } 672 } 673 } 674 } 675 } 676} 677} 678