////////////////////////////////////////////////////////////////////////////////
//
// 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.
//
////////////////////////////////////////////////////////////////////////////////
// TODO (chiedozi): TabbedViewNavigator should override setActive and activate
// the correct ViewNavigator.
package spark.components
{
import flash.display.Stage;
import flash.events.Event;
import flash.events.IEventDispatcher;
import flash.events.MouseEvent;
import mx.core.ISelectableList;
import mx.core.IVisualElement;
import mx.core.UIComponent;
import mx.core.mx_internal;
import mx.effects.IEffect;
import mx.effects.Parallel;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.EffectEvent;
import mx.events.FlexEvent;
import mx.events.PropertyChangeEvent;
import mx.events.PropertyChangeEventKind;
import mx.managers.LayoutManager;
import mx.resources.ResourceManager;
import spark.components.supportClasses.ButtonBarBase;
import spark.components.supportClasses.ViewNavigatorBase;
import spark.effects.Animate;
import spark.effects.animation.MotionPath;
import spark.effects.animation.SimpleMotionPath;
import spark.events.ElementExistenceEvent;
import spark.events.IndexChangeEvent;
import spark.events.RendererExistenceEvent;
use namespace mx_internal;
//--------------------------------------
// Events
//--------------------------------------
/**
* Dispatched when the current view navigator changes as a result of
* a change to the selectedIndex
property or a change
* to the selected tab in the TabBar control.
*
* @eventType spark.events.IndexChangeEvent.CHANGE
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="change", type="spark.events.IndexChangeEvent")]
/**
* Dispatched before the selected view navigator is changed.
* Canceling this event prevents the active view navigator
* from changing.
*
* @eventType spark.events.IndexChangeEvent.CHANGING
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="changing", type="spark.events.IndexChangeEvent")]
/**
* Dispatched when the collection of view navigators managed by the
* TabbedViewNavigator changes.
*
* @eventType mx.events.CollectionEvent.COLLECTION_CHANGE
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="collectionChange", type="mx.events.CollectionEvent")]
/**
* Dispatched when the view navigator's selected index changes.
* When this event is dispatched, the selectedIndex
* and selectedNavigator
properties reference the
* newly selected view navigator.
*
* @eventType mx.events.FlexEvent.VALUE_COMMIT
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="valueCommit", type="mx.events.FlexEvent")]
//--------------------------------------
// Other metadata
//--------------------------------------
[IconFile("TabbedViewNavigator.png")]
/**
* The TabbedViewNavigator class is a container that manages a collection
* of view navigator containers.
* Only one view navigator is active and visible at a time.
* This class includes a TabBar control that provides the ability to toggle between
* the collection of view navigators.
*
*
The following image shows a TabbedViewNavigator with three sections: * Employees, Contacts, and Search:
* *
*
*
The TabbedViewNavigatorApplication container automatically creates
* a single TabbedViewNavigator container for the entire application.
* You can reference the TabbedViewNavigator object by using the navigator
* property of the TabbedViewNavigatorApplication container.
The active or selected navigator can be changed by clicking the corresponding
* tab in the TabBar or by changing the selectedIndex
property of
* the component.
The contents of a child view navigator is destroyed when it is deactivate, * and dynamically created when activated.
* * @see spark.components.View * @see spark.components.ViewNavigator * @see spark.components.TabBar * * @includeExample examples/TabbedViewNavigatorExample.mxml -noswf * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public class TabbedViewNavigator extends ViewNavigatorBase implements ISelectableList { //-------------------------------------------------------------------------- // // Class variables // //-------------------------------------------------------------------------- // TODO (chiedozi): Consider having a NO_SELECTION constant to differentiate // between NO_PROPOSED_SELECTION and clearing the selection. See List. It // seems redundant because TabbedViewNavigator expects requireSelection to be // true on its TabBar. /** * @private * Static constant representing no proposed selection. */ private static const NO_PROPOSED_SELECTION:int = -1; /** * @private * The animation duration used when hiding and showing the action bar. */ private static const TAB_BAR_ANIMATION_DURATION:Number = 250; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function TabbedViewNavigator() { super(); addEventListener(ElementExistenceEvent.ELEMENT_ADD, elementAddHandler); addEventListener(ElementExistenceEvent.ELEMENT_REMOVE, elementRemoveHandler); } //-------------------------------------------------------------------------- // // Skin Parts // //-------------------------------------------------------------------------- [Bindable] [SkinPart(required="false")] /** * A skin part that defines the tab bar of the navigator. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public var tabBar:ButtonBarBase; //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private */ private var animateTabBarVisbility:Boolean = false; /** * @private */ private var contentGroupProps:Object; /** * @private * Determines if a changing event has already been dispatched. * This was introduced to prevent the IndexChangeEvent from * dispatching twice when the tabBar selection changes. */ private var changingEventDispatched:Boolean = false; /** * @private */ private var dataProviderChanged:Boolean = false; /** * @private */ private var explicitTabBarMouseEnabled:Boolean = false; /** * @private * Keeps track of the tab that was last selected. See * tabBarRendererClicked() for information on how it's used. */ private var lastSelectedIndex:int = -1; /** * @private */ private var selectedIndexChanged:Boolean = false; /** * @private */ private var selectedIndexAdjusted:Boolean = false; /** * @private */ private var showingTabBar:Boolean; /** * @private */ private var tabBarVisibilityEffect:IEffect; /** * @private */ private var tabBarProps:Object; /** * @private */ private var tabBarVisibilityChanged:Boolean = false; /** * @private */ mx_internal var selectedNavigatorChangingView:Boolean = false; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // activeView //---------------------------------- [Bindable("viewChangeComplete")] /** * @private */ override public function get activeView():View { if (selectedNavigator) return selectedNavigator.activeView; return null; } //---------------------------------- // exitApplicationOnBackKey //---------------------------------- /** * @private */ override mx_internal function get exitApplicationOnBackKey():Boolean { if (selectedNavigator) return selectedNavigator.exitApplicationOnBackKey; return super.exitApplicationOnBackKey; } //---------------------------------- // maintainNavigationStack //---------------------------------- private var _maintainNavigationStack:Boolean = true; /** * @private * Specifies whether the navigation stack of the view navigator * should remain intact when the view navigator is deactivated. * Iftrue
, when reactivated the view history
* remains the same.
* If false
, the navigator displays the
* first view in its navigation stack.
*
* @default true
*
* @see spark.components.ViewNavigator
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
mx_internal function get maintainNavigationStack():Boolean
{
return _maintainNavigationStack;
}
/**
* @private
*/
mx_internal function set maintainNavigationStack(value:Boolean):void
{
_maintainNavigationStack = value;
}
//----------------------------------
// navigators
//----------------------------------
/**
* The view navigators that are managed by this TabbedViewNavigator.
* Each view navigator is represented as a tab in the tab bar
* of this TabbedViewNavigator.
* Only one view navigator can be active at a time.
* You can reference the active view navigator by using
* the selectedNavigator
property.
*
* Changing this property causes the current view navigator to be removed,
* and sets the selectedIndex
to 0.
* This operation cannot be canceled and is committed immediately.
selectedIndex
property or by selecting
* a tab in the TabBar control.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get selectedNavigator():ViewNavigatorBase
{
if (length == 0 || selectedIndex < 0 || selectedIndex >= length)
return null;
return getElementAt(selectedIndex) as ViewNavigatorBase;
}
//--------------------------------------------------------------------------
//
// Public Methods
//
//--------------------------------------------------------------------------
/**
* Hides the tab bar of the navigator.
*
* @param animate Indicates whether a hide effect should play
* as the tab bar disappears.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function hideTabBar(animate:Boolean = true):void
{
if (!tabBar)
return;
showingTabBar = false;
animateTabBarVisbility = animate;
tabBarVisibilityChanged = true;
invalidateProperties();
}
/**
* Shows the tab bar of the navigator
*
* @param animate Indicates whether a show effect should play
* as the tab bar appears.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function showTabBar(animate:Boolean = true):void
{
if (!tabBar)
return;
showingTabBar = true;
animateTabBarVisbility = animate;
tabBarVisibilityChanged = true;
invalidateProperties();
}
/**
* @private
* Updates the navigator's state based on the properties set
* on the active view. The main use cases are to hide the
* TabBar and the change the overlay state.
*
* @param view The active view.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
override public function updateControlsForView(view:View):void
{
super.updateControlsForView(view);
if (view)
{
if (tabBar)
tabBar.visible = tabBar.includeInLayout = view.tabBarVisible;
overlayControls = view.overlayControls;
}
else
{
// If the current view is null, the tab bar is shown so that the
// user still has a ui for changing the selected tab.
if (tabBar)
tabBar.visible = tabBar.includeInLayout = true;
overlayControls = false;
}
tabBarVisibilityChanged = false;
}
/**
* Calls the backKeyUpHandler() of the selected navigator.
*
* TabbedViewNavigatorApplication 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 (selectedNavigator) selectedNavigator.backKeyUpHandler(); } //-------------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------------- /** * @private * Calculates the final positions for the tab bar visibility animations. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ private function prepareTabBarForAnimation(showAnimation:Boolean):void { var animateTabBarUp:Boolean; if (overlayControls) { // If the control is overlaid on top, the tabBar is animated up if // the tabBar is above the center of the navigator animateTabBarUp = (tabBar.y + (tabBar.height / 2)) <= height / 2; } else { // The tabBar is animated up if it is above the contentGroup animateTabBarUp = tabBar.y <= contentGroup.y; } // Need to validate to capture final positions and sizes of skin parts LayoutManager.getInstance().validateNow(); // This will store the final location and sizes of the components tabBarProps.end = captureAnimationValues(tabBar); contentGroupProps.end = captureAnimationValues(contentGroup); if (tabBarVisibilityChanged) { if (animateTabBarUp) { if (tabBarProps.start.visible) tabBarProps.end.y = -tabBar.height; else tabBarProps.start.y = -tabBar.height; } else { if (tabBarProps.start.visible) tabBarProps.end.y = this.height; else tabBarProps.start.y = this.height; } } tabBar.visible = true; tabBar.includeInLayout = false; tabBar.cacheAsBitmap = true; } /** * @private */ override mx_internal function canRemoveCurrentView():Boolean { return (!selectedNavigator || selectedNavigator.canRemoveCurrentView()); } /** * @private */ private function addNavigatorListeners(navigator:ViewNavigatorBase):void { navigator.addEventListener("viewChangeComplete", navigator_viewChangeCompleteHandler); navigator.addEventListener("viewChangeStart", navigator_viewChangeStartHandler); navigator.addEventListener(ElementExistenceEvent.ELEMENT_ADD, navigator_elementAddHandler); navigator.addEventListener(ElementExistenceEvent.ELEMENT_REMOVE, navigator_elementRemoveHandler); } /** * @private */ private function removeNavigatorListeners(navigator:ViewNavigatorBase):void { navigator.removeEventListener("viewChangeComplete", navigator_viewChangeCompleteHandler); navigator.removeEventListener("viewChangeStart", navigator_viewChangeStartHandler); navigator.removeEventListener(ElementExistenceEvent.ELEMENT_ADD, navigator_elementAddHandler); navigator.removeEventListener(ElementExistenceEvent.ELEMENT_REMOVE, navigator_elementRemoveHandler); } /** * @private */ private function setupNavigator(navigator:ViewNavigatorBase):void { navigator.setParentNavigator(this); navigator.visible = false; navigator.includeInLayout = false; // All navigators should be inactive when initialized. The proper // navigator will be activated during commitProperties(). navigator.setActive(false); startTrackingUpdates(navigator); } /** * @private */ private function cleanUpNavigator(navigator:ViewNavigatorBase):void { navigator.setParentNavigator(null); if (navigator.isActive) navigator.setActive(false); if (navigator.activeView) { navigator.activeView.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, view_propertyChangeHandler); } removeNavigatorListeners(navigator); stopTrackingUpdates(navigator); } /** * @private * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ override protected function commitProperties():void { var changeEvent:IndexChangeEvent; if (selectedIndexChanged || selectedIndexAdjusted || dataProviderChanged) { var navigator:ViewNavigatorBase; // Store the old index var oldIndex:int = _selectedIndex; if (selectedIndex >= length) selectedIndex = (length > 0) ? 0 : NO_PROPOSED_SELECTION; var indexChangedPrevented:Boolean = false; // The selected index property can be changed programmitically on the // TabbedViewNavigator or by its tab bar in response to a user action. If // this was initiated by the tab bar, a chaning event would have already been // dispatched in tabBar_indexChanging(). if (initialized && selectedIndexChanged && !changingEventDispatched) { // If the active view's REMOVING event or the navigator's // CHANGING event was canceled, prevent the index change if (!indexCanChange(selectedIndex)) { _proposedSelectedIndex = NO_PROPOSED_SELECTION; indexChangedPrevented = true; } } changingEventDispatched = false; if (!indexChangedPrevented) { // If the data provider has changed, the navigator elements have // already been removed, so the following code doesn't need to run if (!selectedIndexAdjusted && !dataProviderChanged && _selectedIndex >= 0 && _selectedIndex < length) { navigator = getElementAt(_selectedIndex) as ViewNavigatorBase; navigator.setActive(false, !maintainNavigationStack); navigator.visible = false; navigator.includeInLayout = false; if (navigator.activeView) navigator.activeView.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, view_propertyChangeHandler); removeNavigatorListeners(navigator); } commitSelection(); if (_selectedIndex >= 0) { // 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(); navigator = getElementAt(_selectedIndex) as ViewNavigatorBase; navigator.setActive(true); navigator.visible = true; navigator.includeInLayout = true; addNavigatorListeners(navigator); // Update the states of the controls on the newly activated view navigator. // The updateControlsForView() method will automatically bubble up to all // parents of the navigator. navigator.updateControlsForView(navigator.activeView); // Force a validation of the new navigator to prevent a flicker from // occurring in cases where multiple validation passes are required // to completely validate a view if (initialized) currentContentGroup.validateNow(); if (navigator.activeView) { navigator.activeView.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, view_propertyChangeHandler); } } // Dispatch selection change event if (hasEventListener(IndexChangeEvent.CHANGE)) { changeEvent = new IndexChangeEvent(IndexChangeEvent.CHANGE, false, false); changeEvent.oldIndex = oldIndex; changeEvent.newIndex = _selectedIndex; } } selectedIndexAdjusted = false; dataProviderChanged = false; selectedIndexChanged = false; } if (tabBarVisibilityChanged) commitVisibilityChanges(); // When true, this flag prevents tab 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 should be okay because // TabbedViewNavigator doesn't need any properties in its base class being // validated to complete the above code super.commitProperties(); // Dispatch the index change event after super.commitProperties() has changed // so the callbacks can deal with state changes performed by commitProperties. if (changeEvent) dispatchEvent(changeEvent); } /** * @private * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ protected function commitSelection():void { _selectedIndex = _proposedSelectedIndex; _proposedSelectedIndex = NO_PROPOSED_SELECTION; lastSelectedIndex = _selectedIndex; if (hasEventListener(FlexEvent.VALUE_COMMIT)) dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); } /** * @private * Commits the requested visibility changes made to the action * bar and tab bar. * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ private function commitVisibilityChanges():void { // Can't change the visibility during a view transition if (selectedNavigatorChangingView) return; // If an animation is running, end it if (tabBarVisibilityEffect) tabBarVisibilityEffect.end(); // Change the visibility of the tabBar if the desired state // is different than the current state if (tabBar && showingTabBar != tabBar.visible) { // The animateTabBarVisibility flag is set to true if the // hideTabBar() or showTabBar() methods are called with the // animate flag set to true if (!disableNextControlAnimation && transitionsEnabled && animateTabBarVisbility) { tabBarProps = {target:tabBar, showing:showingTabBar}; tabBarVisibilityEffect = showingTabBar ? createTabBarShowEffect() : createTabBarHideEffect(); tabBarVisibilityEffect.addEventListener(EffectEvent.EFFECT_END, visibilityAnimation_effectEndHandler); tabBarVisibilityEffect.play(); } else { // Since the visibility is not being animated, toggle the state tabBar.visible = tabBar.includeInLayout = showingTabBar; if (activeView) activeView.setTabBarVisible(showingTabBar); } } tabBarVisibilityChanged = false; } /** * Creates the effect to play when the TabBar control is shown. * The produced effect is responsible for animating both the * TabBar and the content group of the navigator. * *TabbedViewNavigator will expect the includeInLayout
* and visible
properties of the TabBar to be true
* after this effect is run.
TabbedViewNavigator expects the includeInLayout
* and visible
properties of the TabBar to be false
* after this effect runs.