【问题标题】:Create a path transition with absolute coordinates for a StackPane object为 StackPane 对象创建具有绝对坐标的路径过渡
【发布时间】:2014-12-26 16:26:53
【问题描述】:

OrangeBlock 是一个橙色块,里面有文字。它被实现为StackPane,在矩形顶部包含文本。 (此方法在the documentation for StackPane 中进行了演示。)

我已将OrangeBlock 放置在坐标 (100, 80) 处,现在正试图使其顺利移动到某些目标坐标。不幸的是,我遇到了一个令人讨厌的障碍:

由于某种原因,PathElements 中的坐标被解释为相对于橙色块。

这是为什么?我怎样才能让我的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);
        }
    }
}

【问题讨论】:

    标签: javafx


    【解决方案1】:

    出于好奇,我调试了 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。一个矩形节点。此外,您需要 layoutBounds,因此 PathTransition 必须在边界可用后创建。

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

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

    所以我对你的问题的解决方案是

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

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

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

    【讨论】:

    • 我相信那些 impl_getPivot**() 方法用于 PathTransition.OrientationType .. ?? .. 这种方式如果设置为 OrthogonalToTangent,它使用节点的中心来适当地旋转它。不过我可能是错的......
    【解决方案2】:

    我现在使用的解决方案是简单地将PathlayoutXlayoutY 向相反方向偏移。

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

    Path 实例化后立即插入对此方法的调用可以解决问题。

    我对这个解决方案并不满意。我不明白为什么layoutXlayoutY 需要参与。有没有更简洁的方法?

    【讨论】:

      【解决方案3】:

      所以基本上,

      relocate(x,y) 方法设置 layout x/y 值... 过渡使用 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);
          }
      }
      

      不会发图片,但是我的第一个结果和你的第一个帖子一样,把它改成上面的代码给了我第二个帖子的结果。

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

      干杯:)

      编辑: 我编辑了一些代码来打印过渡运行时发生的情况,这样您就可以看到原始版本中发生的情况..

      【讨论】:

        【解决方案4】:

        @jdub1581 和@Lorand 都给出了有效的分数:

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

        我再补充一点:

        • 我们混合了两种不同的东西:我们希望块遵循的 global 路径,以及我们必须应用于转换的 local 路径,因此块遵循第一个。

        让我们在群组中添加一个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);
        

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

        现在我们需要确定本地路径,因此我们将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),这只是全局路径上的相对属性。

        【讨论】:

          【解决方案5】:

          您正在使用 relocate 功能来定位您的 Block。 relocate 函数对 x 和 y 进行计算以定位您的对象。如果你使用 setLayoutX 定位 Block 然后使用 getLayoutX 可能不会出现这个问题。相同的解释对 y 属性有效。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-02-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-07-23
            • 2013-09-27
            • 2011-03-19
            • 2015-05-05
            相关资源
            最近更新 更多