Flex 4 Spark Skin ライフサイクル考察 #02

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

スキンの破棄ポリシーについて

前々回のエントリで partRemoved() メソッドについて

スキンパーツのインスタンスが削除されるときに呼び出されます

…と解説しましたが、これはあくまで通常のケースです。
以下の高度な設定方法を用いることによって、的確なスキンの破棄処理を施すことができます。

SkinnableComponent.mx_internal::skinDestructionPolicy プロパティを使う

このプロパティは、名前のとおり「スキンの破棄ポリシー」を示す文字列型のプロパティです。
有効な値は、"never", "auto" の 2 種類で、デフォルト値は、"never" です。

この値を "auto" に変更することで、コンポーネント内部では、コンポーネント自身の Event.ADDED_TO_STAGE ( 以下 "addedToStage" ) イベントと、Event.REMOVED_FROM_STAGE ( 以下 "removedFromStage" ) イベントのハンドリングを有効にします。

たったこれだけの違いですが、"removedFromStage" イベントをハンドリングすることによって、コンポーネントが表示リストから削除されるとき自動的に partRemoved() メソッドを呼び出してくれます。これは、メモリ使用量の削減に繋がる処理です。

また、 "addedToStage" イベントのハンドリングは、表示リストから外されたコンポーネントを復元する場合や、swapChildren(swapElements) のような表示オブジェクト制御メソッド実行時にも使われます。

フローを簡単な図にまとめました。
以下の処理は、skinDestructionPolicy プロパティが "auto" のとき、表示オブジェクト制御処理時に自動的に実行されます。

注意

skinDestructionPolicy プロパティの名前空間が mx_internal で、かつ、デフォルト値が "never" であるということは、不用意に partAdded(), partRemoved() メソッドを実行されないためでもある…とも考えられます。つまり、コンポーネントの設計や仕様を十分に固めず、おまじない感覚で使用すると、逆に火傷する可能性があるということです。

サンプル

addElement(), removeElement(), swapElements() など、表示オブジェクト制御メソッドを実行したときのイベントハンドリングがどのように行われているかを可視化するサンプルを作成しました。( SkinnableComponent.mx_internal::skinDestructionPolicy プロパティを設定していますが、機能を確認できるものではありません )

<?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"
fontSize = "18"
>

<fx:Script>
<![CDATA[
    import mx.core.mx_internal;
    import spark.components.Button;
    import spark.events.IndexChangeEvent;
    protected var button:Button;
    protected var tmpParent:Group;
    protected override function createChildren():void {
        super.createChildren();

        button = leftGroup.addElement( new Button() ) as Button;
        button.width = 150;
        button.height = 150;
        button.addEventListener(MouseEvent.CLICK, buttonClickHandler);
        button.addEventListener(Event.ADDED_TO_STAGE, buttonAddedToStageHandler);
        button.addEventListener(Event.REMOVED_FROM_STAGE, buttonRemovedFromStageHandler);
        button.setStyle("chromeColor", 0x009900);

        button.mx_internal::skinDestructionPolicy = "auto"; //default value is "never".

        leftButton.addEventListener(MouseEvent.CLICK, leftButtonClickHandler);
        rightButton.addEventListener(MouseEvent.CLICK, rightButtonClickHandler);
        bar.addEventListener(IndexChangeEvent.CHANGE, barChangeHandler);
    }
    protected function buttonClickHandler(event:MouseEvent):void {
        if( leftGroup.containsElement(button) ) {
            rightGroup.addElement(button);
        }
        else if( rightGroup.containsElement(button) ) {
            leftGroup.addElement(button);
        }
        else {
            throw new IllegalOperationError("error");
        }
    }
    protected function leftButtonClickHandler(event:MouseEvent):void {
        if( leftGroup.containsElement(button) ) {
            leftGroup.swapElements(button, leftButton);
        }
    }
    protected function rightButtonClickHandler(event:MouseEvent):void {
        if( rightGroup.containsElement(button) ) {
            rightGroup.swapElements(button, rightButton);
        }
    }
    protected function buttonAddedToStageHandler(event:Event):void {
        log.text = getTimer().toString() + " : " + event.type + "\n" + log.text;
    }
    protected function buttonRemovedFromStageHandler(event:Event):void {
        log.text = getTimer().toString() + " : " + event.type + "\n" + log.text;
    }
    protected function barChangeHandler(event:IndexChangeEvent):void {
        var newIndex:int = event.newIndex;
        switch(newIndex) {
            case 0:
                tmpParent.addElement(button);
                break;
            case 1:
                tmpParent = button.parent as Group;
                tmpParent.removeElement(button);
                break;
        }
    }
]]>
</fx:Script>

<s:layout>
    <s:VerticalLayout gap="10" verticalAlign="middle" horizontalAlign="center" />
</s:layout>

<s:HGroup gap="10" width="410" height="200">

    <s:Group id="leftGroup" width="200" height="200">
        <s:Rect left="0" top="0" right="0" bottom="0">
            <s:fill>
                <s:SolidColor color="0x990000" />
            </s:fill>
        </s:Rect>
        <s:Button id="leftButton" width="150" height="150" right="0" bottom="0" />
    </s:Group>

    <s:Group id="rightGroup" width="200" height="200">
        <s:Rect left="0" top="0" right="0" bottom="0">
            <s:fill>
                <s:SolidColor color="0x000099" />
            </s:fill>
        </s:Rect>
        <s:Button id="rightButton" width="150" height="150" right="0" bottom="0" />
    </s:Group>

</s:HGroup>

<s:ButtonBar id="bar" width="200" height="50" requireSelection="true">
    <s:dataProvider>
        <s:ArrayList>
            <fx:String>add</fx:String>
            <fx:String>remove</fx:String>
        </s:ArrayList>
    </s:dataProvider>
</s:ButtonBar>

<s:TextArea id="log" width="410" height="180" />

</s:Application>

出力結果

出力結果は次の通り

This movie requires Flash Player 9