自前 Dragger #03
前回に引き続き、自前ドラック機構を用いた Flex の Tips を解説します。
お題「回転ボタン」
描画ツールの変形機能などに存在する回転ボタンですが、Flex の Panel コンポーネントを拡張して、Panel 自身がクルクル回る機能を実装してみましょう。
Panel の回転は、行列 (Matrix) を用います。マウスが移動するたびに Panel の中心座標とマウス座標の 2 点から角度を算出し、この値を基に行列変換処理を行い、Panel の transform.matrix プロパティにセットすることで実現可能です。
実装コード (RotatablePanel.as)
package jp.classmethod.sample { import flash.events.MouseEvent; import flash.geom.Matrix; import flash.geom.Point; import spark.components.Button; import spark.components.Panel; public class RotatablePanel extends Panel { protected const PI:Number = Math.PI; [SkinPart] public var rotateButton :Button; protected var startCenterX :int; protected var startCenterY :int; protected var startRadians :Number; protected var startRotateMatrix :Matrix; public function RotatablePanel() { super(); minWidth = 200; minHeight = 200; maxWidth = 500; maxHeight = 500; setStyle("skinClass", RotatablePanelSkin); } protected override function partAdded(partName:String, instance:Object):void { super.partAdded(partName, instance); if(instance == rotateButton) { rotateButton.addEventListener(MouseEvent.MOUSE_DOWN, rotateButtonMouseDownHandler); } } protected function rotateButtonMouseDownHandler(event:MouseEvent):void { var gp:Point; var dx:int; var dy:int startRotateMatrix = transform.matrix; gp = localToGlobal( new Point(width * 0.5, height * 0.5) ); startCenterX = gp.x; startCenterY = gp.y; dx = stage.mouseX - startCenterX; dy = stage.mouseY - startCenterY; startRadians = Math.atan2(dy, dx); stage.addEventListener(MouseEvent.MOUSE_MOVE, stageRotateMouseMoveHandler); stage.addEventListener(MouseEvent.MOUSE_UP, stageRotateMouseUpHandler); } protected function stageRotateMouseMoveHandler(event:MouseEvent):void { var dx :int = stage.mouseX - startCenterX; var dy :int = stage.mouseY - startCenterY; var angle :Number = Math.atan2(dy, dx) - startRadians; var m :Matrix = startRotateMatrix.clone(); var lp :Point = globalToLocal( new Point(startCenterX, startCenterY) ); rotateAroundInternalPoint(m, width * 0.5, height * 0.5, angle); transform.matrix = m; event.updateAfterEvent(); } protected function stageRotateMouseUpHandler(event:MouseEvent):void { stage.removeEventListener(MouseEvent.MOUSE_MOVE, stageRotateMouseMoveHandler); stage.removeEventListener(MouseEvent.MOUSE_UP, stageRotateMouseUpHandler); } protected function rotateAroundInternalPoint(m:Matrix, x:Number, y:Number, radians:Number):void { var p:Point; p = new Point(x, y); p = m.transformPoint(p); m.tx = m.tx - p.x; m.ty = m.ty - p.y; m.rotate(radians); m.tx = m.tx + p.x; m.ty = m.ty + p.y; } } }
実装コード (RotatablePanelSkin.mxml)
<?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" mouseEnabled = "false" minWidth = "131" minHeight = "127" alpha.disabled = "0.5" alpha.disabledWithControlBar = "0.5" > <fx:Metadata> <![CDATA[ [HostComponent("jp.classmethod.sample.RotatablePanel")] ]]> </fx:Metadata> <s:states> <s:State name="normal" /> <s:State name="disabled" /> <s:State name="normalWithControlBar" stateGroups="withControls" /> <s:State name="disabledWithControlBar" stateGroups="withControls" /> </s:states> <s:Group left="0" right="0" top="0" bottom="0"> <s:Group id="topGroupMask" left="1" top="1" right="1" bottom="1"> <s:Rect id="topMaskRect" left="0" top="0" right="0" bottom="0"> <s:fill> <s:SolidColor alpha="0" /> </s:fill> </s:Rect> </s:Group> <s:Group id="bottomGroupMask" left="1" top="1" right="1" bottom="1" includeIn="normalWithControlBar, disabledWithControlBar"> <s:Rect id="bottomMaskRect" left="0" top="0" right="0" bottom="0"> <s:fill> <s:SolidColor alpha="0" /> </s:fill> </s:Rect> </s:Group> <s:Rect id="border" left="0" right="0" top="0" bottom="0" > <s:stroke> <s:SolidColorStroke id="borderStroke" weight="1" /> </s:stroke> </s:Rect> <s:Rect id="background" left="1" top="1" right="1" bottom="1"> <s:fill> <s:SolidColor id="backgroundFill" color="#FFFFFF" /> </s:fill> </s:Rect> <s:Group id="contents" left="1" right="1" top="1" bottom="1"> <s:layout> <s:VerticalLayout gap="0" horizontalAlign="justify" /> </s:layout> <s:Group id="topGroup" mask="{topGroupMask}"> <s:Rect id="tbFill" left="0" right="0" top="0" bottom="1"> <s:fill> <s:LinearGradient rotation="90"> <s:GradientEntry color="0xE2E2E2" /> <s:GradientEntry color="0xD9D9D9" /> </s:LinearGradient> </s:fill> </s:Rect> <s:Rect id="tbHilite" left="0" right="0" top="0" bottom="0"> <s:stroke> <s:LinearGradientStroke rotation="90" weight="1"> <s:GradientEntry color="0xEAEAEA" /> <s:GradientEntry color="0xD9D9D9" /> </s:LinearGradientStroke> </s:stroke> </s:Rect> <s:Rect id="tbDiv" left="0" right="0" height="1" bottom="0"> <s:fill> <s:SolidColor color="0xC0C0C0" /> </s:fill> </s:Rect> <s:Label id="titleDisplay" left="9" right="3" top="1" bottom="0" minHeight="30" maxDisplayedLines="1" verticalAlign="middle" textAlign="start" fontWeight="bold" /> </s:Group> <s:Group id="contentGroup" width="100%" height="100%" minWidth="0" minHeight="0" /> </s:Group> <s:Button id="rotateButton" width="30" height="30" bottom="0" right="0" buttonMode="true" chromeColor="0x000099" /> </s:Group> </s:Skin>
実装コード (Main.mxml)
<?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" xmlns:cm = "jp.classmethod.sample.*" backgroundAlpha = "1" backgroundColor = "0x333333" > <cm:RotatablePanel width="200" height="200" verticalCenter="0" horizontalCenter="-150"> <cm:layout> <s:VerticalLayout paddingLeft="10" paddingTop="10" paddingRight="10" paddingBottom="40" /> </cm:layout> <s:Button width="100%" height="100%" /> <s:Button width="100%" height="100%" /> <s:Button width="100%" height="100%" /> <s:Button width="100%" height="100%" /> </cm:RotatablePanel> <cm:RotatablePanel width="200" height="200" verticalCenter="0" horizontalCenter="150"> <cm:layout> <s:VerticalLayout paddingLeft="10" paddingTop="10" paddingRight="10" paddingBottom="40" /> </cm:layout> <s:Button width="100%" height="100%" /> <s:Button width="100%" height="100%" /> <s:Button width="100%" height="100%" /> <s:Button width="100%" height="100%" /> </cm:RotatablePanel> </s:Application>
出力結果
出力結果は次の通り
[SWF]http://publick-blog.s3.amazonaws.com/wp-content/uploads/2011/07/DragLesson03.swf,640,480[/SWF]