前回は、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についてみていきたいと思います。