使用StackPane对象的绝对坐标创建路径转换

时间:2014-12-26 16:26:53

标签: javafx

OrangeBlock是一个带有文字的橙色块。它实现为StackPane,其中包含矩形顶部的文本。 (这种方法在the documentation for StackPane中得到证明。)

我已经在坐标(100,80)处放置了OrangeBlock,现在正试图让它平稳地移动到某些目标坐标。不幸的是,我在路上遇到了一个令人讨厌的问题:

Bumpy PathTransition

由于某种原因,PathElement s中的坐标是相对于橙色块解释的。

这是为什么?如何沿着具有绝对坐标的路径进行OrangeBlock旅行?下面的最小工作示例。

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);
        }
    }
}

5 个答案:

答案 0 :(得分:7)

出于好奇,我调试了JavaFX代码。似乎你运气不好找到合适的解决方案。这里发生了什么:

PathTransition代码有一个方法interpolate(double frac),其中包括:

cachedNode.setTranslateX(x - cachedNode.impl_getPivotX());
cachedNode.setTranslateY(y - cachedNode.impl_getPivotY());

impl_getPivotX()和impl_getPivotY()方法包含:

public final double impl_getPivotX() {
    final Bounds bounds = getLayoutBounds();
    return bounds.getMinX() + bounds.getWidth()/2;
}

public final double impl_getPivotY() {
    final Bounds bounds = getLayoutBounds();
    return bounds.getMinY() + bounds.getHeight()/2;
}

因此PathTransition始终使用节点的中心进行计算。换句话说,这适用于e。 G。一个Circle节点,但没有e。 G。一个Rectangle节点。此外,您需要layoutBounds,因此必须在边界可用后创建PathTransition。

您可以在PathTransition代码中看到计算都是相对的,并且已经涉及布局位置。所以在你的行中你必须考虑这一点。

值得注意的是LineTo类有一个方法setAbsolut(boolean)。但是它并没有解决你的问题。

所以我的问题解决方案是

  • 在主要阶段可见后创建PathTransition
  • 修改moveTo和lineTo参数

这对我有用(我添加了一个Rectangle形状来直观地识别正确的边界):

public class PathTransitionExampleWorking2 extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {

        Group root = new Group();

        Rectangle rect = new  Rectangle( 100, 80, 460-100+60, 320-80+40);
        root.getChildren().add(rect);

        OrangeBlock block = new OrangeBlock(60, 40);
        block.relocate( 100, 80);

        root.getChildren().add(block);

        primaryStage.setScene(new Scene(root, 600, 400));
        primaryStage.show();

        // layout bounds are used in path transition => PathTransition creation must happen when they are available
        PathTransition transition = newPathTransitionTo(block, 460, 320);
        transition.play();
    }

    private static PathTransition newPathTransitionTo(OrangeBlock block, double toX, double toY) {

        double fromX = block.getLayoutBounds().getWidth() / 2;
        double fromY = block.getLayoutBounds().getHeight() / 2;

        toX -= block.getLayoutX() - block.getLayoutBounds().getWidth() / 2;
        toY -= block.getLayoutY() - block.getLayoutBounds().getHeight() / 2;

        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);
        }
    }
}

编辑:另一个解决方案是使用它而不是MoveTo和LineTo:

public static class MoveToAbs extends MoveTo {

    public MoveToAbs( Node node) {
        super( node.getLayoutBounds().getWidth() / 2, node.getLayoutBounds().getHeight() / 2);
    }

}

public static class LineToAbs extends LineTo {

    public LineToAbs( Node node, double x, double y) {
        super( x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
    }

}

注意:在创建primaryStage之后,您仍然需要创建PathTransition。

编辑:这是另一个示例,其中块移动到鼠标单击的位置:

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);

        Label label = new Label( "Click on scene to set destination");
        label.relocate(0, 0);
        root.getChildren().add(label);

        Scene scene = new Scene(root, 600, 400);

        scene.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<Event>() {

            PathTransition transition;

            {
                transition = new PathTransition();
                transition.setNode(block);
                transition.setDuration(Duration.seconds(2));

            }

            @Override
            public void handle(Event event) {

                transition.stop();

                setPositionFixed(block.getLayoutX() + block.getTranslateX(), block.getLayoutY() + block.getTranslateY());

                double x = ((MouseEvent) event).getX();
                double y = ((MouseEvent) event).getY();

                Path path = new Path();
                path.getElements().add(new MoveToAbs( block));
                path.getElements().add(new LineToAbs( block, x, y));

                transition.setPath(path);
                transition.play();

            }

            private void setPositionFixed( double x, double y) {
                block.relocate(x, y);
                block.setTranslateX(0);
                block.setTranslateY(0);
            }

        });

        primaryStage.setScene( scene);
        primaryStage.show();

        PathTransition transition = newPathTransitionTo(block, 460, 320);
        transition.play();

    }

    private static PathTransition newPathTransitionTo(OrangeBlock block, double toX, double toY) {

        Path path = new Path();
        path.getElements().add(new MoveToAbs( block));
        path.getElements().add(new LineToAbs( block, 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);
        }
    }

    public static class MoveToAbs extends MoveTo {

        public MoveToAbs( Node node) {
            super( node.getLayoutBounds().getWidth() / 2, node.getLayoutBounds().getHeight() / 2);
        }

    }

    public static class LineToAbs extends LineTo {

        public LineToAbs( Node node, double x, double y) {
            super( x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
        }

    }
}

答案 1 :(得分:3)

我现在使用的解决方案是在相反的方向上简单地偏移layoutX的{​​{1}}和layoutY

Path

private static void offsetPathForAbsoluteCoords(Path path, OrangeBlock block) { Node rectangle = block.getChildren().iterator().next(); double width = rectangle.getLayoutBounds().getWidth(); double height = rectangle.getLayoutBounds().getHeight(); path.setLayoutX(-block.getLayoutX() + width / 2); path.setLayoutY(-block.getLayoutY() + height / 2); } 实例化后立即插入对此方法的调用可以解决问题。

enter image description here

我对此解决方案并不满意。我不明白为什么PathlayoutX需要参与其中。有更简洁的方式吗?

答案 2 :(得分:3)

基本上,

relocate(x,y)方法设置布局 x / y值... Transition使用translateX / Y值...

我可能错了,但我相信当任一值无效时,场景会在场景中的节点上运行“布局”传递。

当发生这种情况时,它会尝试将节点设置为已知的layoutX / Y值,这些值在调用relocate(x,y)时设置(布局值默认为0,0)。

这导致节点在每个步骤的转换中以“layoutPosition”和“pathPosition”绘制,导致抖动和节点偏离应有的位置。

@Override
public void start(Stage primaryStage) throws Exception {
    Group root = new Group();

    OrangeBlock block = new OrangeBlock(60, 40);
    System.out.println(block.getLayoutX() + " : " + block.getLayoutY());
    block.relocate(100, 80);
    //block.setTranslateX(100);
    //block.setTranslateY(80);
    System.out.println(block.getLayoutX() + " : " + block.getLayoutY());
    root.getChildren().add(block);

    PathTransition transition = newPathTransitionTo(block, 460, 320);
    transition.currentTimeProperty().addListener(e->{
        System.out.println("\nLayout Values: " + block.getLayoutX() + " : " + block.getLayoutY()
                +"\nTranslate Values:" + block.getTranslateX() + " : " + block.getTranslateY()
    );});
    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();//getTranslateX();
    double fromY = block.getLayoutY();//getTranslateY();

    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);
    }
}

不打算发布图片,但我的第一个结果就像你的第一篇文章,将其更改为上面的代码给了我第二篇文章结果。

由于这些原因,通常最好避免动态(移动)对象上的“布局”值。如果您喜欢重定位方法的便利性,我会实现您自己设置的转换值。

干杯:)

修改 我编辑了一些代码来打印转换正在运行时会发生什么,这样你就可以看到原始版本中会发生什么......

答案 3 :(得分:2)

@ jdub1581和@Lorand都给出了有效点:

  • 应用转换修改块translateXProperty()translateYProperty()
  • 过渡应用于块的中心。

我还要再添加一件事:

  • 我们混合了两个不同的东西:我们希望块跟随的全局路径,以及我们必须应用于转换的本地路径,因此块跟随第一个。

让我们向该群组添加pathScene

Path pathScene = new Path();
pathScene.getElements().add(new MoveTo( block.getLayoutX(),  block.getLayoutY()));
pathScene.getElements().add(new LineTo(460, 320));
root.getChildren().add(pathScene);

这将是我们现在的场景(为了清晰起见,我添加了两个带有原点和路径末端坐标的标签):

Path in scene coordinates

现在我们需要确定本地路径,因此我们将pathScene元素更改为块的本地坐标,并将其转换为其中心:

Path pathLocal = new Path();
pathScene.getElements().forEach(elem->{
    if(elem instanceof MoveTo){
        Point2D m = block.sceneToLocal(((MoveTo)elem).getX(),((MoveTo)elem).getY());
        Point2D mc = new Point2D(m.getX()+block.getWidth()/2d,m.getY()+block.getHeight()/2d);
        pathLocal.getElements().add(new MoveTo(mc.getX(),mc.getY()));
    } else if(elem instanceof LineTo){
        Point2D l = block.sceneToLocal(((LineTo)elem).getX(),((LineTo)elem).getY());
        Point2D lc = new Point2D(l.getX()+block.getWidth()/2d,l.getY()+block.getHeight()/2d);
        pathLocal.getElements().add(new LineTo(lc.getX(),lc.getY()));
    }
});

正如@Lorand所提到的,这应该在显示阶段后计算块的大小。

现在我们可以创建过渡并播放它。

PathTransition transition = new PathTransition();
transition.setPath(pathLocal);
transition.setNode(block);
transition.setDelay(Duration.seconds(1));
transition.setDuration(Duration.seconds(2));

transition.play();

最后,这是我们需要使块完全遵循所需路径的所有代码:

@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);

    // Path in scene coordinates, added to group 
    // in order to visualize the transition path for the block to follow
    Path pathScene = new Path();
    pathScene.getElements().add(new MoveTo(block.getLayoutX(), block.getLayoutY()));
    pathScene.getElements().add(new LineTo(460, 320));
    root.getChildren().add(pathScene);

    primaryStage.setScene(new Scene(root, 600, 400));
    primaryStage.show();

    PathTransition transition = newPathTransitionTo(pathScene, block);
    transition.play();
}

private PathTransition newPathTransitionTo(Path pathScene, OrangeBlock block) {
    // Calculate the path in local coordinates of the block
    // so transition is applied to the block without bumps
    Path pathLocal = new Path();
    pathScene.getElements().forEach(elem->{
        if(elem instanceof MoveTo){
            Point2D m = block.sceneToLocal(((MoveTo)elem).getX(),((MoveTo)elem).getY());
            Point2D mc = new Point2D(m.getX()+block.getWidth()/2d,m.getY()+block.getHeight()/2d);
            pathLocal.getElements().add(new MoveTo(mc.getX(),mc.getY()));
        } else if(elem instanceof LineTo){
            Point2D l = block.sceneToLocal(((LineTo)elem).getX(),((LineTo)elem).getY());
            Point2D lc = new Point2D(l.getX()+block.getWidth()/2d,l.getY()+block.getHeight()/2d);
            pathLocal.getElements().add(new LineTo(lc.getX(),lc.getY()));
        }
    });
    PathTransition transition = new PathTransition();
    transition.setPath(pathLocal);
    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);
    }
}

请注意,此解决方案等同于@Lorand提供的解决方案。

如果我们监视块的X,Y转换属性,它们从(0,0)到(360,240),它们只是全局路径上的相对属性。

答案 4 :(得分:1)

您正在使用重定位功能来定位您的Block。重定位函数在x和y上进行计算以定位对象。如果您使用setLayoutX找到Block然后使用getLayoutX,则可能不会发生此问题。相同的解释对y属性有效。

您可以在此处找到有关您的问题的一些信息:http://docs.oracle.com/javase/8/javafx/api/javafx/scene/Node.html#layoutXProperty