作成日
7 December 2011
本記事では、Flex 4.6 SDKから採用された新しいコンポーネントについて解説します。なお、Flash Builder 4.6にバンドルされている標準のFlex SDK(バージョン4.6.0.23201)を対象にしています。
前編ではSpinnerList/DateSpinner/ToggleSwitchコンポーネントを解説しました。後編となる本記事では、Callout/SplitViewNavigatorコンポーネントを解説します。
Calloutは、吹き出しのようなポップアップを表示するコンポーネントです。ComboBoxコンポーネントがドロップダウンリストを表示するときに内部的に使用しているPopUpAnchorコンポーネントと同じように機能します。さらに、そのオーナー(呼び出し元)に対して、吹き出しを相対的に配置することが可能で、オーナーへの方向を表示する矢印(吹き出しのしっぽ)のスキンパーツ追加オプションも兼ね備えているSkinnablePopUpContainerのサブクラスです。
MXMLでCalloutを使用するときには、基本的に<fx:Declarations>タグ内に記述して、必要とするまで表示リストから外しておきます。表示するときには次のように open()メソッドを実行します。
callout.open(owner, modal);
//owner - 呼び出し元コンポーネントのインスタンス
//modal - モーダルの必要/不要を指定するブール値
Calloutの表示位置は、verticalPositionプロパティと、horizontalPositionプロパティを使用します。指定できる値は、ArrowDirectionクラスの定数("before"、"start"、"middle"、"end"、"after")のいずれかで、組み合わせによって25通りの表示パターンを実現できます。サンプルCalloutSample.fxpでは、これらの表示パターンを確認できます。

verticalPosition / horizontalPosition | before | start | middle | end | after |
before | 無し | 下 | 下 | 下 | 無し |
start | 右 | 左 | 上 | 右 | 左 |
middle | 右 | 左 | 無し | 右 | 左 |
end | 右 | 左 | 下 | 右 | 左 |
after | 無し | 上 | 上 | 上 | 無し |
Callout を閉じるときには次のように close () メソッドを実行します。
callout.close(commit, data);
//commit - 戻りデータがアプリケーションによってコミットされる必要があるかどうかを指定するブール値(PopUpEventオブジェクトのcommitプロパティに書き込まれる)
//data - アプリケーションに返すデータ(PopUpEventオブジェクトのdataプロパティに書き込まれる)
次のコードは、Calloutコンポーネントを使ったシンプルなサンプルです(サンプル:CalloutSample.fxp)。
ソースコード:CalloutSample/src/views/CalloutSampleView01.mxml
<?xml version="1.0" encoding="utf-8"?>
<!---
Callout Sample 01
-->
<s:View
xmlns:fx = "http://ns.adobe.com/mxml/2009"
xmlns:s = "library://ns.adobe.com/flex/spark"
xmlns:c = "components.*"
title = "Callout Sample 01"
>
<s:actionContent>
<s:Button label="next" click="navigator.pushView(CalloutSampleView02)" />
</s:actionContent>
<fx:Declarations>
<c:CustomCallout
id = "callout"
verticalPosition = "after"
horizontalPosition = "middle"
/>
</fx:Declarations>
<fx:Script>
<![CDATA[
import spark.events.PopUpEvent;
protected function textInputRemovedFromStageHandler(event:Event):void {
textInput.removeEventListener(Event.REMOVED_FROM_STAGE,
textInputRemovedFromStageHandler);
callout.setVisible(false, true);
}
protected function textInputFocusInHandler(event:FocusEvent):void {
textInput.addEventListener(Event.REMOVED_FROM_STAGE,
textInputRemovedFromStageHandler);
callout.addEventListener(PopUpEvent.CLOSE, calloutCloseHandler);
callout.open(textInput, true);
}
protected function calloutCloseHandler(event:PopUpEvent):void {
callout.removeEventListener(PopUpEvent.CLOSE, calloutCloseHandler);
textInput.removeEventListener(Event.REMOVED_FROM_STAGE,
textInputRemovedFromStageHandler);
textInput.text = event.data;
}
]]>
</fx:Script>
<s:TextInput
id = "textInput"
verticalCenter = "0"
horizontalCenter = "0"
focusIn = "textInputFocusInHandler(event)"
prompt = "Callout owner"
/>
</s:View>
CustomCallout.mxml
<?xml version="1.0" encoding="utf-8"?>
<!---
Custom Callout
-->
<s:Callout
xmlns:fx = "http://ns.adobe.com/mxml/2009"
xmlns:s = "library://ns.adobe.com/flex/spark"
>
<s:List id="list" change="close(true, list.selectedItem)">
<s:dataProvider>
<s:ArrayList>
<fx:String>AAAAA</fx:String>
<fx:String>BBBBB</fx:String>
<fx:String>CCCCC</fx:String>
<fx:String>DDDDD</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:List>
</s:Callout>

オーナーがボタン系コンポーネントの場合、表示するときにDropDownControllerクラスを利用すると簡単に実装できて便利です。次のコードは、ButtonをオーナーとしてCalloutを表示するサンプルです。
ソースコード:CalloutSample/src/views/CalloutSampleView02.mxml
<?xml version="1.0" encoding="utf-8"?>
<!---
Callout Sample 02
-->
<s:View
xmlns:fx = "http://ns.adobe.com/mxml/2009"
xmlns:s = "library://ns.adobe.com/flex/spark"
title = "Callout Sample 01"
>
<fx:Declarations>
<s:Callout
id = "callout"
verticalPosition = "{verticalPositionButtonBar.selectedItem}"
horizontalPosition = "{horizontalPositionButtonBar.selectedItem}"
>
<s:layout>
<s:HorizontalLayout
paddingLeft = "5"
paddingTop = "5"
paddingRight = "5"
paddingBottom = "5"
/>
</s:layout>
<s:Button width="100" label="ok" />
<s:Button width="100" label="cancel" />
</s:Callout>
</fx:Declarations>
<fx:Script>
<![CDATA[
import spark.components.supportClasses.DropDownController;
import spark.events.DropDownEvent;
protected var dropDownController:DropDownController;
protected override function createChildren():void {
super.createChildren();
dropDownController = new DropDownController();
dropDownController.closeOnResize = false;
dropDownController.addEventListener(DropDownEvent.OPEN,
dropDownControllerOpenHandler);
dropDownController.addEventListener(DropDownEvent.CLOSE,
dropDownControllerCloseHandler);
dropDownController.openButton = openButton;
if (callout != null) {
dropDownController.dropDown = callout;
}
openButton.addEventListener(MouseEvent.CLICK, openButtonClickaHandler);
}
protected function dropDownControllerOpenHandler(event:DropDownEvent):void {
openButton.addEventListener(Event.REMOVED_FROM_STAGE,
openButtonRemovedFromStageHandler);
callout.open(openButton, false);
}
protected function dropDownControllerCloseHandler(event:DropDownEvent):void {
openButton.removeEventListener(Event.REMOVED_FROM_STAGE,
openButtonRemovedFromStageHandler);
callout.close();
}
protected function openButtonRemovedFromStageHandler(event:Event):void {
callout.setVisible(false, true);
dropDownController.closeDropDown(false);
}
protected function openButtonClickaHandler(event:MouseEvent):void {
dropDownController.openDropDown();
}
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout
paddingLeft = "10"
paddingTop = "10"
paddingRight = "10"
paddingBottom = "10"
/>
</s:layout>
<s:Label text="Vertical Position" />
<s:ButtonBar id="verticalPositionButtonBar" requireSelection="true">
<s:dataProvider>
<s:ArrayList>
<fx:String>before</fx:String>
<fx:String>start</fx:String>
<fx:String>middle</fx:String>
<fx:String>end</fx:String>
<fx:String>after</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:ButtonBar>
<s:Rect height="10" /><!-- spacer -->
<s:Label text="Horizontal Position" />
<s:ButtonBar id="horizontalPositionButtonBar" requireSelection="true">
<s:dataProvider>
<s:ArrayList>
<fx:String>before</fx:String>
<fx:String>start</fx:String>
<fx:String>middle</fx:String>
<fx:String>end</fx:String>
<fx:String>after</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:ButtonBar>
<s:Rect height="10" /><!-- spacer -->
<s:Group width="100%" height="100%">
<s:Button
id = "openButton"
verticalCenter = "0"
horizontalCenter = "0"
label = "Callout owner"
/>
</s:Group>
</s:View>

先述の処理は、CalloutButtonコンポーネントを使用することで、コードをさらにシンプルにすることができます。次のコードは、CalloutSampleView02.mxmlを簡略化したCalloutButtonのサンプルです。
ソースコード:CalloutSample/src/views/CalloutSampleView03.mxml
<?xml version="1.0" encoding="utf-8"?>
<!---
Callout Sample 03
-->
<s:View
xmlns:fx = "http://ns.adobe.com/mxml/2009"
xmlns:s = "library://ns.adobe.com/flex/spark"
title = "Callout Sample 03"
>
<s:actionContent>
<s:Button label="next" click="navigator.pushView(CalloutSampleView01)" />
</s:actionContent>
<s:layout>
<s:VerticalLayout
paddingLeft = "10"
paddingTop = "10"
paddingRight = "10"
paddingBottom = "10"
/>
</s:layout>
<s:Label text="Vertical Position" />
<s:ButtonBar id="verticalPositionButtonBar" requireSelection="true">
<s:dataProvider>
<s:ArrayList>
<fx:String>before</fx:String>
<fx:String>start</fx:String>
<fx:String>middle</fx:String>
<fx:String>end</fx:String>
<fx:String>after</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:ButtonBar>
<s:Rect height="10" /><!-- spacer -->
<s:Label text="Horizontal Position" />
<s:ButtonBar id="horizontalPositionButtonBar" requireSelection="true">
<s:dataProvider>
<s:ArrayList>
<fx:String>before</fx:String>
<fx:String>start</fx:String>
<fx:String>middle</fx:String>
<fx:String>end</fx:String>
<fx:String>after</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:ButtonBar>
<s:Rect height="10" /><!-- spacer -->
<s:Group width="100%" height="100%">
<s:CalloutButton
id = "callout"
verticalCenter = "0"
horizontalCenter = "0"
verticalPosition = "{verticalPositionButtonBar.selectedItem}"
horizontalPosition = "{horizontalPositionButtonBar.selectedItem}"
label = "Callout owner"
>
<s:calloutLayout>
<s:HorizontalLayout
paddingLeft = "5"
paddingTop = "5"
paddingRight = "5"
paddingBottom = "5"
/>
</s:calloutLayout>
<s:calloutContent>
<s:Button width="100" label="ok" />
<s:Button width="100" label="cancel" />
</s:calloutContent>
</s:CalloutButton>
</s:Group>
</s:View>
オーナーがボタンの場合の実装方法として2通り紹介しました。最初のDropDownControllerクラスを利用した方法は、CalloutButtonコンポーネントの仕組みを自前で実装しています。通常は、CalloutButtonコンポーネントを使えば特に問題ありませんが、その内部的な仕組みも知っておいた方が、いざというときに応用が効くので覚えておきましょう。
SplitViewNavigatorコンポーネントは、複数のViewNavigatorコンポーネントを管理するコンテナです。デバイスの画面の向きが、portrait(縦長)状態、landscape(横長)状態のときに、それぞれ異なった画面分割方法を提供します。
SplitViewNavigatorコンポーネントの基本的な使用方法は、2つのViewNavigatorコンポーネントを配置して、autoHideFirstViewNavigatorプロパティ値にtrueを指定します。autoHideFirstViewNavigatorプロパティ値と、2つのViewNavigatorコンポーネントとの関係は図4の通りです(1つ目と2つ目とで、表示状態が異なります)。

次のコードは、SplitViewNavigatorコンポーネントを使った、シンプルなサンプルです(サンプル:SplitViewNavigatorSample.fxp)
ソースコード:SplitViewNavigatorSample/src/SplitViewNavigatorSample.mxml
<?xml version="1.0" encoding="utf-8"?>
<!---
SplitViewNavigator Sample
-->
<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;
protected function resizeHandler(event:ResizeEvent):void {
currentState = aspectRatio;
}
protected function clickHandler(event:MouseEvent):void {
sn.showFirstViewNavigatorInPopUp(nb);
}
]]>
</fx:Script>
<s:states>
<s:State name="portrait"/>
<s:State name="landscape"/>
</s:states>
<s:SplitViewNavigator
id = "sn"
width = "100%"
height = "100%"
autoHideFirstViewNavigator = "true"
>
<s:ViewNavigator width="256" height="100%" firstView="views.MenuView" />
<s:ViewNavigator width="100%" height="100%" firstView="views.DetailView">
<s:actionContent.portrait>
<s:Button id="nb" label="show menu" click="clickHandler(event)" />
</s:actionContent.portrait>
</s:ViewNavigator>
</s:SplitViewNavigator>
</s:Application>
ソースコード:SplitViewNavigatorSample/src/views/MenuView.mxml
<?xml version="1.0" encoding="utf-8"?>
<!---
Menu View
-->
<s:View
xmlns:fx = "http://ns.adobe.com/mxml/2009"
xmlns:s = "library://ns.adobe.com/flex/spark"
title = "Menu View"
>
<fx:Script>
<![CDATA[
import spark.components.SplitViewNavigator;
import spark.components.ViewNavigator;
import spark.events.IndexChangeEvent;
protected function listChangeHandler(event:IndexChangeEvent):void {
var sn :SplitViewNavigator = navigator.parentNavigator as SplitViewNavigator;
var vn :ViewNavigator = sn.getViewNavigatorAt(1) as ViewNavigator;
vn.pushView(DetailView, list.selectedItem);
}
]]>
</fx:Script>
<fx:Declarations>
…
</s:ArrayList>
</fx:Declarations>
<s:List
id="list" width="100%" height="100%"
dataProvider="{listData}" change="listChangeHandler(event)" />
</s:View>
ソースコード:SplitViewNavigatorSample/src/views/DetailView.mxml
<?xml version="1.0" encoding="utf-8"?>
<!---
Detail View
-->
<s:View
xmlns:fx = "http://ns.adobe.com/mxml/2009"
xmlns:s = "library://ns.adobe.com/flex/spark"
title = "{new ObjectProxy(data).label}"
>
<fx:Script>
<![CDATA[
import mx.utils.ObjectProxy;
]]>
</fx:Script>
<s:Rect width="100%" height="100%">
<s:fill>
<s:SolidColor color="0x333333" />
</s:fill>
</s:Rect>
<s:Image
left = "0"
top = "0"
right = "0"
bottom = "0"
source = "{new ObjectProxy(data).data}"
fillMode = "clip"
smooth = "true"
/>
</s:View>
autoHideFirstViewNavigatorプロパティ値がtrueで、かつデバイスの画面の向きがportrait状態のときに、1つ目のViewNavigatorは自動的に隠れますが、showFirstViewNavigatorInPopUp()メソッドを使ってCallout状態で表示することも可能です。



以上が Flex 4.6 SDK から採用された新しいモバイルコンポーネントについての紹介でした。CalloutとSpritViewNavigatorコンポーネントの実装は、コードを見ていただくと分かると思いますが、少しだけ大変です。ですが、これらのチュートリアルを確認し慣れることによって、簡単にFlexモバイルアプリケーションに組み込むことができるでしょう。