\
It is recommend that developers read the ViewNavigator Specification before reading this document.
\
Callout - A type of popup used to display content above the main content area. A callout has a visual identifier that points to the component that caused it to be displayed.
\
Since the release of Flex 4.5, tablet devices have become a more common target platform. As a result, developers are looking for ways to easily create applications for these devices. Due to the increased real-estate provided by the form factor and usage patterns of tablet devices, tablet interfaces vary from their phone counterparts. Many applications are moving towards a more web like interface where the screen is broken up into 'frames' or pieces of content. A common interface pattern used on tablets is the master/detail interface. This UI pattern divides the screen into two main content areas, the master view and the detail view. The master view tends to be the main driver of the application, and interacting with it often changes the content displayed in the detail view.
The image above shows an example of a Master/Detail interface. As the user interacts with the list in the master view on the left, the content area on the right changes to reflect the selection. Interacting with components in the detail view never changes the state of the master view. In addition, it is important to note that each section maintains its own ActionBar and internal view hierarchy. This pattern comes with a couple of best practices when dealing with orientation. In portrait, some apps consider hiding the master view and providing a UI affordance that redisplays it in a popup. On the other hand, some applications reposition the master view to be above the detail view.
The Flex framework will help developers create these UIs by introducing a new ViewNavigator component, [SplitViewNavigator]. This component is a container that manages the display of two view navigators side-by-side, imitating a master/detail interface. Since the component operates with ViewNavigators, both content areas will have unique ActionBars and view hierarchy. [SplitViewNavigator] will provide apis that allow a developer to change the navigator's behavior when the orientation changes as well as apis for displaying a navigator in a popup such as a Callout.
Note that Flex will not provide an application class similar to ViewNavigatorApplication to support tablet development. Instead developers are expected to use existing application classes (Application, ViewNavigatorApplication and TabbedViewNavigatorApplication) in conjunction with the new navigator and manually hook up necessary device features (such as the Back and Menu key on Android devices).
\
Jennifer is building a tablet application using Flex 4.5. She wants to create a master/detail interface that is commonly used on tablet devices. To do so, she uses the SplitViewNavigator component in her top level Application mxml file and provides it two view navigators. By default, SplitViewNavigator shows the master view on the left, and detail view on the right for all orientations. Jennifer's design requires that in the portrait orientation, the master navigator is displayed on top of the detail navigator. Since SplitViewNavigator is skinnable, she swaps the containers layout to be a vertical layout when the application enteres a portrait orientation.
Stan is building a master/detail tablet application using [SplitViewNavigator]. When his application is run on a device that is in portrait orientation, he would like to hide the master view and reveal it in a popup. To do so, he sets autoHideFirstViewNavigator
property to true on his [SplitViewNavigator]. This causes the navigator to automatically hide the left navigator when in a portrait orientation. From there, he adds a Button to his ActionBar that when clicked calls the showFirstViewNavigatorInPopUp
method. When executed, the navigator displays itself in a popup. By default, the popup is a Callout component that will automatically position itself next to the owner passed into the method above.
\
[SplitViewNavigator] is a chrome-less skinnable container that is responsible for displaying multiple ViewNavigators at once. The component consists of a single contentGroup that holds the child navigators supplied by the developer. [SplitViewNavigator]'s children can be any component that extends ViewNavigatorBase. So one navigator could be a TabbedViewNavigator, and the other could be a standard ViewNavigator. By default, SplitViewNavigator lays its children out in a horizontal fashion using HorizontalLayout.
Below is a sample MXML tag that initializes a SplitViewNavigator:
<s:SplitViewNavigator width="100%" height="100%"> <s:ViewNavigator firstView="views.MasterCategory"/> <s:ViewNavigator firstView="views.DetailView"/> </s:SplitViewNavigator>
Note that SplitViewNavigator doesn't restrict the amount of navigators supplied to it.
\
Accessing the Navigators
SplitViewNavigator is a IVisualElementContainer and supports all the methods of that interface. A developer can use methods such as getElementAt()
to obtain a reference to a navigator. Just note that when a navigator is displayed in a popup, it is no longer a child of the SplitViewNavigator, so you may get unexpected results until the popup is closed. To deal with this, SplitViewNavigator provides the getViewNavigatorAt()
method. This function provides a safe way to access the navigators regardless of whether one is displayed in a popup or not.
There are situations where a View would like access to the SplitViewNavigator or one of the child navigators it manages. For example, when using a master/detail UI model, selecting an item in the master view may cause the detail navigator to navigate to another view. A view can communicate with SplitViewNavigator by using the parentNavigator
property of its navigator introduced in Flex 4.5. In the MXML example above, the MasterCategory view can access the SplitViewNavigator by doing the following:
public function itemSelected():void { // Get a reference to the SplitViewNavigator managing this View's navigator var splitNavigator:SplitViewNavigator = navigator.parentNavigator as SplitViewNavigator; // Push a new view on the detail navigator (splitNavigator.getViewNavigatorAtIndex(1) as ViewNavigator).pushView(SomeView); }
If multiple navigators live between the View and the [SplitViewNavigator], it is the developers responsibility to traverse the parent chain. For example, if a TabbedViewNavigator was used in the sample above, an additional parentNavigator
would need to be used:
public function itemSelected():void { // Access the SplitViewNavigator managing this View's navigator var splitNavigator:SplitViewNavigator = navigator.parentNavigator.parentNavigator as SplitViewNavigator; ... }
\
By default, SplitViewNavigator ignores all orientation events. This means that both navigators will remain visible in both landscape and portrait orientations. The developer can use the autoHideFirstViewNavigator
property to change this behavior. When set to true
, the first view navigator's visible
property will automatically be set to false
when the device has a portrait aspectRatio.
When hidden, the developer can toggle the first view navigator by setting it's visible
property to true. Though the recommended approach is to use the showFirstViewNavigatorInPopUp()
method described in #Showing a Navigator in a PopUp.
\
When using a master/detail interface, it's common for a tablet application to hide the master view when the device is held in a portrait orientation. Often, developers will add a visual affordance that can be used to hide and show the master view in a popup or dialog. To support this use case, [SplitViewNavigator] will introduce the showFirstViewNavigatorInPopUp()
method. When called, this method will show the first view navigator in a modal popup, which by default is a Callout. While visible, the user will not be able to interact with the application below. This method is ignored if called while the popup is open. When the device is reoriented to landscape, the popup will automatically be hidden and the original child navigator restored. A developer can manually close the popup using the hideViewNavigatorPopUp()
method.
A new Callout skin will be provided that has the actionBar blended with the frame of the callout. Read the Callout Specification for more information about the new Callout components.
#Example 3 shows how a developer can show a navigator in a callout.
\
Hiding and Showing Navigators
Besides the autoHideFirstViewNavigator
property described above, SplitViewNavigator does not supply any apis for hiding and showing navigators. Instead, the component will respect the value of the navigators' visible
property. When hidden, a navigator will be removed from the contentGroup's layout as well. If a navigator is currently being displayed in a popup and its visible
property is set to false
, the popup will close.
public function hideFirstNavigator():void { // Access the SplitViewNavigator managing this View's navigator var splitNavigator:SplitViewNavigator = navigator.parentNavigator. as SplitViewNavigator; // This will cause the SplitViewNavigator to hide the navigator splitNavigator.getViewNavigatorAt(0).visible = false; }
\
Customizing the Layout of the Navigators
By default, SplitViewNavigator uses a HorizontalLayout to lay out its children. The developer can always change the layout used component by providing a different layout class. The developer should supply a width and height value for each navigator being managed.
<s:SplitViewNavigator width="100%" height="100%"> <s:layout> <s:VerticalLayout gap="1"/> </s:layout> <s:ViewNavigator firstView="views.MasterCategory" width="100%" height="256"/> <s:ViewNavigator firstView="views.DetailView" width="100%" height="100%"/> </s:SplitViewNavigator>
In the example above, the width of both child navigators will extend to the width of the SplitViewNavigator. The first navigator's height is explicitly sized and the second navigator will take up the rest of the space. To customize the layout of the navigators beyond this, the developer can write a custom layout.
\
SplitViewNavigator doesn't automatically integrate with the device back or menu key on Android. It is the developer's responsibility to listen for and implement handlers for the device key events. As a best practice, the framework recommends listening for these events on the stage and not on a specific View to ensure that they are always received by the application.
\
SplitViewNavigator will implement the loadViewData
and saveViewData
methods that are part of the ViewNavigatorBase
super class. This means the component will know how to serialize and deserialize the navigation stack and view data for each of its child navigators. But, since the framework will not be introducing an Application class built around SplitViewNavigator, the developer must manually connect these methods to the persistence manager. How to do this is shown in #Example 4.
\
This component was designed with tablet devices in mind. Though not restricted, this component isn't recommended for use on mobile phones due to their available screen real-estate on those devices. But, a developer could use this component to aid in the development of a universal application that runs across tablet and phone devices. If the developer places a SplitViewNavigator in their root Application file, they can hide and show the first or second navigator depending on whether the device is a tablet or phone device (using their own heuristic to determine this). At that point the application can use conditional code to determine which navigator to perform navigation operations. For example, on a phone, the developer can hide the first ViewNavigator and always operate on the second ViewNavigator, while on a tablet, the developer would operate on both of the navigators.
Note that this is not a explicitly supported use case for this component, but a developer can attempt to do this at their own risk.
\
\
package spark.components { //-------------------------------------- // SkinStates //-------------------------------------- /** * This skin state is triggered when the aspectRatio of the main * application is portrait. * * @langversion 3.0 * @playerversion AIR 3.0 * @productversion Flex 4.5.2 */ <a href="SkinState%28%26quot%3Bportrait%26quot%3B%29">SkinState("portrait")</a> /** * The state is triggered when the aspectRatio of the main * application is landscape. * * @langversion 3.0 * @playerversion AIR 3.0 * @productversion Flex 4.5.2 */ <a href="SkinState%28%26quot%3Blandscape%26quot%3B%29">SkinState("landscape")</a> public class SplitViewNavigator extends ViewNavigatorBase { //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- public function SplitViewNavigator(); //-------------------------------------------------------------------------- // // Skin Parts // //-------------------------------------------------------------------------- <a href="SkinPart%28required%3D%26quot%3Bfalse%26quot%3B%29">SkinPart(required="false")</a> /** * Inherited from SkinnableContainer * * @langversion 3.0 * @playerversion AIR 3.0 * @productversion Flex 4.5.2 */ public var contentGroup:Group; <a href="SkinPart%28required%3D%26quot%3Bfalse%26quot%3B%29">SkinPart(required="false")</a> /** * The popup used to display the first view navigator when * showFirstViewNavigatorInPopUp() is called. * * @langversion 3.0 * @playerversion AIR 3.0 * @productversion Flex 4.5.2 */ public var viewNavigatorPopUp:SkinnablePopUpContainer; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // autoHideFirstViewNavigator //---------------------------------- /** * This flag indicates whether the visibility of the first view navigator should * be automatically be toggled when the device receives an orientation change event. * When true, the visible property of the first navigator will be set to false in the * as the device enters the portrait orientation, and true when entering a landscape * orientation. This behavior will override any value currently set by the application. * When hidden, the developer can manually set the visible property on the navigator * to redisplay it. * * @default false * * @langversion 3.0 * @playerversion AIR 3.0 * @productversion Flex 4.5.2 */ public function get autoHideFirstViewNavigator():Boolean; public function set autoHideFirstViewNavigator(value:Boolean):void; //-------------------------------------------------------------------------- // // Public Methods // //-------------------------------------------------------------------------- /** * This method returns a specific child navigator independent of the container's * child display hierarchy. Since a child navigator is not parented by this * container when visible inside a popup, this method should be used instead of * <code>getElementAt()</code>. * * <p>When the navigator popup is open, the navigator at index 0 will refer to the * navigator in the popup.</p> * * @param index Index of the navigator to retrieve * * @return The navigator at the specified index, null if one does not exist * * @langversion 3.0 * @playerversion AIR 3 * @productversion Flex 4.5.2 */ public function getViewNavigatorAt(index:int):ViewNavigatorBase; /** * Displays the first navigator inside the SplitViewNavigator's popup * skinpart. * * If the popup is already open, the method call will be ignored. * * @langversion 3.0 * @playerversion AIR 3.0 * @productversion Flex 4.5.2 */ public function showFirstViewNavigatorInPopUp(owner:DisplayObjectContainer):void; /** * Hides the navigator popup if its open. * * @langversion 3.0 * @playerversion AIR 3.0 * @productversion Flex 4.5.2 */ public function hideViewNavigatorPopUp():void; //-------------------------------------------------------------------------- // // Overriden Methods: ViewNavigatorBase // //-------------------------------------------------------------------------- /** * Deserializes state data for this component's child navigators. This * method expects the value object to be a object that was previously * generated by the saveViewData() method. * * @param value The object to deserialize * * @langversion 3.0 * @playerversion AIR 3.0 * @productversion Flex 4.5.2 */ override public function loadViewData(value:Object):void; /** * Serializes the component's child navigators in a format that * can be saved in a PersistenceManager. * * @return The serialized data * * @langversion 3.0 * @playerversion AIR 3.0 * @productversion Flex 4.5.2 */ override public function saveViewData():Object; } }
\
package spark.skins.mobile { public class SplitViewNavigatorSkin extends MobileSkin { public function SplitViewNavigatorSkin(); /** * The container that holds the secondary navigator */ public var viewNavigatorPopUp:SkinnablePopUpContainer; } }
\
\
This is an example of how you declare a SplitViewNavigator. This will create a master/detail layout where the left navigator will show the MasterView and the right will display the DetailView.
<s:SplitViewNavigator width="100%" height="100%"> <s:ViewNavigator firstView="views.MasterCategory" width="256" height="100%"/> <s:ViewNavigator firstView="views.DetailView" width="100%" height="100%"/> </s:SplitViewNavigator>
The code below shows how the MasterView can use the SplitViewNavigator to communicate with the secondary view navigator. It consists of a full screen list. When a list item is selected, a new view is pushed on the secondary navigator.
<?xml version="1.0" encoding="utf-8"?> <s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Master View"> <fx:Script> <![CDATA[ import spark.components.SplitViewNavigator; import spark.components.ViewNavigator; import spark.events.IndexChangeEvent; protected function listChangeHandler(event:IndexChangeEvent):void { var splitNavigator:SplitViewNavigator = navigator.parentNavigator as SplitViewNavigator; var detailNavigator:ViewNavigator = splitNavigator.getViewNavigatorAtIndex(1) as ViewNavigator; detailNavigator.pushView(DetailView, event.newIndex); } ]]> </fx:Script> <fx:Declarations> <s:ArrayList id="listData"> <fx:String>Item 0</fx:String> <fx:String>Item 1</fx:String> <fx:String>Item 2</fx:String> <fx:String>Item 3</fx:String> <fx:String>Item 4</fx:String> <fx:String>Item 5</fx:String> <fx:String>Item 6</fx:String> <fx:String>Item 7</fx:String> <fx:String>Item 8</fx:String> <fx:String>Item 9</fx:String> <fx:String>Item 10</fx:String> <fx:String>Item 11</fx:String> <fx:String>Item 12</fx:String> <fx:String>Item 13</fx:String> <fx:String>Item 14</fx:String> <fx:String>Item 15</fx:String> <fx:String>Item 16</fx:String> <fx:String>Item 17</fx:String> <fx:String>Item 18</fx:String> <fx:String>Item 19</fx:String> </s:ArrayList> </fx:Declarations> <s:List width="100%" height="100%" dataProvider="{listData}" change="listChangeHandler(event)"/> </s:View>
\
This example skin shows how a developer can swap the layout between vertical and horizontal layouts using the skin states of SplitViewNavigator. These states are automatically toggled as the application resizes itself.
<?xml version="1.0" encoding="utf-8"?> <s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"> <!-- host component --> <fx:Metadata> <a href="HostComponent%28%26quot%3Bspark.components.SplitViewNavigator%26quot%3B%29">HostComponent("spark.components.SplitViewNavigator")</a> </fx:Metadata> <fx:Declarations> <s:Callout id="viewNavigatorCallout" width="200" height="200" /> </fx:Declarations> <!-- states --> <s:states> <s:State name="portrait" /> <s:State name="landscape" /> </s:states> <s:Group id="contentGroup" width="100%" height="100%"> <s:layout.portrait> <s:VerticalLayout gap="1"/> </s:layout.portrait> <s:layout.landscape> <s:HorizontalLayout gap="1"/> </s:layout.landscape> </s:Group> </s:Skin>
\
This example shows how to create an application that hides the first navigator when the device is in a portrait orientation. The application then displays the navigator in a Callout when a button in the ActionBar is clicked.
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" resize="resizeHandler(event)"> <fx:Script> <![CDATA[ import mx.events.ResizeEvent; // Update the applications state based on the orientation of the device protected function resizeHandler(event:ResizeEvent):void { currentState = aspectRatio; } ]]> </fx:Script> <s:states> <s:State name="portrait"/> <s:State name="landscape"/> </s:states> <s:SplitViewNavigator id="splitNavigator" width="100%" height="100%" autoHideFirstViewNavigator="true"> <s:ViewNavigator firstView="views.MasterCategory" width="256" height="100%"/> <s:ViewNavigator firstView="views.DetailView" width="100%" height="100%"> <s:actionContent.portrait> <s:Button id="navigatorButton" label="Show Navigator" click="splitNavigator.showFirstViewNavigatorInPopUp(navigatorButton)" /> </s:actionContent.portrait> </s:ViewNavigator> </s:SplitViewNavigator> </s:Application>
\
This example shows how to use a PersistenceManager with a SplitViewNavigator in to a spark Application. In this use case, the developer is responsible for loading and saving the persisted data when the application launches and deactivates.
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" initialize="initializeHandler(event)"> <fx:Script> <![CDATA[ import spark.managers.PersistenceManager; public var persistenceManager:PersistenceManager; protected function initializeHandler(event:FlexEvent):void { NativeApplication.nativeApplication.addEventListener(Event.DEACTIVATE, onDeactivate); persistenceManager = new PersistenceManager(); persistenceManager.load(); var data:Object = persistenceManager.getProperty("navigatorState"); if (data) splitNavigator.loadViewData(data); } protected function onDeactivate(event:Event):void { persistenceManager.setProperty("navigatorState", splitNavigator.saveViewData()); persistenceManager.save(); } ]]> </fx:Script> <s:SplitViewNavigator width="100%" height="100%"> <s:ViewNavigator firstView="views.MasterCategory" width="256" height="100%"/> <s:ViewNavigator firstView="views.DetailView" width="100%" height="100%"/> </s:SplitViewNavigator> </s:Application>
\
\
\
This feature is primarily targeting the tablet use case, but should still function in a phone or desktop application. When used on a phone, there may not be enough real-estate to use this component effectively, so its use isn't recommended.
\
There are no Cross-Platform considerations for this component.
\
\
\
\
\
\
Wiki: Callout and CalloutButton
Wiki: Flex 4.6
Wiki: SplitViewNavigator