//////////////////////////////////////////////////////////////////////////////// // // 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 spark.transitions { import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.display.Stage; import flash.events.Event; import flash.events.EventDispatcher; import flash.geom.Matrix; import flash.geom.Point; import flash.geom.Rectangle; import mx.core.FlexGlobals; import mx.core.IVisualElementContainer; import mx.core.UIComponent; import mx.core.mx_internal; import mx.effects.IEffect; import mx.effects.Parallel; import mx.events.EffectEvent; import mx.events.FlexEvent; import mx.geom.TransformOffsets; import mx.managers.SystemManager; import spark.components.ActionBar; import spark.components.Group; import spark.components.TabbedViewNavigator; import spark.components.View; import spark.components.ViewNavigator; import spark.components.supportClasses.ButtonBarBase; import spark.components.supportClasses.ViewNavigatorBase; import spark.effects.Animate; import spark.effects.Fade; import spark.effects.animation.MotionPath; import spark.effects.animation.SimpleMotionPath; import spark.effects.easing.IEaser; import spark.effects.easing.Sine; import spark.primitives.BitmapImage; import spark.utils.BitmapUtil; use namespace mx_internal; /** * Dispatched when the transition starts. * * @eventType mx.events.FlexEvent.TRANSITION_START * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Event(name="transitionStart", type="mx.events.FlexEvent")] /** * Dispatched when the transition completes. * * @eventType mx.events.FlexEvent.TRANSITION_START * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Event(name="transitionEnd", type="mx.events.FlexEvent")] /** * The ViewTransitionBase class is the base class for all view transitions. * It is not intended to be used as a transition on its own. * In addition to providing common convenience and helper methods used by * view transitions, this class provides a default action bar transition * sequence. * *

When a view transition is initialized, the owning view navigator * sets the startView and endView properties * to the views the transition animates. * The navigator property is * set to the view navigator.

* *

The lifecycle of a transition is as follows:

* * *

Note:Create and configure view transitions in ActionScript; * you cannot create them in MXML.

* * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public class ViewTransitionBase extends EventDispatcher { //-------------------------------------------------------------------------- // // Constants // //-------------------------------------------------------------------------- /** * Constant used in tandem with the actionBarTransitionMode property to * hint the default action bar transition behavior. */ mx_internal static const ACTION_BAR_MODE_FADE:String = "fade"; /** * Constant used in tandem with the actionBarTransitionMode property to * hint the default action bar transition behavior. */ mx_internal static const ACTION_BAR_MODE_FADE_AND_SLIDE:String = "fadeAndSlide"; /** * Constant used in tandem with the actionBarTransitionMode property to * hint the default action bar transition behavior. */ mx_internal static const ACTION_BAR_MODE_NONE:String = "none"; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function ViewTransitionBase() { super(); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private * Flag set when we've determined that we need to transition the navigator * in its entirety and cannot transition the control bars independently. */ protected var consolidatedTransition:Boolean = false; /** * @private * startView's action bar height. */ private var cachedActionBarHeight:Number; /** * @private * startView's action bar width. */ private var cachedActionBarWidth:Number; /** * @private * Transient display object used to hold temporary bitmap snapshots * during transition. */ private var transitionGroup:Group; /** * @private * Flag to assist with cleanup of any constructs used only for vertical * transitions (e.g. clipping masks). */ private var verticalTransition:Boolean; /** * @private * Flag used to determine whether the transition should wait a frame when * it receives the EFFECT_END event. This is true by default. It is only * set to false when the endTransitions() method is called. */ private static var renderLastFrame:Boolean = true; /** * @private * Private vector that stores all the active transitions. */ private static var activeTransitions:Vector. = new Vector.(); /** * @private * Ends all currently active view transitions. */ mx_internal static function endTransitions():void { // Prevent the transitions from waiting a frame when they receive the // EFFECT_END event. See effectComplete(). renderLastFrame = false; // End all active transitions. They will be removed from the vector // in transitionComplete(). for (var i:int = 0; i < activeTransitions.length; i++) activeTransitions[i].effect.end(); // Restore render flag renderLastFrame = true; } //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // duration //---------------------------------- private var _duration:Number = 250; /** * Duration of the transition, in milliseconds. * The default may vary depending on the transition * but is defined in ViewTransitionBase as 250 ms. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get duration():Number { return _duration; } /** * @private */ public function set duration(value:Number):void { _duration = value; } //---------------------------------- // easer //---------------------------------- private var _easer:IEaser = new Sine(.5); /** * The easing behavior for this transition. The IEaser object is * generally propagated to the IEffect instance managing the actual * transition animation. * * @default Sine(.5); * * @see spark.effects.easing * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get easer():IEaser { return _easer; } /** * @private */ public function set easer(value:IEaser):void { _easer = value; } //---------------------------------- // effect //---------------------------------- private var _effect:IEffect; /** * Provides access to the underlying IEffect instance which the * transition is using to perform the transition (if any). This property * is only valid after the FlexEvent.TRANSITION_START event even has been * dispatched. * * If a transition does not make use of IEffect to perform the transition * this can be null. * * @default null * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ mx_internal function get effect():IEffect { return _effect; } mx_internal function set effect(value:IEffect):void { _effect = value; } //---------------------------------- // endView //---------------------------------- private var _endView:View; /** * The view that the navigator is transitioning * to, as set by the owning ViewNavigator object. * This property can be null. * * @default null * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get endView():View { return _endView; } /** * @private */ public function set endView(value:View):void { _endView = value; } //---------------------------------- // navigator //---------------------------------- private var _navigator:ViewNavigator; /** * Reference to the owning ViewNavigator instance set by the owning * ViewNavigator. * * @default null * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get navigator():ViewNavigator { return _navigator; } /** * @private */ public function set navigator(value:ViewNavigator):void { _navigator = value; } //---------------------------------- // startView //---------------------------------- private var _startView:View; /** * The currently active view of the view navigator, * as set by the owning view navigator. * This property can be null. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get startView():View { return _startView; } /** * @private */ public function set startView(value:View):void { _startView = value; } //---------------------------------- // suspendBackgroundProcessing //---------------------------------- private var _suspendBackgroundProcessing:Boolean = true; /** * When set to true, the UIComponent.suspendBackgroundProcessing() * method is invoked prior to the transition playing. * This disables Flex's layout manager and improving performance. * Upon completion of the transition, * the layout manager function is restored by a call to the * UIComponent.resumeBackgroundProcessing() method. * * @default false * * @see mx.core.UIComponent#suspendBackgroundProcessing() * @see mx.core.UIComponent#resumeBackgroundProcessing() * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get suspendBackgroundProcessing():Boolean { return _suspendBackgroundProcessing; } /** * @private */ public function set suspendBackgroundProcessing(value:Boolean):void { _suspendBackgroundProcessing = value; } //---------------------------------- // transitionControlsWithContent //---------------------------------- private var _transitionControlsWithContent:Boolean; /** * When set to true, the primary view transition * is used to transition the view navigator in its entirety, * including the action bar. * Specific transitions for the action bar are not performed. * Because the tab bar is associated with the entire application, * and not a view, view transitions do not affect it. * *

Note that even when set to false, there are cases * where its not feasible to transition the action bar. * For example, when the action bar does not exist in one of * the two views, or if the action bar changes size.

* * @default false * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get transitionControlsWithContent():Boolean { return _transitionControlsWithContent; } /** * @private */ public function set transitionControlsWithContent(value:Boolean):void { _transitionControlsWithContent = value; } //-------------------------------------------------------------------------- // // Private Properties // //-------------------------------------------------------------------------- //---------------------------------- // actionBarTransitionMode //---------------------------------- /** * @private * Convenience property used by ViewTransitionBase overrides to hint the behavior of * the default action bar transition as appropriate for the type and nature * of the specific view transition. Can be one either ViewTransitionBase.ACTION_BAR_MODE_FADE, * ViewTransitionBase.ACTION_BAR_MODE_FADE_AND_SLIDE, or null. If set to fade * and slide, the actionBarTransitionDirection property is considered by the * default createActionBarEffect() implementation. * * @default ACTION_BAR_MODE_FADE_AND_SLIDE */ mx_internal var actionBarTransitionMode:String = ACTION_BAR_MODE_FADE_AND_SLIDE; //---------------------------------- // actionBarTransitionDirection //---------------------------------- /** * @private * Convenience property used by ViewTransitionBase overrides to hint the direction * of the default action bar transition when the actionBarTransitionMode is set to * "fadeAndSlide". Can be or null or set to one of the ViewTransitionDirection * constants. This property is considered by the default createActionBarEffect() * implementation. * * @default ViewTransitionDirection.LEFT */ mx_internal var actionBarTransitionDirection:String = ViewTransitionDirection.LEFT; //---------------------------------- // cachedNavigatorSnapshot //---------------------------------- /** * @private * Cached image of the cumulative view of the owning navigator * captured by the default captureStartValues() implementation. * This snapshot is generally leveraged by transitions that need to * performing a full screen transition. */ mx_internal var cachedNavigator:BitmapImage; /** * @private * Stores the location of the cached navigator in the global coordinate space * so that the transition can properly position it when added to the display list. */ mx_internal var cachedNavigatorGlobalPosition:Point = new Point(); //---------------------------------- // cachedActionGroupSnapshot //---------------------------------- /** * @private * Cached image of the action bar's action group. This image is * only captured by default if action group content exists in the * previous view. */ mx_internal var cachedActionGroup:BitmapImage; /** * @private * Stores the location of the cached navigator in the global coordinate space * so that the transition can properly position it when added to the display list. */ mx_internal var cachedActionGroupGlobalPosition:Point = new Point(); //---------------------------------- // cachedTitleGroupSnapshot //---------------------------------- /** * @private * Cached image of the action bar's title group. This image is * only captured by default if title group content exists in the * previous view. */ mx_internal var cachedTitleGroup:BitmapImage; /** * @private * Stores the location of the cached navigator in the global coordinate space * so that the transition can properly position it when added to the display list. */ mx_internal var cachedTitleGroupGlobalPosition:Point = new Point(); //---------------------------------- // cachedNavigationGroupSnapshot //---------------------------------- /** * @private * Cached image of the action bar's navigation group. This image is * only captured by default if navigation group content exists in the * previous view. */ mx_internal var cachedNavigationGroup:BitmapImage; /** * @private * Stores the location of the cached navigator in the global coordinate space * so that the transition can properly position it when added to the display list. */ mx_internal var cachedNavigationGroupGlobalPosition:Point = new Point(); //---------------------------------- // targetNavigator //---------------------------------- /** * @private * Convenience property which caches our primary containing navigator, * this is usually our owning ViewNavigator but may be an outer TabNavigator */ protected var targetNavigator:ViewNavigatorBase; //---------------------------------- // parentNavigator //---------------------------------- /** * @private * Convenience property which caches our primary containing navigator, * this is usually our owning ViewNavigator but may be an outer TabNavigator */ protected var parentNavigator:ViewNavigatorBase; //---------------------------------- // actionBar //---------------------------------- /** * @private * Convenience property which caches our associated action bar. */ protected var actionBar:ActionBar; /** * @private * Convenience property which caches our associated tab bar. */ protected var tabBar:ButtonBarBase; //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- mx_internal function preInit():void { // Override } /** * Called by the ViewNavigator during the preparation phase of a transition. * It is invoked when the new view has been fully realized and validated and the * action bar and tab bar content reflect the state of the new view. * The transition can use this method capture any values it requires from the * pending view. * Any bitmaps reflecting the state of the new view, tab bar, * or action bar should be captured if required for animation. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function captureStartValues():void { // Remember some common references. parentNavigator = navigator.parentNavigator; if (parentNavigator is TabbedViewNavigator) { targetNavigator = parentNavigator; tabBar = TabbedViewNavigator(parentNavigator).tabBar; } else { targetNavigator = navigator; } if (navigator) actionBar = navigator.actionBar; // Determine first if we're able to transition our control bars independently // of our view content. If we are, then capture the necessary action bar // bitmap snapshots for use later by our default action bar transition. if (!consolidatedTransition) consolidatedTransition = !canTransitionControlBarContent(); // Snapshot component parts of action bar in preparation for our // default action bar transition, (if appropriate). if (!consolidatedTransition) { if (componentIsVisible(actionBar)) { // Save bounds of action bar. cachedActionBarWidth = actionBar.width; cachedActionBarHeight = actionBar.height; // Snapshot title content of our startView. if (actionBar.titleGroup && actionBar.titleGroup.visible) cachedTitleGroup = getSnapshot(actionBar.titleGroup, 4, cachedTitleGroupGlobalPosition); else if (actionBar.titleDisplay && (actionBar.titleDisplay is UIComponent) && UIComponent(actionBar.titleDisplay).visible) cachedTitleGroup = getSnapshot(UIComponent(actionBar.titleDisplay), 4, cachedTitleGroupGlobalPosition); // Snapshot actionContent if it's changing between our start and end views. if (startView.actionContent != endView.actionContent) cachedActionGroup = getSnapshot(actionBar.actionGroup, 4, cachedActionGroupGlobalPosition); // Snapshot navigationContent if it's changing between our start and end views. if (startView.navigationContent != endView.navigationContent) cachedNavigationGroup = getSnapshot(actionBar.navigationGroup, 4, cachedNavigationGroupGlobalPosition); } } } /** * Called by the ViewNavigator during the preparation phase of a transition. * It is invoked when the new view has been fully realized and validated and the * action bar and tab bar content reflect the state of the new view. * It is at this point that the transition can capture any values it requires from the * pending view. * In addition any bitmaps reflecting the state of the new view, tab bar, * or action bar should be captured, if required for animation. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function captureEndValues():void { // One final check to determine if we will be required to perform a full // (consolidated) transition. if (!consolidatedTransition) { consolidatedTransition = ((actionBar.height != cachedActionBarHeight) || (actionBar.width != cachedActionBarWidth)); } } /** * Called by the ViewNavigator when the transition * should begin animating. * At this time, the transition should dispatch a * start event. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function play():void { if (effect) { activeTransitions.push(this); effect.addEventListener(EffectEvent.EFFECT_END, effectComplete); // Dispatch TRANSITION_START. if (hasEventListener(FlexEvent.TRANSITION_START)) dispatchEvent(new FlexEvent(FlexEvent.TRANSITION_START)); if (navigator && navigator.stage && navigator.stage.hasEventListener(FlexEvent.TRANSITION_START)) navigator.stage.dispatchEvent(new FlexEvent(FlexEvent.TRANSITION_START)); effect.play(); } else transitionComplete(); } /** * Called by the ViewNavigator during the preparation phase * of a transition. * This method gives the transition the chance to create and * configure the underlying IEffect instance, or to add any transient * elements to the display list. * Example transient elements include bitmap placeholders, temporary * containers required during the transition, and other elements. * If required, a final validation pass occurs prior to the invocation * of the play() method. * *

If it is determined that a standard transition can be initiated, * meaning one that transitions the control bars separately from the views, * the default implementation of this method constructs * a single Parallel effect which wraps the individual effect sequences * for the view transition, the action bar transition, and the tab bar transition. * This method uses the methods, createActionBarEffect(), * createTabBarEffect(), and createViewEffect().

* *

If transitionControlsWithContent is set to true, * or if it is determined that the control bars cannot be transitioned independently, * a single effect is created to transition the navigator in its entirety. * In this case, only createConsolidatedEffect() is invoked.

* * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function prepareForPlay():void { if (!consolidatedTransition) { effect = new Parallel(); // Prepare action bar effect if (actionBar) { var actionBarEffect:IEffect = createActionBarEffect(); if (actionBarEffect) Parallel(effect).addChild(actionBarEffect); } // Prepare tab bar effect if (targetNavigator is TabbedViewNavigator) { if (TabbedViewNavigator(targetNavigator).tabBar) { var tabBarEffect:IEffect = createTabBarEffect(); if (tabBarEffect) Parallel(effect).addChild(tabBarEffect); } } // Prepare view effect var viewEffect:IEffect = createViewEffect(); if (viewEffect) Parallel(effect).addChild(viewEffect); } else { // Prepare full transition of navigator in its entirety. effect = createConsolidatedEffect(); } // Disable layout manager if requested. if (suspendBackgroundProcessing) UIComponent.suspendBackgroundProcessing(); } /** * Called by the default prepareForPlay() implementation, * this method is responsible for creating the Spark effect * played on the action bar when the transition starts. * This method should be overridden by subclasses if a custom action bar * effect is required. * By default, this method returns a basic action bar effect. * * @return An IEffect instance serving as the action bar effect. * This effect is played by the default play() method implementation. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function createActionBarEffect():IEffect { var transformOffsets:TransformOffsets; var slideDistance:Number; var animatedProperty:String; var actionBarSkin:UIComponent = actionBar.skin; var slideTargets:Array = new Array(); var fadeOutTargets:Array = new Array(); var fadeInTargets:Array = new Array(); // Return if we have a noop action bar transition mode. if (!actionBar || actionBarTransitionMode == ACTION_BAR_MODE_NONE || !actionBarTransitionMode) return null; transitionGroup = new Group(); transitionGroup.autoLayout = false; transitionGroup.includeInLayout = false; transitionGroup.width = actionBar.width; transitionGroup.height = actionBar.height; addComponentToContainer(transitionGroup, actionBarSkin); // Construct our parallel effect. var actionBarEffect:Parallel = new Parallel(); // Calculate the slide distance based on direction. switch (actionBarTransitionDirection) { case ViewTransitionDirection.RIGHT: animatedProperty = "x"; slideDistance = -actionBar.width / 2.5; break; case ViewTransitionDirection.DOWN: animatedProperty = "y"; slideDistance = -actionBar.height / 2.5; verticalTransition = true; break; case ViewTransitionDirection.UP: animatedProperty = "y"; slideDistance = actionBar.height / 2.5; verticalTransition = true; break; case ViewTransitionDirection.LEFT: default: animatedProperty = "x"; slideDistance = actionBar.width / 2.5; break; } transitionGroup.clipAndEnableScrolling = true; // Suppress slide if our action bar transition behavior is fade-only. if (actionBarTransitionMode == ACTION_BAR_MODE_FADE) slideDistance = 0; // If the skin has title content queue new title content for fade in. if (actionBar.titleGroup || actionBar.titleDisplay) { var titleComponent:UIComponent = actionBar.titleGroup; if (!titleComponent || !titleComponent.visible) titleComponent = actionBar.titleDisplay as UIComponent; if (titleComponent) { // Initialize the transformation offests transformOffsets = new TransformOffsets(); transformOffsets[animatedProperty] = slideDistance; slideTargets.push(transformOffsets); // Initialize titleGroup titleComponent.cacheAsBitmap = true; titleComponent.alpha = 0; titleComponent.postLayoutTransformOffsets = transformOffsets; fadeInTargets.push(titleComponent); // We reparent the titleComponent into the transition group so // that the items are properly clipped when animating vertically if (verticalTransition) transitionGroup.addElementAt(titleComponent, 0); } if (cachedTitleGroup) addCachedElementToGroup(transitionGroup, cachedTitleGroup, cachedTitleGroupGlobalPosition); } // If a cache of the navigation group exists, that means the content // changed. In this case the queue cached representation to be faded // out. if (cachedNavigationGroup) addCachedElementToGroup(transitionGroup, cachedNavigationGroup, cachedNavigationGroupGlobalPosition); // If a cache of the action group exists, that means the content // changed. In this case the queue cached representation to be faded // out. if (cachedActionGroup) addCachedElementToGroup(transitionGroup, cachedActionGroup, cachedActionGroupGlobalPosition); // Create fade in animations for navigationContent and actionContent // of the next view. if (endView) { if (endView.navigationContent) { // Initialize the transformation offests transformOffsets = new TransformOffsets(); transformOffsets[animatedProperty] = slideDistance; slideTargets.push(transformOffsets); actionBar.navigationGroup.postLayoutTransformOffsets = transformOffsets; actionBar.navigationGroup.cacheAsBitmap = true; actionBar.navigationGroup.alpha = 0; fadeInTargets.push(actionBar.navigationGroup); // We reparent the titleComponent into the transition group so // that the items are properly clipped when animating vertically if (verticalTransition) transitionGroup.addElementAt(actionBar.navigationGroup, 0); } if (endView.actionContent) { // Initialize the transformation offests transformOffsets = new TransformOffsets(); transformOffsets[animatedProperty] = slideDistance; slideTargets.push(transformOffsets); actionBar.actionGroup.postLayoutTransformOffsets = transformOffsets; actionBar.actionGroup.cacheAsBitmap = true; actionBar.actionGroup.alpha = 0; fadeInTargets.push(actionBar.actionGroup); // We reparent the titleComponent into the transition group so // that the items are properly clipped when animating vertically if (verticalTransition) transitionGroup.addElementAt(actionBar.actionGroup, 0); } } // Ensure bitmaps are rendered prior to invocation of our effect. transitionGroup.validateNow(); // Setup fade out targets if (cachedTitleGroup) { // Initialize the transformation offests transformOffsets = new TransformOffsets(); slideTargets.push(transformOffsets); cachedTitleGroup.postLayoutTransformOffsets = transformOffsets; fadeOutTargets.push(cachedTitleGroup.displayObject); } if (cachedNavigationGroup) { // Initialize the transformation offests transformOffsets = new TransformOffsets(); slideTargets.push(transformOffsets); cachedNavigationGroup.postLayoutTransformOffsets = transformOffsets; fadeOutTargets.push(cachedNavigationGroup.displayObject); } if (cachedActionGroup) { // Initialize the transformation offests transformOffsets = new TransformOffsets(); slideTargets.push(transformOffsets); cachedActionGroup.postLayoutTransformOffsets = transformOffsets; fadeOutTargets.push(cachedActionGroup.displayObject); } // If no fade effects we aren't animating anything so return null if (fadeInTargets.length == 0 && fadeOutTargets.length == 0) return null; // Create fade in effect if (fadeInTargets.length > 0) { var fadeInEffect:Fade = new Fade(); fadeInEffect.targets = fadeInTargets; fadeInEffect.duration = duration; fadeInEffect.alphaFrom = 0; fadeInEffect.alphaTo = 1; actionBarEffect.addChild(fadeInEffect); } // Create fade out effect if (fadeOutTargets.length > 0) { var fadeOutEffect:Fade = new Fade(); fadeOutEffect.targets = fadeOutTargets; fadeOutEffect.duration = duration; fadeOutEffect.alphaFrom = 1; fadeOutEffect.alphaTo = 0; actionBarEffect.addChild(fadeOutEffect); } // Create slide in effect var vector:Vector. = new Vector.(); vector.push(new SimpleMotionPath(animatedProperty, null, null, -slideDistance)); var moveEffect:Animate = new Animate(); moveEffect.targets = slideTargets; moveEffect.motionPaths = vector; moveEffect.easer = new spark.effects.easing.Sine(.7); moveEffect.duration = duration; moveEffect.addEventListener(EffectEvent.EFFECT_UPDATE, actionBarMoveEffect_effectUpdateHandler); moveEffect.addEventListener(EffectEvent.EFFECT_END, actionBarMoveEffect_effectEndedHandler); actionBarEffect.addChild(moveEffect); return actionBarEffect; } /** * Called by the default prepareForPlay() implementation, * this method is responsible for creating the Spark effect played * on the tab bar when the transition starts. * This method should be overridden by subclasses. * By default, this returns null. * * @return An IEffect instance serving as the tab bar transition. * This effect is played by the default play() method implementation. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function createTabBarEffect():IEffect { return null; } /** * Called by the default prepareForPlay() implementation, * this method is responsible for creating the Spark effect played * on the current and next view when the transition starts. * This method should be overridden by subclasses. * By default, this method returns null. * * @return An IEffect instance serving as the view transition. * This effect is played by the default play() method implementation. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function createViewEffect():IEffect { return null; } /** * Called by the default prepareForPlay() implementation, * this method is responsible for creating the Spark effect played to * transition the entire navigator, inclusive of the control bar content, * when necessary. * This method should be overridden by subclasses. * By default, this method returns null. * * @return An IEffect instance serving as the view transition. * This effect is played by the default play() method implementation. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function createConsolidatedEffect():IEffect { return null; } /** * Called by the transition to indicate that the transition * has completed. * This method dispatches the end event. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function transitionComplete():void { var stage:Stage; if (navigator) stage = navigator.stage; cleanUp(); activeTransitions.splice(activeTransitions.indexOf(this), 1); if (hasEventListener(FlexEvent.TRANSITION_END)) dispatchEvent(new FlexEvent(FlexEvent.TRANSITION_END)); if (stage && stage.hasEventListener(FlexEvent.TRANSITION_END)) stage.dispatchEvent(new FlexEvent(FlexEvent.TRANSITION_END)); } /** * Called after the transition completes. * This method is responsible for releasing any references * and temporary constructs used by the transition. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function cleanUp():void { if (!consolidatedTransition && transitionGroup) { if (cachedTitleGroup) transitionGroup.removeElement(cachedTitleGroup); if (cachedNavigationGroup) transitionGroup.removeElement(cachedNavigationGroup); if (cachedActionGroup) { transitionGroup.removeElement(cachedActionGroup); actionBar.actionGroup.cacheAsBitmap = false; } if (actionBar) { // Restore title group and title content to their original state. if (actionBar.titleGroup && actionBar.titleGroup.visible) { actionBar.titleGroup.postLayoutTransformOffsets = null; actionBar.titleGroup.cacheAsBitmap = false; } if (actionBar.titleDisplay && (actionBar.titleDisplay is DisplayObject) && DisplayObject(actionBar.titleDisplay).visible) { (actionBar.titleDisplay as UIComponent).postLayoutTransformOffsets = null; DisplayObject(actionBar.titleDisplay).cacheAsBitmap = false; } // Restore title group and title content to their original home. if (verticalTransition) { var titleComponent:UIComponent = actionBar.titleGroup; if (!titleComponent || !titleComponent.visible) titleComponent = actionBar.titleDisplay as UIComponent; if (titleComponent) { transitionGroup.removeElement(titleComponent); addComponentToContainer(titleComponent, actionBar.skin); } } // Restore navigation group to their proper home. if (endView.navigationContent && verticalTransition) { transitionGroup.removeElement(actionBar.navigationGroup); if (actionBar.titleDisplay) { var childIndex:uint = actionBar.skin.getChildIndex(actionBar.titleDisplay as DisplayObject); addComponentToContainerAt(actionBar.navigationGroup, actionBar.skin, childIndex); } else addComponentToContainer(actionBar.navigationGroup, actionBar.skin); } // Restore action group to their proper home. if (endView.actionContent && verticalTransition) { transitionGroup.removeElement(actionBar.actionGroup); if (actionBar.titleDisplay) { childIndex = actionBar.skin.getChildIndex(actionBar.titleDisplay as DisplayObject); addComponentToContainerAt(actionBar.actionGroup, actionBar.skin, childIndex); } else addComponentToContainer(actionBar.actionGroup, actionBar.skin); } removeComponentFromContainer(transitionGroup, actionBar.skin); actionBar.skin.scrollRect = null; // Force actionBar to update content group positions after // animating positions. If the width and height change during // the transition, we need relayout it's children because // during the transition they are removed from layout and // missed during the validation pass. See SDK-30142. // TODO (jasonsj): Consider ending transitions when orientation // changes if ((actionBar.width != cachedActionBarWidth) || (actionBar.height != cachedActionBarHeight)) { actionBar.skin.invalidateDisplayList(); } if (actionBar.actionGroup) actionBar.actionGroup.postLayoutTransformOffsets = null; if (actionBar.navigationGroup) actionBar.navigationGroup.postLayoutTransformOffsets = null; } verticalTransition = false; cachedActionBarHeight = 0; cachedActionBarWidth = 0; transitionGroup = null; cachedTitleGroup = null; cachedNavigationGroup = null; cachedActionGroup = null; } consolidatedTransition = false; actionBar = null; tabBar = null; parentNavigator = null; targetNavigator = null; navigator = null; startView = null; endView = null; // Re-enable layout manager if appropriate. if (suspendBackgroundProcessing) UIComponent.resumeBackgroundProcessing(); } /** * Determine if Flex can perform a transition on * action bar or tab bar content independently of the views. * *

Flex cannot perform a transition on the control bars independently:

*
    *
  • If the containing view navigator is a TabbedViewNavigator * and its tab bar's visibility changes between views.
  • *
  • If the value of the view navigator's overlayControls * property changes between views.
  • *
  • If the size or visibility of the action bar changes * between views.
  • *
* * @return false if Flex determines controls bars between views are * incompatible in some way. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function canTransitionControlBarContent():Boolean { // Short circuit if we've already been asked to not consider // control bars during transition. if (transitionControlsWithContent) return false; // Test for visibility or size of tab bar changing. if (targetNavigator is TabbedViewNavigator) { var tabBar:ButtonBarBase = TabbedViewNavigator(targetNavigator).tabBar; if (componentIsVisible(tabBar) != endView.tabBarVisible) return false; } // Test for visibility or size of action bar changing. if (navigator is ViewNavigator) { var actionBar:ActionBar = ViewNavigator(navigator).actionBar; if (componentIsVisible(actionBar) != endView.actionBarVisible) return false; } // Test for valid views. if (!startView || !endView) return false; // Test for value of overlayControls changing. if (startView.overlayControls != endView.overlayControls) return false; return true; } /** * Used to render snap shots of screen elements in * preparation for transitioning. * The bitmap is returned in the form of a BitmapImage object. * *

The BitmapImage is in target's parent coordiantes space - * it overlaps the target precisely if paranted to the same parent. * * When moving to a different parent, make sure to adjust the * transformation of the BitmapImage to correctly account for the * change in coordinate spaces. * * The updated value of the globalPosition parameter * can be used for that.

* * @param target Display object to capture. * * @param padding Padding around the object to be included in * the BitmapImage object. * * @param globalPosition When non-null, globalPosition * will be updated with the origin of the BitmapImage in global * coordiantes. When moving to a different coordinate space, this * value can be used to adjust the snapshot's position so its * global position on screen doesn't change. * * @return BitmapImage object representing the target. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function getSnapshot(target:UIComponent, padding:int = 4, globalPosition:Point = null):BitmapImage { if (!target || !target.visible || target.width == 0 || target.height == 0) return null; var snapshot:BitmapImage = new BitmapImage(); // Ensure bitmap leverages its own display object for performance // reasons. snapshot.alwaysCreateDisplayObject = true; // Capture image, with consideration for transform and color matrix. // Return null if an error is thrown. var bounds:Rectangle = new Rectangle(); try { snapshot.source = BitmapUtil.getSnapshotWithPadding(target, padding, true, bounds); } catch (e:SecurityError) { return null; } // Size and offset snapShot to match our image bounds data. snapshot.width = bounds.width; snapshot.height = bounds.height; var m:Matrix = new Matrix(); m.translate(bounds.left, bounds.top); // Apply target's inverse concatenated matrix: var parent:DisplayObjectContainer = target.parent; if (parent) { var inverted:Matrix = parent.transform.concatenatedMatrix.clone(); inverted.invert(); m.concat(inverted); } snapshot.setLayoutMatrix(m, false); // Exclude from layout. snapshot.includeInLayout = false; if (globalPosition) { var pt:Point = parent ? parent.localToGlobal(new Point(snapshot.x, snapshot.y)) : new Point(); globalPosition.x = pt.x; globalPosition.y = pt.y; } return snapshot; } //-------------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------------- /** * @private * Remove listeners */ private function actionBarMoveEffect_effectEndedHandler(event:EffectEvent):void { event.target.removeEventListener(EffectEvent.EFFECT_UPDATE, actionBarMoveEffect_effectUpdateHandler); event.target.removeEventListener(EffectEvent.EFFECT_END, actionBarMoveEffect_effectEndedHandler); } /** * @private * Since layout is disabled, we need to force validation on all of the * participating display objects. */ private function actionBarMoveEffect_effectUpdateHandler(event:EffectEvent):void { if (!actionBar) return; // This code is a temporary performance fix for transitions. Since layout is // disabled during ViewTransitions, in the past this method would call // validateDisplayList() on the animation targets to update their internal position // matrices. We learned that this was causing a reduction in framerate due to // actionScript overhead. To workaround the issue we are moving the x and y positions // of the underlying displayObjects manually. This is a temporary fix to get // our performance numbers back up after checking in a fix for SDK-30839. // TODO (chiedozi): Clean up this code and use propery layout methods if (verticalTransition) { if (actionBar.actionGroup && actionBar.actionGroup.postLayoutTransformOffsets) actionBar.actionGroup.$y = actionBar.actionGroup.y + actionBar.actionGroup.postLayoutTransformOffsets.y; if (actionBar.navigationGroup && actionBar.navigationGroup.postLayoutTransformOffsets) actionBar.navigationGroup.$y = actionBar.navigationGroup.y + actionBar.navigationGroup.postLayoutTransformOffsets.y; if (actionBar.titleDisplay && UIComponent(actionBar.titleDisplay).postLayoutTransformOffsets) UIComponent(actionBar.titleDisplay).$y = UIComponent(actionBar.titleDisplay).y + UIComponent(actionBar.titleDisplay).postLayoutTransformOffsets.y; if (actionBar.titleGroup && actionBar.titleGroup.postLayoutTransformOffsets) actionBar.titleGroup.$y = actionBar.titleGroup.y + actionBar.titleGroup.postLayoutTransformOffsets.y; if (cachedTitleGroup && cachedTitleGroup.displayObject) cachedTitleGroup.displayObject.y = cachedTitleGroup.y + cachedTitleGroup.postLayoutTransformOffsets.y; if (cachedNavigationGroup && cachedNavigationGroup.displayObject) cachedNavigationGroup.displayObject.y = cachedNavigationGroup.y + cachedNavigationGroup.postLayoutTransformOffsets.y; if (cachedActionGroup && cachedActionGroup.displayObject) cachedActionGroup.displayObject.y = cachedActionGroup.y + cachedActionGroup.postLayoutTransformOffsets.y; } else { if (actionBar.actionGroup && actionBar.actionGroup.postLayoutTransformOffsets) actionBar.actionGroup.$x = actionBar.actionGroup.x + actionBar.actionGroup.postLayoutTransformOffsets.x; if (actionBar.navigationGroup && actionBar.navigationGroup.postLayoutTransformOffsets) actionBar.navigationGroup.$x = actionBar.navigationGroup.x + actionBar.navigationGroup.postLayoutTransformOffsets.x; if (actionBar.titleDisplay && UIComponent(actionBar.titleDisplay).postLayoutTransformOffsets) UIComponent(actionBar.titleDisplay).$x = UIComponent(actionBar.titleDisplay).x + UIComponent(actionBar.titleDisplay).postLayoutTransformOffsets.x; if (actionBar.titleGroup && actionBar.titleGroup.postLayoutTransformOffsets) actionBar.titleGroup.$x = actionBar.titleGroup.x + actionBar.titleGroup.postLayoutTransformOffsets.x; if (cachedTitleGroup && cachedTitleGroup.displayObject) cachedTitleGroup.displayObject.x = cachedTitleGroup.x + cachedTitleGroup.postLayoutTransformOffsets.x; if (cachedNavigationGroup && cachedNavigationGroup.displayObject) cachedNavigationGroup.displayObject.x = cachedNavigationGroup.x + cachedNavigationGroup.postLayoutTransformOffsets.x; if (cachedActionGroup && cachedActionGroup.displayObject) cachedActionGroup.displayObject.x = cachedActionGroup.x + cachedActionGroup.postLayoutTransformOffsets.x; } } /** * @private */ private function effectComplete(event:EffectEvent):void { effect.removeEventListener(EffectEvent.EFFECT_END, effectComplete); // Validate the last frame of the actionBar animation so that it // renders properly. We put this here because layout isn't reenabled // until the next frame, meaning this validation won't be applied for // two frames. if (!consolidatedTransition) actionBarMoveEffect_effectUpdateHandler(null); if (renderLastFrame) { // We don't call transitionComplete just yet, we want to ensure // that the last frame of animation actually gets rendered on screen // before we clean up after ourselves. This prevents a perceived // stutter on the very last frame. navigator.addEventListener(Event.ENTER_FRAME, enterFrameHandler); } else { enterFrameHandler(null); } } /** * @private */ private function enterFrameHandler(event:Event):void { if (event) navigator.removeEventListener(Event.ENTER_FRAME, enterFrameHandler); effect = null; transitionComplete(); } /** * @private * Helper method to test whether a component is visible to user. */ mx_internal function componentIsVisible(component:UIComponent):Boolean { return component && component.visible && component.width && component.height && component.alpha; } /** * @private * Helper method to add a UIComponent instance to either an IVisualElementContainer * or DisplayObjectContainer. */ mx_internal function addComponentToContainerAt(component:UIComponent, container:UIComponent, index:int):void { if (container is IVisualElementContainer) IVisualElementContainer(container).addElementAt(component, index); else container.addChildAt(component, index); } /** * @private * Helper method to add a UIComponent instance to either an IVisualElementContainer * or DisplayObjectContainer. */ mx_internal function addComponentToContainer(component:UIComponent, container:UIComponent):void { if (container is IVisualElementContainer) IVisualElementContainer(container).addElement(component); else container.addChild(component); } /** * @private * Helper method to remove a UIComponent instance from either an IVisualElementContainer * or DisplayObjectContainer. */ mx_internal function removeComponentFromContainer(component:UIComponent, container:UIComponent):void { if (container is IVisualElementContainer) IVisualElementContainer(container).removeElement(component); else container.removeChild(component); } /** * @private * Helper method to set the child index of the given component. */ mx_internal function setComponentChildIndex(component:UIComponent, container:UIComponent, index:int):void { if (container is IVisualElementContainer) IVisualElementContainer(container).setElementIndex(component, index); else container.setChildIndex(component, index); } /** * @private * Adds the element to the targetGroup and adjusts the position * so that the global position remains the same. * * Note the targetGroup must be already added to the display list and * positioned in order for this method to adjust the cachedElement's position * correctly. * * @param targetGroup The Group that will parent the cached element. * @param cachedElement The cached element - the return value of getSnapshot() * @param cachedElementGlobalPosition The global position returned from getSnapshot() * * @see #getSnapshot */ mx_internal function addCachedElementToGroup(targetGroup:Group, cachedElement:BitmapImage, cachedElementGlobalPosition:Point):void { targetGroup.addElement(cachedElement); // We are moving the cachedTitleGroup to the transitionGroup's coordinate space, // adjust the position var localOrigin:Point = targetGroup.globalToLocal(cachedElementGlobalPosition); cachedElement.x = localOrigin.x; cachedElement.y = localOrigin.y; } /** * @private * Helper method that returns index of the given component. */ mx_internal function getComponentChildIndex(component:UIComponent, container:UIComponent):int { if (container is IVisualElementContainer) return IVisualElementContainer(container).getElementIndex(component); else return container.getChildIndex(component); } } }