読者です 読者をやめる 読者になる 読者になる

アプリプログラムのソース

ちょいと昨日せこせことアプリの作成をしたので、作成にあたって押さえておきたい点を紹介したいと思います。

スクロールバーを付ける

Flex4からスクロールバーは自前で準備することとなったため以下のようにScrollerを配置します。

<s:Scroller width="100" height="100">
    <s:Group>
        <s:Image source="hoge.jpg" height="200"/>
    </s:Group>
</s:Scroller>

レイアウトクラスを駆使する

Flex4からは便利なレイアウトクラスが登場しました。これで縦、横の制御やタイル状に並べたりすることができます。

<s:Group>
    <s:layout>
        <s:HorizontalLayout verticalAlign="middle"
            horizontalAlign="left"/>
    </s:layout>
    <s:Image source="@Embed('assets/loader.png')"/>
    <s:Label text="Image Loading..." color="0x333333" top="0"/>
</s:Group>

アニメーションクラスの autoCenterTransform を使う

Flex3でなかなか面倒だったのが画像を中心から回転させることでした。今回からは autoCenterTransform プロパティが登場し、画像の中心から回転させることが用意になりました。

<s:Parallel repeatCount="0">
    <s:Rotate angleFrom="0" angleTo="360"
    duration="2000" autoCenterTransform="true" easer="{new Linear()}"/>
</s:Parallel>

ViewNavigatorで表示するたびにViewを毎回作成させない

現在のAdobeさんのサンプルプログラムではページの遷移がある度にページを作成します。
これでは、HTTPServiceなど初期化の際に使用する場合はかなりまずいので以下ように View に destructionPolicy を設定します。

<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx"
        title="Maid Photo Gallery"
        destructionPolicy="none"
        >

メイドフォトギャラリギャラリー

簡単なものですが今回自分が作成したアプリです。

アプリはアンドロイドマーケットにて「メイド」などで検索すれば出てくるかと思います。
ソースは以下になります。
ちょっとでもアプリ作成の参考になればうれしいです。

MaidPhotoHome.mxml

トップに表示されるベージです。ネットから画像情報を取得して、タイル状に画像を配置します。

<?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"
        xmlns:mx="library://ns.adobe.com/flex/mx"
        chromeColor="#FF8CE3"
        creationComplete="view1_creationCompleteHandler(event)"
        overlayControls="false"
        symbolColor="#FF74D0"
        title="Maid Photo Gallery"
        destructionPolicy="none"
        >
    <fx:Script>
        <![CDATA[
            import com.adobe.serialization.json.JSON;
            import flash.net.navigateToURL;
            import mx.events.FlexEvent;
            import mx.rpc.events.ResultEvent;
            import spark.components.Image;
            import spark.events.IndexChangeEvent;
            //初期化
            protected function view1_creationCompleteHandler(event:FlexEvent):void
            {
                myHts.send();
            }
            //HTTPServiceの完了イベント
            private var obj:Object = new Object();
            protected function onResult(e:ResultEvent):void
            {
                obj = JSON.decode(e.result.toString());
                //画像をタイル状に配置していく
                for( var i:int = 0 ; i < obj.length ; i ++){
                    var image:Image = new Image();
                    image.name = "" + i;
                    image.width = 110;
                    image.height = 110;
                    image.filters = [dsf];
                    image.source = obj[i].icon;
                    image.setStyle( "backgroundColor" , 0xffffff );
                    image.addEventListener(Event.COMPLETE , onRender );
                    image.addEventListener(MouseEvent.CLICK , onClick );
                    moetenMaidPhotoGallery.imageArr[i] = image;
                    tg.addElement( image );
                }
            }
            //画像読み込み完了イベント
            private function onRender(e:Event):void
            {
                myAnim.play([e.currentTarget]);
            }
            //画像クリックイベント
            private function onClick( e:Event ):void
            {
                navigator.pushView( DetailView , obj[int( e.currentTarget.name ) ] );
            }
            //ラベルクリックイベント
            private function label1_clickHandler(event:MouseEvent):void
            {
                navigateToURL( new URLRequest ('http://moeten.info/maidcafe/') );
            }
        ]]>
    </fx:Script>
    <fx:Declarations>
        <!--アニメーション-->
        <s:Parallel id="myAnim" duration="1000">
            <s:Fade alphaFrom="0.1" alphaTo="1" duration="1000" />
        </s:Parallel>
        <!--配列-->
        <s:ArrayCollection id="listData" />
        <!--HTTP-->
        <s:HTTPService id="myHts" url="http://moeten.info/maidcafe/json.php" fault="{}" invoke="{}"
                       result="onResult(event)" resultFormat="text" showBusyCursor="true" />
        <s:DropShadowFilter id="dsf" alpha="0.5" distance="0" blurX="4" blurY="4" color="0x000000"/>
    </fx:Declarations>
    <!--レイアウト-->
    <s:layout>
        <s:VerticalLayout horizontalAlign="center" paddingTop="10" paddingLeft="30" paddingRight="20"/>
    </s:layout>
    <!--画像配置用Group-->
    <s:Scroller  id="myS" width="100%" height="100%" verticalCenter="0" horizontalCenter="0">
        <s:Group id="tg" verticalCenter="0" horizontalCenter="0">
            <s:layout>
                <s:TileLayout columnWidth="125" rowHeight="125"
                              verticalGap="14" horizontalGap="14" verticalAlign="middle" horizontalAlign="center"/>
            </s:layout>
        </s:Group>
    </s:Scroller>
    <!--著作情報用Group-->
    <s:Group width="100%" height="20">
        <s:layout>
            <s:BasicLayout/>
        </s:layout>
        <s:Rect width="140" height="100%" topLeftRadiusX="5" topLeftRadiusY="5"  topRightRadiusX="5" topRightRadiusY="5" horizontalCenter="0">
            <s:fill>
                <s:SolidColor color="0xffccff"/>
            </s:fill>
        </s:Rect>
        <s:Label click="label1_clickHandler(event)" color="0x666666" fontSize="12" bottom="5" buttonMode="true" verticalCenter="2" horizontalCenter="0"
                 text="- moeten.info -"/>
    </s:Group>
</s:View>

DetailVew.mxml

画像単体表示用です。画像をSDカードに保存したり、お店の説明を表示することができます。

<?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" destructionPolicy="none"
        xmlns:mx="library://ns.adobe.com/flex/mx" creationComplete="view1_creationCompleteHandler(event)"
        title="{data.title}" viewActivate="refresh()">
    <fx:Script>
        <![CDATA[
            import mx.events.FlexEvent;
            import spark.effects.Wipe;
            import spark.effects.easing.Linear;
            //初期化関数
            protected function view1_creationCompleteHandler(event:FlexEvent):void
            {
                myPB.visible = true;
                startLoader();
            }
            //リフレッシュイベント
            public function refresh():void
            {
            }
            //画像を読み込む
            private var loader:URLLoader;
            protected function startLoader():void{
                var request:URLRequest = new URLRequest( data.image );
                loader = new URLLoader();
                loader.dataFormat = URLLoaderDataFormat.BINARY;
                loader.addEventListener(Event.COMPLETE , onComp );
                loader.load(request);
            }
            //画像読み込み完了
            protected function onComp( e:Event ):void{
                saveBtn.enabled = true;
                image.source = loader.data;
                this.removeElement(myPB);
                myAnim.play([image]);
            }
            //画像を保存
            protected function saveButton_clickHandler(event:MouseEvent):void
            {
                var file:File = File.documentsDirectory.resolvePath( data.image_name );
                var stream:FileStream = new FileStream();
                try {
                    stream.open(file, FileMode.WRITE );
                    var ba:ByteArray = loader.data;
                    stream.writeBytes( ba );
                } catch (e:EOFError) {
                    // ファイル終端の処理
                } catch (e:IOError) {
                    // 入出力エラーの処理
                } finally {
                    // ストリームをクローズする
                    saveBtn.label = "保存しました";
                    saveBtn.enabled = false;
                    stream.close();
                }
            }
            //お店ページへ飛ぶ
            protected function launchButton_clickHandler(event:MouseEvent):void
            {
                navigateToURL(new URLRequest(data.url));
            }
            //お店ページへ飛ぶ
            protected function label1_clickHandler(event:MouseEvent):void
            {
                navigateToURL(new URLRequest(data.url));
            }
            //お店紹介表示
            protected function button1_clickHandler(event:MouseEvent):void
            {
                if( myComment.visible == true ){
                    myComment.visible = false;
                    commentBtn.label = "お店説明";
                }else{
                    commentBtn.label = "閉じる";
                    myComment.visible = true;
                }
            }
        ]]>
    </fx:Script>
    <fx:Declarations>
        <!--アニメーション-->
        <s:Parallel id="myAnim" effectEnd="{commentBtn.visible = true;myTitleBox.visible = true; myTitle.visible = true;}">
            <s:Fade alphaFrom="0" alphaTo="1" duration="2000"/>
            <s:Rotate3D angleXFrom="-10" angleXTo="0" angleYFrom="-40" angleYTo="0" angleZFrom="-10" angleZTo="0" duration="2000" autoCenterTransform="true"/>
        </s:Parallel>
        <s:Parallel id="myMove" repeatCount="0" duration="20000">
            <s:Move xFrom="{this.width}" xTo="{-myTitle.width}" easer="{new Linear()}" duration="20000"/>
        </s:Parallel>
        <s:Parallel id="myShow">
            <s:Fade alphaFrom="0" alphaTo="1" duration="1000"/>
            <s:Rotate3D angleXFrom="90" angleXTo="0" autoCenterTransform="true" duration="1000"/>
        </s:Parallel>
        <s:Parallel id="myRotate3D" repeatCount="0">
            <s:Rotate angleFrom="0" angleTo="360" duration="2000" autoCenterTransform="true" easer="{new Linear()}"/>
        </s:Parallel>
        <s:Sequence id="myFade" repeatCount="0">
            <s:Fade alphaFrom="0.3" alphaTo="1" duration="900"/>
            <s:Fade alphaFrom="1" alphaTo="0.3" duration="900"/>
        </s:Sequence>
    </fx:Declarations>
    <!-- 縦のレイアウト -->
    <s:Scroller width="100%" height="100%" >
        <s:Group contentBackgroundColor="#FFFFFF">
            <s:layout>
                <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
            </s:layout>
            <s:Image id="image" width="100%"/>
        </s:Group>
    </s:Scroller>
    <!--画像読み込み時に表示するローダーアイコン-->
    <s:Group id="myPB" horizontalCenter="0" verticalCenter="0" removedEffect="Fade">
        <s:layout>
            <s:HorizontalLayout verticalAlign="middle" horizontalAlign="left"/>
        </s:layout>
        <s:Group width="55" height="55" clipAndEnableScrolling="true">
            <s:Image alpha="0.5" source="@Embed('assets/loader.png')" creationCompleteEffect="myRotate3D"/>
        </s:Group>
        <s:Label text="Image Loading..." color="0x333333" top="0" creationCompleteEffect="myFade"/>
    </s:Group>
    <!--お店タイトル-->
    <s:Group id="myTitleBox" width="100%" clipAndEnableScrolling="true" visible="false" showEffect="Fade">
        <s:Rect alpha="0.9"
                height="40" width="100%">
            <s:fill >
                <s:SolidColor color="0xffccff"/>
            </s:fill>
        </s:Rect>
        <s:Label id="myTitle" y="0"
                 chromeColor="#563FFF"
                 click="label1_clickHandler(event)" color="0xffffff"
                 fontSize="18"
                 top="12"
                 text="{data.todouhuken+' '+data.category+' '+data.name}" textAlign="center"
                 showEffect="myMove" visible="false"
                 >
            <s:filters>
                <s:GlowFilter id="gf" blurX="4" blurY="4" strength="6"  color="0xff40d4"/>
            </s:filters>
            </s:Label>
    </s:Group>
    <!--お店説明-->
    <s:Group id="myComment"
             visible="false"
             horizontalCenter="0"
             verticalCenter="30"
             width="90%"
             showEffect="{myShow}"
             hideEffect="Fade">
        <s:layout>
            <s:VerticalLayout horizontalAlign="center" paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10"/>
        </s:layout>
        <s:Scroller>
            <s:Group minHeight="300" maxHeight="400" clipAndEnableScrolling="true" width="100%">
            <s:TextArea  lineHeight="24" width="100%" borderColor="0xffccff" color="#000000"
                         alpha="0.9"
                         borderVisible="false"
                         editable="false" fontSize="18" horizontalCenter="0" text="{data.setumei}"
                         selectable="false"/>
            </s:Group>
        </s:Scroller>
        <s:VGroup width="100%" paddingBottom="10" paddingTop="10" gap="20" horizontalAlign="center">
            <s:Button width="100%" height="40" label="公式サイトへGO!"
                      click="launchButton_clickHandler(event)" color="#FFFFFF" horizontalCenter="0"/>
            <s:Button id="saveBtn" width="100%" height="40" label="画像保存"
                      click="saveButton_clickHandler(event)" color="#FFFFFF" cornerRadius="7"
                      enabled="false" horizontalCenter="0"/>
        </s:VGroup>
    </s:Group>
    <!--説明呼び出しボタン-->
    <s:Button mouseDownEffect="Fade" id="commentBtn" right="20" top="60" height="54" label="お店説明" alpha="0.9"
              chromeColor="#FFFFFF" click="button1_clickHandler(event)"
              visible="false" showEffect="myShow"
              color="#008AC3"
              cornerRadius="30" fontSize="18"/>
</s:View>

参考リンク

Adobe Labs - Adobe AIR Launchpad
AIR2.5のサンプルプログラムとソースを取得できます。
Flex SDK 4.5 “Hero”を使って、お手軽Androidアプリ開発 | ClockMaker Blog
AIR2.5のサンプルプログラムとソースがダウンロードできます。
Mobile Application - Flex SDK - Adobe Open Source
AIR2.5の説明
View and ViewNavigator - Flex SDK - Adobe Open Source
ViewNavigatorの説明。
spark.effects.Animate - Adobe® Flex® 4.1 リファレンスガイド
今回から扱いが少し変わったアニメーションの説明
mx.states.AddItems - Adobe® Flex® 4.1 リファレンスガイド
stateの説明。Flex4からはstateが重要視されてます。
Flex | Blog for Nayan
viewStackの使い方の説明