OrangeBlock
is an orange block with text inside. It is implemented as a StackPane
that contains text on top of a rectangle. (This approach is demonstrated in the documentation for StackPane.)
I've placed an OrangeBlock
at coordinates (100, 80) and am now trying to make it travel smoothly to some target coordinates. Unfortunately I get a nasty bump in my path:
For some reason the coordinates in the PathElement
s are interpreted relative to the orange block.
Why is this? And how can I make my OrangeBlock
travel along a path with absolute coordinates? Minimal working example below.
import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
public class PathTransitionExample extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
OrangeBlock block = new OrangeBlock(60, 40);
block.relocate(100, 80);
root.getChildren().add(block);
PathTransition transition = newPathTransitionTo(block, 460, 320);
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
transition.play();
}
private static PathTransition newPathTransitionTo(OrangeBlock block,
double toX, double toY) {
double fromX = block.getLayoutX();
double fromY = block.getLayoutY();
Path path = new Path();
path.getElements().add(new MoveTo(fromX, fromY));
path.getElements().add(new LineTo(toX, toY));
PathTransition transition = new PathTransition();
transition.setPath(path);
transition.setNode(block);
transition.setDelay(Duration.seconds(1));
transition.setDuration(Duration.seconds(2));
return transition;
}
public static void main(String[] args) {
launch(args);
}
private static class OrangeBlock extends StackPane {
public OrangeBlock(int width, int height) {
Rectangle rectangle = new Rectangle(width, height, Color.ORANGE);
Text text = new Text("Block");
getChildren().addAll(rectangle, text);
}
}
}
Both @jdub1581 and @Lorand have given valid points:
translateXProperty()
andtranslateYProperty()
.I'll add one more thing:
Let's add a
pathScene
to the group:This will be our scene now (I've added two labels with the coordinates of the origin and end of the path for clarity):
Now we need to determine the local path, so we'll change
pathScene
elements to local coordinates of the block, and translate it to its center:As @Lorand also mentioned, this should be done after the stage is shown to compute the size of the block.
Now we can create the transition and play it.
Finally, this is all the code we need to make the block follow the desired path:
Note that this solution is equivalent to the one given by @Lorand.
If we monitorize the X, Y translate properties of the block, these go from (0,0) to (360,240), which are just the relative ones on the global path.
So Basically,
the relocate(x,y) method sets the layout x/y values... the Transition uses the translateX/Y values...
I could be wrong but I believe that when either value gets invalidated the scene runs a "layout" pass on the nodes in scene.
When this happens it tries to set the node to it's known layoutX/Y values, which are set when you call relocate(x,y) (layout values are 0,0 by default).
This causes the node to be drawn in "layoutPosition" then "pathPosition" within the transition at each step, causing jitters and the node to be offset from where it should be.
Not going to post pics, But my first result was like your first post, changing it to the above code gave me the second post result.
Usually it is always best to avoid "layout" values on a dynamic (moving) object for these reasons. If you like the convenience of the relocate method, I'd implement your own setting the translate values instead.
Cheers :)
EDIT: I edited some code to print what happens as the transition is running so you can see what happens in your original version..
I debugged the JavaFX code out of curiosity. Seems like you are out of luck with a proper solution. Here's what happens:
The PathTransition code has a method interpolate(double frac) which includes:
The impl_getPivotX() and impl_getPivotY() methods contain this:
So the PathTransition always uses the center of your node for the calculation. In other words this works with e. g. a Circle node, but not with e. g. a Rectangle node. Moreover you need the layoutBounds, so the PathTransition must be created after the bounds were made available.
You can see in the PathTransition code that the calculations are all relative and already involve the layout position. So in your lineTo you have to consider this.
Worth noting is that the LineTo class has a method setAbsolut(boolean). However it doesn't solve your problem.
So my solution to your problem would be
This works for me (I added a Rectangle shape to identify the proper bounds visually):
edit: another solution would be to use this instead of MoveTo and LineTo:
Note: You still have to create the PathTransition after the primaryStage was created.
edit: here's another example with the block moving to the position of the mouse-click:
The solution I'm using now is to simply offset
layoutX
andlayoutY
of thePath
in the opposite direction.Inserting a call to this method immediately after the
Path
instantiation fixes the problem.I'm not really satisfied with this solution. I don't understand why
layoutX
andlayoutY
need to be involved at all. Is there a neater way?You're using relocate function to locate your Block. relocate function makes computation on x and y for locating your object. If you used setLayoutX to locate Block and then use getLayoutX, this problem might not be happened. Same explanations is valid for y property.
You can find some informations about your problem in here: http://docs.oracle.com/javase/8/javafx/api/javafx/scene/Node.html#layoutXProperty