//////////////////////////////////////////////////////////////////////////////// // // 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.components { import flash.events.Event; import flash.events.EventDispatcher; import flash.net.registerClassAlias; import mx.core.IVisualElement; 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.events.PropertyChangeEvent; import mx.managers.LayoutManager; import spark.components.supportClasses.NavigationStack; import spark.components.supportClasses.ViewDescriptor; import spark.components.supportClasses.ViewNavigatorAction; import spark.components.supportClasses.ViewNavigatorBase; import spark.components.supportClasses.ViewReturnObject; import spark.core.ContainerDestructionPolicy; import spark.effects.Animate; import spark.effects.animation.Animation; import spark.effects.animation.MotionPath; import spark.effects.animation.SimpleMotionPath; import spark.events.ViewNavigatorEvent; import spark.layouts.supportClasses.LayoutBase; import spark.transitions.SlideViewTransition; import spark.transitions.ViewTransitionBase; import spark.transitions.ViewTransitionDirection; use namespace mx_internal; [DefaultProperty("navigationStack")] //-------------------------------------- // SkinStates //-------------------------------------- /** * The state used when the navigator is in portrait orientation. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [SkinState("portrait")] /** * The state used when the navigator is in landscape orientation. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [SkinState("landscape")] /** * The state used when the navigator is in portrait orientation * and the navigator controls are overlaid on top. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [SkinState("portraitAndOverlay")] /** * The state used when the navigator is in landscape orientation * and the navigator controls are overlaid on top. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [SkinState("landscapeAndOverlay")] //-------------------------------------- // Other metadata //-------------------------------------- [IconFile("ViewNavigator.png")] /** * The ViewNavigator component is a container that consists of a collection of * View objects, where only the top most view is visible and active. * Use the ViewNavigator container to control the navigation among * the views of a mobile application. * The ViewNavigatorApplication container automatically creates a * single ViewNavigator container for the entire application. * *

Navigation in a mobile application is controlled by a stack of View objects. * The top View object on the stack defines the currently visible view. * The ViewNavigator container maintains the stack. * To change views, push a new View object onto the stack, * or pop the current View object off of the stack. * Popping the currently visible View object from the stack destroys * the View object, and returns the user to the previous view on the stack.

* *

When a view is pushed on top of the stack, the old view's data * property is automatically persisted. * It is restored when the view is reactived as a result of * the current view being popped off the stack. * When a new view becomes active by being pushed onto the stack, * the old view's instance is destroyed.

* *

The ViewNavigator displays an optional ActionBar control that displays contextual * information defined by the active view. * When the active view changes, the action bar is automatically updated.

* * @mxml * *

The <s:ViewNavigator> tag inherits all of the tag * attributes of its superclass and adds the following tag attributes:

* *
 *  <s:ViewNavigator
 *   Properties
 *    actionContent="null"
 *    actionLayout="null"
 *    defaultPopTransition="SlideViewTransition"
 *    defaultPushTransition="SlideViewTransition"
 *    firstView="null"
 *    firstViewData="null"
 *    navigationContent="null"
 *    navigationLayout="null"
 *    poppedViewReturnedObject="null"
 *    title=""
 *    titleContent="null"
 *    titleLayout="null"
 * 
 *  >
 *  
* * @see spark.components.View * @see spark.components.ActionBar * @see spark.components.TabbedViewNavigator * @see spark.transitions.ViewTransitionBase * * @includeExample examples/ViewNavigatorExample.mxml -noswf * @includeExample examples/ViewNavigatorExampleHomeView.mxml -noswf * @includeExample examples/ViewNavigatorExampleSearch.mxml -noswf * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public class ViewNavigator extends ViewNavigatorBase { //-------------------------------------------------------------------------- // // Class variables // //-------------------------------------------------------------------------- /** * @private * The animation duration used when hiding and showing the action bar. */ private static const ACTION_BAR_ANIMATION_DURATION:Number = 250; /** * @private * The animation duration used when running a default view transition. */ private static const DEFAULT_VIEW_TRANSITION_DURATION:Number = 300; /** * @private * Flag indicating whether the classes required for the PersistenceManager * have been registered with the player. */ private static var classAliasesRegistered:Boolean = false; /** * @private */ private static var viewTransitionSuspendCount:int = 0; private static var eventDispatcher:EventDispatcher; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function ViewNavigator() { super(); if (!classAliasesRegistered) { // Register aliases for custom classes that will be written to // persistence store by navigator registerClassAlias("ViewDescriptor", ViewDescriptor); registerClassAlias("NavigationStack", NavigationStack); classAliasesRegistered = true; } // Default view transitions var slideLeft:SlideViewTransition = new SlideViewTransition(); slideLeft.duration = DEFAULT_VIEW_TRANSITION_DURATION; slideLeft.direction = ViewTransitionDirection.LEFT; defaultPushTransition = slideLeft; var slideRight:SlideViewTransition = new SlideViewTransition(); slideRight.duration = DEFAULT_VIEW_TRANSITION_DURATION; slideRight.direction = ViewTransitionDirection.RIGHT; defaultPopTransition = slideRight; } //-------------------------------------------------------------------------- // // Skin Parts // //-------------------------------------------------------------------------- //---------------------------------------- // Navigator Controls //---------------------------------------- [SkinPart(required="false")] /** * A skin part that defines the action bar of the navigator. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public var actionBar:ActionBar; //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private */ private var actionBarProps:Object; /** * @private * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ private function get actionBarPropertyInvalidated():Boolean { return actionContentInvalidated || actionLayoutInvalidated || navigationContentInvalidated || navigationLayoutInvalidated || titleInvalidated || titleContentInvalidated || titleLayoutInvalidated || overlayControlsInvalidated; } /** * @private * The show/hide effect that is currently being played on the action bar. */ private var actionBarVisibilityEffect:IEffect; /** * @private */ private var contentGroupProps:Object; /** * @private * Internal flag used to track whether a show/hide effect should be * played when the action bar visibility is updated. */ private var animateActionBarVisbility:Boolean = false; /** * @private * Flag indicating the that actionBar visiblity has been invalidated * by the active view. */ private var actionBarVisibilityInvalidated:Boolean = false; /** * @private * Flag indicates that the backKey handler has run and the navigator * is waiting a validation pass. */ private var backKeyWasPressed:Boolean = false; /** * @private * The view data for the active view. */ private var currentViewDescriptor:ViewDescriptor = null; /** * @private */ private var delayedNavigationActions:Vector. = new Vector.(); /** * @private * Flag indicates that the navigator has been requested to show * a different view. */ mx_internal var viewChangeRequested:Boolean = false; /** * @private */ private var emptyViewDescriptor:ViewDescriptor = null; /** * @private * Variable used to count how many enterframes the navigator has * received after preparing a transition. */ private var enterFrameCount:int = 0; /** * @private * This following property stores the mouseEnabled * value defined on the navigator so that it can be * restored after a view transition. */ private var explicitMouseEnabled:Boolean; /** * @private * This following property stores the mouseChildren * value defined on the navigator so that it can be * restored after a view transition. */ private var explicitMouseChildren:Boolean; /** * @private */ private var overlayControlsInvalidated:Boolean = false; /** * @private * The view data for the pending view. */ private var pendingViewDescriptor:ViewDescriptor = null; /** * @private * The transition to play when the pending view is activated. */ private var pendingViewTransition:ViewTransitionBase = null; /** * @private * A variable used to store the transition to play after a * validation pass. This needs to be a different variable than * pendingViewTransition because the pending transition can * change as push and pops come in. */ mx_internal var activeTransition:ViewTransitionBase; /** * @private */ private var showingActionBar:Boolean; /** * @private * Flag indicates whether the navigator is in the process of * changing a view. */ // mx_internal so that TabbedViewNavigator can access mx_internal var viewChanging:Boolean = false; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // active //---------------------------------- /** * @private * Only gets called by TabbedViewNavigator. */ override mx_internal function setActive(value:Boolean, clearNavigationStack:Boolean = false):void { if (value == isActive) return; if (clearNavigationStack) navigationStack.popToFirstView(); if (value) { createTopView(); // If the view is already initialized, that means it was cached. We can complete the // activation process if the view hasn't been invalidated in any way. if (activeView) { if (activeView.initialized && !activeView.invalidatePropertiesFlag && !activeView.invalidateSizeFlag && !activeView.invalidateDisplayListFlag) { completeViewCommitProcess(); } else { // Wait until the view validates before activating it activeView.addEventListener(FlexEvent.UPDATE_COMPLETE, view_updateCompleteHandler); } } // Need to force a validation on the actionBar invalidateActionBarProperties(); } else { if (activeView) { var canDestroy:Boolean = (activeView.destructionPolicy != ContainerDestructionPolicy.NEVER); // If the instance of the view is being destroyed but our navigationStack is // maintained, the active view needs to serialize its data is application // persistence is enabled. if (canDestroy || clearNavigationStack) destroyViewInstance(navigationStack.topView, !clearNavigationStack); else deactiveView(activeView); } } // Call super after the above code so that the view has a chance // to be created before its active property is set. super.setActive(value, clearNavigationStack); } //---------------------------------- // activeView //---------------------------------- [Bindable("viewChangeComplete")] /** *

During a view transition, this property references the * view that the navigator is transitioning to.

* * @inheritDoc * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ override public function get activeView():View { if (pendingViewDescriptor) return pendingViewDescriptor.instance; if (currentViewDescriptor && currentViewDescriptor != emptyViewDescriptor) return currentViewDescriptor.instance; return null; } //---------------------------------- // exitApplicationOnBackKey //---------------------------------- /** * @private * This method is used to determine whether the application can * return to the home screen on Android when the back key is * pressed. An application can return to the home screen if the * length of the navigator is 1 or less. */ override mx_internal function get exitApplicationOnBackKey():Boolean { // If a back key is already being processed, we know that this // method is being called as a result of a duplicate back key press // during the same validation pass. So don't return to the home screen // and let the navigator process the navigation action during the // next validation. return !backKeyWasPressed && length <= 1; } //---------------------------------- // context //---------------------------------- /** * The string that describes the context in which the current view was * created. * This property is assigned to the value of the context * parameter passed to the ViewNavigator.pushView() method. * * @default null * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get context():Object { if (pendingViewDescriptor) return pendingViewDescriptor.context; else if (currentViewDescriptor) return currentViewDescriptor.context; return null; } //--------------------------------- // defaultPushTransition //--------------------------------- private var _defaultPushTransition:ViewTransitionBase; /** * Specifies the default view transition for push navigation operations. * * @default SlideViewTransition * * @see spark.transitions.SlideViewTransition * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get defaultPushTransition():ViewTransitionBase { return _defaultPushTransition; } /** * @private */ public function set defaultPushTransition(value:ViewTransitionBase):void { _defaultPushTransition = value; } //--------------------------------- // defaultPopTransition //--------------------------------- private var _defaultPopTransition:ViewTransitionBase; /** * Specifies the default view transition for pop navigation operations. * * @default SlideViewTransition * * @see spark.transitions.SlideViewTransition * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get defaultPopTransition():ViewTransitionBase { return _defaultPopTransition; } /** * @private */ public function set defaultPopTransition(value:ViewTransitionBase):void { _defaultPopTransition = value; } //---------------------------------- // firstView //---------------------------------- private var _firstView:Class; /** * Each view in an application corresponds to a View container * class defined in an ActionScript or MXML file. * This property specifies the view to use to initialize the first view * of the stack. * This property must reference a class that extends View container. * *

Specify any data passed to the first view by using * the firstViewData property.

* * @default null * * @see #firstViewData * @see View * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get firstView():Class { return _firstView; } /** * @private */ public function set firstView(value:Class):void { _firstView = value; } //---------------------------------- // firstViewData //---------------------------------- private var _firstViewData:Object; /** * The Object to pass to the data property * of the first view when the navigator is initialized. * Specify the first view by using the firstView property. * * @default null * * @see #firstView * @see View * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get firstViewData():Object { return _firstViewData; } /** * @private */ public function set firstViewData(value:Object):void { _firstViewData = value; } //---------------------------------- // length //---------------------------------- [Bindable("lengthChanged")] /** * Returns the number of views being managed by the navigator. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get length():int { return navigationStack.length; } //---------------------------------- // navigationStack //---------------------------------- /** * @private */ override mx_internal function set navigationStack(value:NavigationStack):void { super.navigationStack = value; viewChangeRequested = true; invalidateProperties(); } //---------------------------------- // poppedViewReturnedObject //---------------------------------- private var _poppedViewReturnedObject:ViewReturnObject = null; /** * Holds the object returned by the last view that was popped * off the navigation stack or replaced by another view. * To return a value, the view being popped off the stack overrides * its createReturnObject() method. * *

This object is only available when the navigator is in the process of switching * views in response to a pop or replace navigation operation. * This object is guaranteed to be valid when the new view receives * the add event, and is destroyed after * the view receives a viewActivate event.

* * @default null * * @see View#createReturnObject() * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get poppedViewReturnedObject():ViewReturnObject { return _poppedViewReturnedObject; } //-------------------------------------------------------------------------- // // UI Template Properties // //-------------------------------------------------------------------------- //---------------------------------- // actionContent //---------------------------------- private var _actionContent:Array; private var actionContentInvalidated:Boolean = false; [ArrayElementType("mx.core.IVisualElement")] /** * This property overrides the actionContent * property in the ActionBar and * ViewNavigatorApplication components. * * @copy ActionBar#actionContent * * @default null * * @see ActionBar#actionContent * @see spark.skins.mobile.ActionBarSkin * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get actionContent():Array { return _actionContent; } /** * @private */ public function set actionContent(value:Array):void { _actionContent = value; if (!activeView || (activeView && !activeView.actionContent)) { actionContentInvalidated = true; invalidateProperties(); } } //---------------------------------- // actionLayout //---------------------------------- private var _actionLayout:LayoutBase; private var actionLayoutInvalidated:Boolean = false; /** * @copy ActionBar#actionContent * * @default null * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get actionLayout():LayoutBase { return _actionLayout; } /** * @private */ public function set actionLayout(value:LayoutBase):void { _actionLayout = value; if (!activeView || (activeView && !activeView.actionLayout)) { actionLayoutInvalidated = true; invalidateProperties(); } } //---------------------------------- // navigationContent //---------------------------------- private var _navigationContent:Array; private var navigationContentInvalidated:Boolean = false; [ArrayElementType("mx.core.IVisualElement")] /** * This property overrides the navigationContent * property in the ActionBar and * ViewNavigatorApplication components. * * @copy ActionBar#navigationContent * * @default null * * @see ActionBar#navigationContent * @see spark.skins.mobile.ActionBarSkin * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get navigationContent():Array { return _navigationContent; } /** * @private */ public function set navigationContent(value:Array):void { _navigationContent = value; if (!activeView || (activeView && !activeView.navigationContent)) { navigationContentInvalidated = true; invalidateProperties(); } } //---------------------------------- // navigationLayout //---------------------------------- private var _navigationLayout:LayoutBase; private var navigationLayoutInvalidated:Boolean = false; /** * @copy ActionBar#navigationLayout * * @default null * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get navigationLayout():LayoutBase { return _navigationLayout; } /** * @private */ public function set navigationLayout(value:LayoutBase):void { _navigationLayout = value; if (!activeView || (activeView && !activeView.navigationLayout)) { navigationLayoutInvalidated = true; invalidateProperties(); } } //---------------------------------- // title //---------------------------------- private var _title:String; private var titleInvalidated:Boolean = false; [Bindable] /** * This property overrides the title * property in the ActionBar and ViewNavigatorApplication components. * * @copy ActionBar#title * * @default "" * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get title():String { return _title; } /** * @private */ public function set title(value:String):void { if (_title != value) { _title = value; // title will only have an effect on the view if titleContent or title isn't // set anywhere else if (!activeView || (activeView && !activeView.title && !activeView.titleContent && !titleContent)) { titleInvalidated = true; invalidateProperties(); } } } //---------------------------------- // titleContent //---------------------------------- private var _titleContent:Array; private var titleContentInvalidated:Boolean = false; [ArrayElementType("mx.core.IVisualElement")] /** * This property overrides the titleContent * property in the ActionBar and ViewNavigatorApplication components. * * @copy ActionBar#titleContent * * @default null * * @see ActionBar#titleContent * @see spark.skins.mobile.ActionBarSkin * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get titleContent():Array { return _titleContent; } /** * @private */ public function set titleContent(value:Array):void { _titleContent = value; if (!activeView || (activeView && !activeView.titleContent)) { titleContentInvalidated = true; invalidateProperties(); } } //---------------------------------- // titleLayout //---------------------------------- private var _titleLayout:LayoutBase; private var titleLayoutInvalidated:Boolean = false; /** * @copy ActionBar#titleLayout * * @default null * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get titleLayout():LayoutBase { return _titleLayout; } /** * @private */ public function set titleLayout(value:LayoutBase):void { _titleLayout = value; if (!activeView || (activeView && !activeView.titleLayout)) { titleLayoutInvalidated = true; invalidateProperties(); } } //-------------------------------------------------------------------------- // // Public Methods // //-------------------------------------------------------------------------- /** * Removes all of the views from the navigator stack. * This method changes the display to a blank screen. * * @param transition The view transition to play while switching views. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function popAll(transition:ViewTransitionBase = null):void { if (navigationStack.length == 0 || !canRemoveCurrentView()) return; scheduleAction(ViewNavigatorAction.POP_ALL, null, null, null, transition); } /** * Pops the current view off the navigation stack. * The current view is represented by the top view on the stack. * The previous view on the stack becomes the current view. * * @param transition The view transition to play while switching views. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function popView(transition:ViewTransitionBase = null):void { if (navigationStack.length == 0 || !canRemoveCurrentView()) return; scheduleAction(ViewNavigatorAction.POP, null, null, null, transition); } /** * Removes all views except the bottom view from the navigation stack. * The bottom view is the one that was first pushed onto the stack. * * @param transition The view transition to play while switching views. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function popToFirstView(transition:ViewTransitionBase = null):void { if (navigationStack.length < 2 || !canRemoveCurrentView()) return; scheduleAction(ViewNavigatorAction.POP_TO_FIRST, null, null, null, transition); } /** * Pushes a new view onto the top of the navigation stack. * The view pushed onto the stack becomes the current view. * * @param viewClass The class used to create the view. * This argument must reference a class that extends View container. * * @param data The data object to pass to the view. * This argument is written to the data property of the new view. * * @param context An arbitrary object written to * the ViewNavigator.context property. * When the new view is created, it can reference this property * and perform an action based on its value. * For example, the view could display data in different ways based * on the value of context. * * @param transition The view transition to play while switching views. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function pushView(viewClass:Class, data:Object = null, context:Object = null, transition:ViewTransitionBase = null):void { if (viewClass == null || !canRemoveCurrentView()) return; scheduleAction(ViewNavigatorAction.PUSH, viewClass, data, context, transition); } /** * Replaces the top view of the navigation stack with a new view. * The view replacing the current view on the stack becomes the current view. * * @param viewClass The class used to create the replacement view. * This argument must reference a class that extends View container. * * @param data The data object to pass to the view. * This argument is written to the data property of the new view. * * @param context An arbitrary object used to describe the context * of the push. When the new view is created, it can * reference this property. * * @param transition The view transition to play while switching views. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function replaceView(viewClass:Class, data:Object = null, context:Object = null, transition:ViewTransitionBase = null):void { if (viewClass == null || !canRemoveCurrentView()) return; scheduleAction(ViewNavigatorAction.REPLACE, viewClass, data, context, transition); } /** * Shows the action bar. * * @param animate Indicates whether a show effect should be played * when the action bar appears. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function showActionBar(animate:Boolean = true):void { if (!actionBar) return; // Ignore this call if the actionBar is already being shown if (actionBarVisibilityEffect && showingActionBar) return; showingActionBar = true; animateActionBarVisbility = animate; actionBarVisibilityInvalidated = true; invalidateProperties(); } /** * Hides the action bar. * * @param animate Indicates whether a hide effect should be played * when the action bar is hidden. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function hideActionBar(animate:Boolean = true):void { if (!actionBar) return; // Ignore this call if the actionBar is already being hidden if (actionBarVisibilityEffect && !showingActionBar) return; showingActionBar = false; animateActionBarVisbility = animate; actionBarVisibilityInvalidated = true; invalidateProperties(); } /** * Pops to the previous view of the navigator in response to the back * key. ViewNavigator only allows this method to be called once during * a navigation event. All subsequent calls to this method will be ignored * until the current view transition is complete. * *

ViewNavigatorApplication automatically calls this method when the back * key is pressed.

* * @langversion 3.0 * @playerversion AIR 3.1 * @productversion Flex 4.6 */ override public function backKeyUpHandler():void { if (!backKeyWasPressed && activeView && !activeView.backKeyHandledByView()) { popView(); backKeyWasPressed = true; } } //-------------------------------------------------------------------------- // // Protected Methods // //-------------------------------------------------------------------------- /** * @private * Initializes the view change process by disabling inputs on the * navigator. If the navigator has a parent, the parents mouse * interaction flags are disabled. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function committingNavigatorAction():void { viewChanging = true; explicitMouseChildren = mouseChildren; explicitMouseEnabled = mouseEnabled; mouseEnabled = false; mouseChildren = false; } /** * @private */ override mx_internal function canRemoveCurrentView():Boolean { var view:View; if (!currentViewDescriptor) return true; view = currentViewDescriptor.instance; return (view == null || view.canRemove()); } /** * @private * Helper method that clears the action bar property invalidation flags. * * @default null * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ private function clearActionBarInvalidationFlags():void { actionContentInvalidated = false; actionLayoutInvalidated = false; navigationContentInvalidated = false; navigationLayoutInvalidated = false; titleInvalidated = false; titleContentInvalidated = false; titleLayoutInvalidated = false; overlayControlsInvalidated = false; } /** * @private * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ override protected function commitProperties():void { if (!isActive) return; // If this is the components first validation pass, push the firstView // on the stack if possible, otherwise set the currentViewChange flag // to true so that an empty screen is created. If the currentViewDescriptor // property exists, that means an empty view was previously created // because a firstView property wasn't supplied. if (!initialized && navigationStack.length == 0 && !currentViewDescriptor) { if (firstView) navigationStack.pushView(firstView, firstViewData); viewChangeRequested = true; } if (viewChangeRequested) commitNavigatorAction(); // Updating the action bar properties and visibility is the responsibility // of commitViewChange if the current view has changed because they must take // part in transitions. If the view change is processed during this validation, // the following flags will be false. if (actionBarPropertyInvalidated) updateControlsForView(activeView); if (actionBarVisibilityInvalidated) commitVisibilityChanges(); // When true, this flag prevents action bar animations from running. This flag // is only set to true if the application received an orientation event this // frame (See ViewNavigatorBase). The flag is reset at the end of commitProperties // so that animations run again. if (disableNextControlAnimation) disableNextControlAnimation = false; // Call base class' commitProperties after the above so that state changes // as a result of overlayControls are caught. This is okay because // this method doesn't rely on any properties from the base class. super.commitProperties(); } /** * @private */ private function get lastActionWasAPop():Boolean { return ((lastAction == ViewNavigatorAction.POP) || (lastAction == ViewNavigatorAction.POP_ALL) || (lastAction == ViewNavigatorAction.POP_TO_FIRST) || (lastAction == ViewNavigatorAction.REPLACE)); } /** * @private * This method registers a navigation operation with the navigators * action queue. Navigation operations aren't performed until the * following frame to allow components to properly update their * visual state before any complicated actionScript code is run by the * navigator. * *

This method will execute all operations when the next ENTER_FRAME * event is dispatched by the runtime.(); animate.motionPaths.push(new SimpleMotionPath("y", actionBarProps.start.y, actionBarProps.end.y)); // Add action bar effect to final parallel effect effect = animate; finalEffect.addChild(effect); // Create animation for content group effect = createContentVisibilityEffect(contentGroupProps); effect.target = contentGroup; finalEffect.addChild(effect); return finalEffect; } /** * @private * Responsible for calculating the final positions of the action bar * when it's visiblity is changed. This method will force a validation * pass. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ private function prepareActionBarForAnimation(showAnimation:Boolean):void { var animateActionBarUp:Boolean; // Determine whether the action bar should be animated up or down if (overlayControls) { // If the control is overlaid on top, the actionBar is animated up if // the actionBar is above the center of the navigator animateActionBarUp = (actionBar.y + (actionBar.height / 2)) <= height / 2; } else { // The actionBar is animated up if it is above the contentGroup animateActionBarUp = actionBar.y <= contentGroup.y; } // Need to validate to capture final positions and sizes of skin parts. // If the navigator is a child of another, we need the root navigator // to perform the validation so that all widths and heights of all // containers are sized. LayoutManager.getInstance().validateNow(); // This will store the final location and sizes of the components actionBarProps.end = captureAnimationValues(actionBar); contentGroupProps.end = captureAnimationValues(contentGroup); // Update the end position of the animation based on whether the // actionBar is showing/hiding and if it is animating up or down. if (animateActionBarUp) { if (showAnimation) actionBarProps.start.y = -actionBar.height; else actionBarProps.end.y = -actionBar.height; } else { if (showAnimation) actionBarProps.start.y = this.height; else actionBarProps.end.y = this.height; } actionBar.visible = true; actionBar.includeInLayout = false; actionBar.cacheAsBitmap = true; } /** * @private * Creates the effect to play on the contentGroup when the navigator is * generating an animation to play to hide or show the action bar. This effect * should only target the contentGroup as it will be played in parallel with * other effects that animate the other navigator skin parts. * * @param hiding Indicates whether the action bar is hiding or showing * @param props The bounds properties that were captured for the actionBar. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ private function createContentVisibilityEffect(props:Object):IEffect { var animate:Animate = new Animate(); animate.target = contentGroup; animate.duration = ACTION_BAR_ANIMATION_DURATION; animate.motionPaths = new Vector.(); animate.motionPaths.push(new SimpleMotionPath("height", props.start.height, props.end.height)); animate.motionPaths.push(new SimpleMotionPath("y", props.start.y, props.end.y)); contentGroup.includeInLayout = false; return animate; } /** * @private * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ private function visibilityAnimation_effectEndHandler(event:EffectEvent):void { event.target.removeEventListener(EffectEvent.EFFECT_END, visibilityAnimation_effectEndHandler); // Clear flags and temporary properties actionBarVisibilityEffect = null; if (activeView) activeView.setActionBarVisible(actionBarProps.showing); // Check if the visible and cacheAsBitmap properties have been set. These are // only set if the default transitions are used. If developers create their // own animations, these properties won't be set. if (actionBarProps.start != undefined) { actionBar.visible = actionBar.includeInLayout = !actionBarProps.start.visible; actionBar.cacheAsBitmap = actionBarProps.start.cacheAsBitmap; } actionBarProps = null; // Content group properties object only created if the default transitions were used if (contentGroupProps) { contentGroup.includeInLayout = contentGroupProps.start.includeInLayout; // The default action bar hide and show animation will animate the width and height // of the navigator's contentGroup. If the explicitWidth or explicitHeight properties // were NaN before the animation, they'll be set to real values by the animation. As // a result, it is necessary to restore them to NaN so that layout properly sizes these // components. if (isNaN(contentGroupProps.start.explicitHeight)) contentGroup.explicitHeight = NaN; if (isNaN(contentGroupProps.start.explicitWidth)) contentGroup.explicitWidth = NaN; if (!isNaN(contentGroupProps.start.percentWidth)) contentGroup.percentWidth = contentGroupProps.start.percentWidth; if (!isNaN(contentGroupProps.start.percentHeight)) contentGroup.percentHeight = contentGroupProps.start.percentHeight; contentGroupProps = null; } } /** * @private * This method is responsible for completing a view change validation pass. * It is responsible for cleaning up and destroying the old view, as well as * activating the new one. * *

If a transition was played, this method is called after the transition * completes.

* * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function navigatorActionCommitted():void { // Destroy the previous view if (currentViewDescriptor) destroyViewInstance(currentViewDescriptor); // Update view pointers currentViewDescriptor = pendingViewDescriptor; pendingViewDescriptor = null; // Clear empty flag if necessary if (emptyViewDescriptor && currentViewDescriptor != emptyViewDescriptor) emptyViewDescriptor = null; // If there is no focus or the item that had focus isn't // on the display list anymore, update the focus to be // the active view or the view navigator updateFocus(); // Clear the returned object _poppedViewReturnedObject = null; // Restore mouse children properties before revalidation occurs. This // needs to occur before a possible revalidation occurs so that the // saved mouseChildren and mouseEnabled flags aren't overwritten. mouseChildren = explicitMouseChildren; mouseEnabled = explicitMouseEnabled; // SDK-28230 // Wait a frame before sending the complete event so that the player // has the chance to render the last frame before any custom actionscript // is run in response to a VIEW_ACTIVATE event. addEventListener(Event.ENTER_FRAME, enterFrameHandler); } /** * @private * Called when the activeView has received its first updateComplete event * after the navigator has been activated. See setActive(). */ private function view_updateCompleteHandler(event:FlexEvent):void { event.target.removeEventListener(FlexEvent.UPDATE_COMPLETE, view_updateCompleteHandler); completeViewCommitProcess(); } /** * @private * Called after a navigation operation is complete. See * navigatorActionCommitted(). */ private function enterFrameHandler(event:Event):void { removeEventListener(Event.ENTER_FRAME, enterFrameHandler); completeViewCommitProcess(); } /** * @private * Activates the current view and completes the view change process. */ private function completeViewCommitProcess():void { // ViewNavigator doesn't allow for another navigation operation to // be run during a view change. If the component attempts to do // one, it is queued and run after the current transition is complete. // The delayedNavigationActions queue size will be non zero in that case. // If there are items in the queue, force another validation to commit // navigation change. Otherwise the transition process can end. if (delayedNavigationActions.length > 0) { executeDelayedActions(); commitNavigatorAction(); return; } // The viewChanging flag is set to false right before the current view // activates so that navigation operations run during VIEW_ACTIVATE // are properly executed by the navigator viewChanging = false; // At this point, currentViewDescriptor points to the new view. // The navigator needs to listen for property change events on the // view so that it can be notified when the template properties // (e.g, title, titleContent, etc) are changed. if (currentViewDescriptor) { var currentView:View = currentViewDescriptor.instance; if (currentView) { currentView.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, view_propertyChangeHandler); // Activate the current view. This will dispatch a VIEW_ACTIVATE event. currentView.setActive(true); } } // Notify listeners that the view change is complete if (hasEventListener("viewChangeComplete")) dispatchEvent(new Event("viewChangeComplete")); // Clear flag indicating that the back key was pressed backKeyWasPressed = false; lastAction = ViewNavigatorAction.NONE; } /** * @private * Called in commitProperties() and begins the view transition * process. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function commitNavigatorAction():void { if (!isActive) { viewChangeRequested = false; return; } // Private event if (hasEventListener("viewChangeStart")) dispatchEvent(new Event("viewChangeStart")); // If a ui control is animating, force it to end if (actionBarVisibilityEffect) actionBarVisibilityEffect.end(); if (activeView && lastActionWasAPop) { _poppedViewReturnedObject = createViewReturnObject(currentViewDescriptor); } committingNavigatorAction(); pendingViewDescriptor = navigationStack.topView; // Create an empty view if no firstView viewClass is defined if (pendingViewDescriptor == null) { emptyViewDescriptor = new ViewDescriptor(View); pendingViewDescriptor = emptyViewDescriptor; } if (pendingViewDescriptor.viewClass != null) { var view:View = createViewInstance(pendingViewDescriptor); viewChangeRequested = false; // Hide the view so that it doesn't render this frame view.visible = false; activeTransition = transitionsEnabled ? pendingViewTransition : null; // TODO (chiedozi): Consider capturingStartValues now and updating actionBar // to remove forced validation in prepareViewTransition() // Prepare the view transition after the navigator has validated the new View addEventListener(FlexEvent.UPDATE_COMPLETE, prepareViewTransition); } pendingViewTransition = null; } /** * @private * This method is used to create the top view of the ViewNavigator. This * is only used by TabbedViewNavigator when the selected tab has changed. */ override mx_internal function createTopView():void { // Check if the top view already exists if (activeView) return; invalidateActionBarProperties(); // If the navigation stack is empty, push on the firstView for the // navigator. if (navigationStack.length == 0) { if (firstView != null) navigationStack.pushView(firstView, firstViewData); else return; } // Update the current view reference currentViewDescriptor = navigationStack.topView; // Create the view if needed var view:View = currentViewDescriptor.instance; if (!view) { view = createViewInstance(currentViewDescriptor); view.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, view_propertyChangeHandler); } // Cancel any view change requests that occurred prior to this call // since the top most view was just created. viewChangeRequested = false; } /** * @private */ private function createViewInstance(viewProxy:ViewDescriptor):View { var view:View; if (viewProxy.instance == null) { view = new viewProxy.viewClass(); viewProxy.instance = view; } else { view = viewProxy.instance; // Need to update the view's orientation state if it was saved view.setCurrentState(view.getCurrentViewState(), false); } // Restore persistence data if necessary if (viewProxy.data == null && viewProxy.persistenceData != null) viewProxy.data = view.deserializeData(viewProxy.persistenceData); view.setNavigator(this); view.data = viewProxy.data; view.percentWidth = view.percentHeight = 100; addElement(view); return view; } /** * @private */ private function createViewReturnObject(viewProxy:ViewDescriptor):ViewReturnObject { var view:View = viewProxy.instance; if (view) return new ViewReturnObject(view.createReturnObject(), viewProxy.context); return null; } /** * @private */ private function destroyViewInstance(viewProxy:ViewDescriptor, forceDataPersist:Boolean = false):void { var currentView:View = viewProxy.instance; if (!currentView) return; // Deactivate the view if it is active deactiveView(currentView); removeElement(currentView); // Grab the data from the old view and persist it if (lastAction == ViewNavigatorAction.PUSH || forceDataPersist) { viewProxy.data = currentView.data; viewProxy.persistenceData = currentView.serializeData(); } // Check if we can delete the reference for the view instance. If the current // view is being replaced or popped of the stack, we know we can delete it. // Otherwise a push is happening and we need to check the destructionPolicy // of the view. if (lastActionWasAPop || currentView.destructionPolicy != ContainerDestructionPolicy.NEVER) { currentView.setNavigator(null); viewProxy.instance = null; } } /** * @private */ private function deactiveView(view:View):void { if (view.isActive) view.setActive(false); view.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, view_propertyChangeHandler); } /** * @private */ override public function saveViewData():Object { var savedData:Object = super.saveViewData(); if (currentViewDescriptor && currentViewDescriptor.instance) currentViewDescriptor.persistenceData = currentViewDescriptor.instance.serializeData(); if (!savedData) savedData = {}; savedData.navigationStack = navigationStack; return savedData; } /** * @private */ override public function loadViewData(value:Object):void { super.loadViewData(value); if (value) navigationStack = value.navigationStack as NavigationStack; } /** * @private * Method is called during the view transition process after the * instance of the new view is added to the display list. It initializes * the underlying ViewDescriptor object and prepares the transition. * * Called after the UPDATE_COMPLETE event is received on the navigator * after the pendingView is added to the display list. * See commitNavigatorAction. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ private function prepareViewTransition(event:Event):void { removeEventListener(FlexEvent.UPDATE_COMPLETE, prepareViewTransition); var currentView:View; var pendingView:View; // Deactivate the current view if (currentViewDescriptor) { currentView = currentViewDescriptor.instance; currentView.setActive(false); // In most cases this is a No-Op because this method is called in response // to UPDATE_COMPLETE on the navigator and there should be no objects // in the LayoutManager's queue. It's only here to ensure that the // current view is up to date incase anything changed. currentView.validateNow(); } // Store new view if (pendingViewDescriptor) { pendingView = pendingViewDescriptor.instance; pendingView.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, view_propertyChangeHandler); } if (activeTransition) { activeTransition.addEventListener(FlexEvent.TRANSITION_END, transitionComplete); activeTransition.startView = currentView; activeTransition.endView = pendingView; activeTransition.navigator = this; activeTransition.preInit(); if (stage) stage.dispatchEvent(new Event("viewTransitionPrepare")); } // Only dispatch this event if the stage exists. if (stage && viewTransitionSuspendCount > 0) { if (!eventDispatcher) eventDispatcher = new EventDispatcher(); eventDispatcher.addEventListener("viewTransitionReady", completeTransitionPreparations); } else { completeTransitionPreparations(); } } /** * @private */ mx_internal static function suspendTransitions():void { viewTransitionSuspendCount++; } /** * @private */ mx_internal static function resumeTransitions():void { if (viewTransitionSuspendCount == 0) return; viewTransitionSuspendCount--; if (viewTransitionSuspendCount == 0) eventDispatcher.dispatchEvent(new Event("viewTransitionReady")); } /** * @private */ private function completeTransitionPreparations(event:Event = null):void { if (event) event.target.removeEventListener("viewTransitionReady", completeTransitionPreparations); var pendingView:View; if (pendingViewDescriptor) { pendingView = pendingViewDescriptor.instance; pendingView.visible = true; } // Give the transition a chance to prepare before the view updates if (activeTransition) activeTransition.captureStartValues(); // This event is dispatched here to allow developers to incorporate // length specific changes into the view navigator transitions if (hasEventListener("lengthChanged")) dispatchEvent(new Event("lengthChanged")); // Invalidate the actionBar properties if (actionBar) { invalidateActionBarProperties(); updateControlsForView(pendingView); } // Only force validation for the navigator if initialized to avoid // validation from occurring with wrong measured dimensions if (initialized) { // Need to validate my children now to prevent flicker when no transition, // or so sizes can be measured before transition if (parentNavigator) UIComponent(parentNavigator).validateNow(); else validateNow(); } if (activeTransition) { activeTransition.captureEndValues(); activeTransition.prepareForPlay(); // Wait a frame so that any queued work can be completed by the framework // and runtime before the transition starts. As of Flex 4.6, we wait 2 // frames to allow StageText to fully render the swapped in bitmaps. // Otherwise rendering time would overlap with the first frame of the animation. enterFrameCount = 0; addEventListener(Event.ENTER_FRAME, startViewTransition); } else { navigatorActionCommitted(); } } /** * @private * Starts the view transition. */ private function startViewTransition(event:Event):void { // Incrememnt the enterFrameCount. ViewNavigator waits two frames // before begining the animation. enterFrameCount++; if (enterFrameCount < 2) return; // Remove the enter frame listener removeEventListener(Event.ENTER_FRAME, startViewTransition); if (hasEventListener(FlexEvent.TRANSITION_START)) dispatchEvent(new FlexEvent(FlexEvent.TRANSITION_START, false, false)); // Force the master clock of the animation engine to update its // current time so that the overhead of creating the view and preparing // the transition is not included in our animation interpolation. // See SDK-27793 Animation.pulse(); activeTransition.play(); } /** * @private * Called when a transition dispatches an FlexEvent.TRANSITION_END event. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ private function transitionComplete(event:Event):void { ViewTransitionBase(event.target).removeEventListener(FlexEvent.TRANSITION_END, transitionComplete); if (hasEventListener(FlexEvent.TRANSITION_END)) dispatchEvent(new FlexEvent(FlexEvent.TRANSITION_END, false, false)); activeTransition = null; navigatorActionCommitted(); } /** * @private * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ override public function updateControlsForView(view:View):void { super.updateControlsForView(view); if (!actionBar) return; // If there is no view, update the actionBar should display the // navigator defaults if (view == null) { actionBar.actionContent = actionContent; actionBar.actionLayout = actionLayout; actionBar.navigationContent = navigationContent; actionBar.navigationLayout = navigationLayout; actionBar.title = title; actionBar.titleContent = titleContent; actionBar.titleLayout = titleLayout; overlayControls = false; } else { if (actionContentInvalidated) { actionBar.actionContent = view && view.actionContent ? view.actionContent : actionContent; actionContentInvalidated = false; } if (actionLayoutInvalidated) { actionBar.actionLayout = view && view.actionLayout ? view.actionLayout : actionLayout; actionLayoutInvalidated = false; } if (navigationContentInvalidated) { actionBar.navigationContent = view && view.navigationContent ? view.navigationContent : navigationContent; navigationContentInvalidated = false; } if (navigationLayoutInvalidated) { actionBar.navigationLayout = view && view.navigationLayout ? view.navigationLayout : navigationLayout; navigationLayoutInvalidated = false; } if (titleInvalidated) { actionBar.title = view && view.title ? view.title : title; titleInvalidated = false; } if (titleContentInvalidated) { actionBar.titleContent = view && view.titleContent ? view.titleContent : titleContent; titleContentInvalidated = false; } if (titleLayoutInvalidated) { actionBar.titleLayout = view && view.titleLayout ? view.titleLayout : titleLayout; titleLayoutInvalidated = false; } if (overlayControlsInvalidated) { if (overlayControls != view.overlayControls) { overlayControls = view.overlayControls; // We need to call super commitProperties() so that the new state is applied super.commitProperties(); } overlayControlsInvalidated = false; } actionBar.visible = actionBar.includeInLayout = view && view.actionBarVisible; actionBarVisibilityInvalidated = false; actionBar.invalidateSize(); actionBar.invalidateDisplayList(); } } /** * @private * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ private function view_propertyChangeHandler(event:PropertyChangeEvent):void { var property:Object = event.property; // Check for actionBar related property changes if (actionBar) { var propertyInvalidated:Boolean = true; if (property == "title") titleInvalidated = true; else if (property == "titleContent") titleContentInvalidated = true; else if (property == "titleLayout") titleLayoutInvalidated = true; else if (property == "actionContent") actionContentInvalidated = true; else if (property == "actionLayout") actionLayoutInvalidated = true; else if (property == "navigationContent") navigationContentInvalidated = true; else if (property == "navigationLayout") navigationLayoutInvalidated = true; else propertyInvalidated = false; if (propertyInvalidated) invalidateProperties(); } if (property == "overlayControls") { overlayControlsInvalidated = true; invalidateProperties(); } } /** * @private * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ override protected function partAdded(partName:String, instance:Object):void { super.partAdded(partName, instance); // If the actionBar changes, need to reset the properties on it if (instance == actionBar) { actionContentInvalidated = true; actionLayoutInvalidated = true; navigationContentInvalidated = true; navigationLayoutInvalidated = true; titleInvalidated = true; titleContentInvalidated = true; titleLayoutInvalidated = true; invalidateProperties(); } } /** * @private */ override protected function partRemoved(partName:String, instance:Object):void { super.partRemoved(partName, instance); // Clear out all the content that is within the actionBar if (instance == actionBar) { actionBar.actionContent = null; actionBar.actionLayout = null; actionBar.titleContent = null; actionBar.titleLayout = null; actionBar.navigationContent = null; actionBar.navigationContent = null; } } } }