前回は、Full press-drag-release gesture を利用するにあたって、MouseDragEventについてみていきました。今回は、Full press-drag-release gestureを利用して、以下のようなものを作成してみたいと思います。
Paneが2つあり、1つのPaneに配置されているノードをドラッグして、もう1つのPaneに移動するというようなものです。
ノードをドラッグし、マウスをリリースしたときに、もう1つのPane上にある場合は、そのPaneにノードを移動し、それ以外の場合は、もとのPaneに戻っていきます。また、ノードをドラッグ中に、移動先のPane上にきたときは、そのPaneの外枠を赤線で強調表示します。
まずは、以下のようにViewを定義します。
<Scene xmlns:fx="http://javafx.com/fxml" fx:id="scene" width="640" height="480"> <stylesheets> <URL value="@FullPressDragReleaseGestureDemoStyle.css"/> </stylesheets> <StackPane> <GridPane> <StackPane GridPane.columnIndex="0" GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS"> <StackPane fx:id="leftSidePane" styleClass="pane" prefWidth="200" prefHeight="200" maxWidth="-Infinity" maxHeight="-Infinity"> <Circle fx:id="draggableCircle" radius="30"/> </StackPane> </StackPane> <StackPane GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS"> <StackPane fx:id="rightSidePane" styleClass="pane" prefWidth="200" prefHeight="200" maxWidth="-Infinity" maxHeight="-Infinity"/> </StackPane> </GridPane> <Pane fx:id="dragPane" mouseTransparent="true"/> </StackPane> </Scene>
ノードをドラッグするときに、シーンの最前面で移動したいので、ルートをStackPaneで定義し、ノードをドラッグするためのPaneを最前面になるように設定しています。
次に、ドラッグするノードとそのノードを配置するPaneにイベントハンドラを設定します。
ドラッグするノードについては、以下のようにイベントハンドラを設定します。
private Pane sourcePane; private double sourceLayoutX; private double sourceLayoutY; private DragContext context; draggableCircle.setOnMousePressed(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { Circle target = (Circle)event.getSource(); sourcePane = (Pane)target.getParent(); sourcePane.getChildren().remove(target); dragPane.getChildren().add(target); Point2D p = dragPane.sceneToLocal(event.getSceneX(), event.getSceneY()); target.setLayoutX(p.getX() - (event.getX() - target.getCenterX())); target.setLayoutY(p.getY() - (event.getY() - target.getCenterY())); sourceLayoutX = target.getLayoutX(); sourceLayoutY = target.getLayoutY(); context = DragContext.of(target) .atMouseSceneLocation(event.getSceneX(), event.getSceneY()); } }); draggableCircle.setOnDragDetected(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { ((Node)event.getSource()).startFullDrag(); } }); draggableCircle.setOnMouseDragged(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { if (context == null) { return; } context.dragNodeForMouseSceneLocation(event.getSceneX(), event.getSceneY()); } }); draggableCircle.setOnMouseReleased(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { context = null; if (sourcePane == null) { return; } animate((Node)event.getSource(), sourcePane, sourcePane); } });
MOUSE_PRESSEDイベントで、対象のノードを現在配置されているPaneから、ドラッグするためのPaneに移動し、そのPane上で、現在配置されいる位置と同じ位置になるように、ノードの位置を設定しています。
DRAG_DETECTEDイベントで、startFullDragメソッドを呼び出して、Full press-drag-release gestureを開始します。
ノードのドラッグに利用しているDragContextについては、こちらを参照してください。
MOUSE_RELEASEDイベントで、ドラッグ終了時の処理を行っています。ドラッグ終了時に、もう1つのPane上にない場合は、元のPaneにノードを戻します。ドラッグ終了時には、MOUSE_DRAG_RELEASED -> MOUSE_RELEASED -> MOUSE_DRAG_EXITEDの順にイベントが発生しますので、もう1つのPane上にノードを移動する場合は、MOUSE_DRAG_RELEASEDイベントで、MOUSE_PRESSEDで設定したsourcePaneをnullにするようにします。そうすると、MOUSE_RELEASEDイベントで、sourcePaneがnullの場合は、もう1つのPane上に移動したことになりますので、何も処理を行いません。逆に、sourcePaneがnullでない場合は、元のPaneに移動する必要があります。元のPaneへの移動は、animateメソッドでアニメーションで移動するようにしています。animateメソッドは、以下のようになります。
private void animate(final Node node, final Pane sourcePane, final Pane destinationPane) { double toLayoutX = sourceLayoutX + getPaneHGap(sourcePane, destinationPane); double toLayoutY = sourceLayoutY + getPaneVGap(sourcePane, destinationPane); ParallelTransitionBuilder.create() .children( TimelineBuilder.create() .keyFrames( new KeyFrame( Duration.millis(400), new KeyValue(node.layoutXProperty(), toLayoutX) ) ) .build(), TimelineBuilder.create() .keyFrames( new KeyFrame( Duration.millis(400), new KeyValue(node.layoutYProperty(), toLayoutY) ) ) .build() ) .onFinished(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { dragPane.getChildren().remove(node); destinationPane.getChildren().add(node); } }) .build() .play(); } private double getPaneHGap(Pane sourcePane, Pane destinationPane) { return destinationPane.localToScene(destinationPane.getBoundsInParent()).getMinX() - sourcePane.localToScene(sourcePane.getBoundsInParent()).getMinX(); } private double getPaneVGap(Pane sourcePane, Pane destinationPane) { return destinationPane.localToScene(destinationPane.getBoundsInParent()).getMinY() - sourcePane.localToScene(sourcePane.getBoundsInParent()).getMinY(); }
アニメーションでノードを移動させ、アニメーションの終了時に、ドラッグのためのPaneから、移動先のPaneにノードを移動させています。
移動先のノードの位置については、2つのPaneの大きさを同じにしていますので、移動元の位置に対して、2つのPaneの位置のギャップを追加するようにしています。
最後に、ノードを配置するPaneのイベントハンドラについて、以下のように設定します。
EventHandler<MouseDragEvent> mouseDragReleaseHandler = new EventHandler<MouseDragEvent>() { @Override public void handle(MouseDragEvent event) { Pane targetPane = (Pane)event.getSource(); if (sourcePane.equals(targetPane)) { return; } animate((Node)event.getGestureSource(), sourcePane, targetPane); sourcePane = null; } }; EventHandler<MouseDragEvent> mouseDragEnteredHandler = new EventHandler<MouseDragEvent>() { @Override public void handle(MouseDragEvent event) { Pane targetPane = (Pane)event.getSource(); if (sourcePane.equals(targetPane)) { return; } targetPane.getStyleClass().add("target-pane"); } }; EventHandler<MouseDragEvent> mouseDragExitedHandler = new EventHandler<MouseDragEvent>() { @Override public void handle(MouseDragEvent event) { Pane targetPane = (Pane)event.getSource(); if (sourcePane != null && sourcePane.equals(targetPane)) { return; } targetPane.getStyleClass().remove("target-pane"); } }; leftSidePane.setOnMouseDragReleased(mouseDragReleaseHandler); leftSidePane.setOnMouseDragEntered(mouseDragEnteredHandler); leftSidePane.setOnMouseDragExited(mouseDragExitedHandler); rightSidePane.setOnMouseDragReleased(mouseDragReleaseHandler); rightSidePane.setOnMouseDragEntered(mouseDragEnteredHandler); rightSidePane.setOnMouseDragExited(mouseDragExitedHandler);
MOUSE_DRAG_RELEASEDイベントで、移動元のPaneでない場合は、ノードをイベント発生もとのPaneに移動し、ノードがもう1つのPaneに移動したことを表すために、sourcePaneをnullに設定しています。ノードの移動には、先ほど定義したanimateメソッドを利用しています。
もう1つのPane上に、ドラッグ中のノードが入ってきたときに、外枠を赤色で強調表示するために、MOUSE_DRAG_ENTEREDイベントとMOUSE_DRAG_EXITEDイベントで処理を行っています。外枠を強調表示するには、スタイルクラスを変更することで行っています。スタイルは以下のように定義しています。
.root { -fx-base: black; -fx-background-color: linear-gradient(to bottom, derive(-fx-base, 60%), derive(-fx-base, 40%)); -fx-target-border-color: transparent; } .pane { -fx-background-color: -fx-pane-color, linear-gradient(to bottom, derive(-fx-pane-color, 60%), derive(-fx-pane-color, 40%)); -fx-background-insets: 0, 1; -fx-background-radius: 10, 9; -fx-border-color: -fx-target-border-color; -fx-border-width: 5; -fx-border-radius: 10; } .target-pane { -fx-target-border-color: red; } #leftSidePane { -fx-pane-color: purple; } #rightSidePane { -fx-pane-color: green; }
ベースとなるスタイルに、外枠の定義をしておき、MOUSE_DRAG_ENTEREDイベントが発生したときに設定するスタイルで、外枠の色を設定しています。
以上で完了になります。
ノードをドラッグしたときに、他のノードに対して、マウスイベントを発生させたい場合は、DRAG_DETECTEDイベントで、startFullDragメソッドを呼び出して、Full press-drag-release gestureを開始し、MouseDragEventの対象となるイベントに対して、ハンドラを追加すればよいことになります。その場合は、MOUSE_PRESSEDイベントでmouseTransparentプロパティをtrueに設定するようにします。次回は、Platform-supported drag-and-drop gestureについてみていきたいと思います。