2015-01-15 31 views
3

我已經創建了自己的Marquee類/控件,它在加載後的前幾秒內工作得很好。但是,一旦應用程序第一次加載選取框認爲它的localbounds是maxWidth:0.0 minWidth:2.0。下面是Marquee類代碼和我用來在我的測試應用中加載它的代碼。如何在初始化時立即實現JavaFX控件以實現localBounds?

跑馬燈類:

public class Marquee extends HBox { 
     /** 
     * Add a node to the Marquee control 
     * @param node - a node to add 
     */ 
     public void add(Node node) { 
      getChildren().add(node); 
     } 

     /** 
     * Add a list of nodes to the Marquee control 
     * @param observable list - an observable list to add 
     */ 
     public void addAll(ObservableList<Node> list) { 
      getChildren().addAll(list); 
     } 

     /** 
     * Default Constructor: Initializes the Marquee Object with default settings: 
     * Empty Array List of Nodes, initial delay, Direction.LEFT, Duration.seconds(10), Interpolator.LINEAR, 10) 
     */ 
     public Marquee() 
     { 
      this(FXCollections.observableArrayList(new ArrayList<Node>()), Duration.seconds(3), Direction.LEFT, Duration.seconds(10), Interpolator.LINEAR, 10.0); 
     } 

     /** 
     * Constructor: Initializes the Marquee Object with default settings 
     * @param observable list 
     */ 
     public Marquee(ObservableList<Node> nodes) 
     { 
      this(nodes, Duration.seconds(3), Direction.LEFT, Duration.seconds(10), Interpolator.LINEAR, 10.0); 
     } 

     /** 
     * Constructor: Initializes the Marquee Object with default settings 
     * @param observable list 
     * @param duration - usually in seconds i.e. Duration.seconds(10) 
     */ 
     public Marquee(ObservableList<Node> nodes, Duration duration) { 
      this(nodes, Duration.seconds(3), Direction.LEFT, duration, Interpolator.LINEAR, 10.0); 
     } 

     /** 
     * Constructor: Initializes the Marquee Object with default settings 
     * @param observable list 
     * @param direction - an enum, i.e Direction.LEFT or Direction.RIGHT 
     * @param duration - usually in seconds i.e. Duration.seconds(10) 
     */ 
     public Marquee(ObservableList<Node> nodes, Direction direction, Duration duration) { 
      this(nodes, Duration.seconds(3), direction, duration, Interpolator.LINEAR, 10.0); 
     } 

     /** 
     * Constructor: Initializes the Marquee Object with default settings 
     * @param observable list 
     * @param duration - usually in seconds i.e. Duration.seconds(10) 
     * @param interpolator - effects the translation behavior, i.e 
     * Interpolator.EASE_BOTH, or EASE_LINEAR 
     */ 
     public Marquee(ObservableList<Node> nodes, Duration duration, Interpolator interpolator) 
     { 
      this(nodes, Duration.seconds(3), Direction.LEFT, duration, interpolator, 10.0); 
     } 

     /** 
     * Constructor: Initializes the Marquee Object with default settings: 
     * @param observable list 
     * @param initialDelay - the amount of time before the marquee will begin scrolling 
     * after the application has loaded 
     * @param direction - an enum, i.e Direction.LEFT or Direction.RIGHT 
     * @param duration - usually in seconds i.e. Duration.seconds(10) 
     * @param interpolator - effects the translation behavior, i.e 
     * Interpolator.EASE_BOTH, or EASE_LINEAR 
     */ 
     public Marquee(ObservableList<Node> list, Duration initialDelay, Direction direction, Duration duration, Interpolator interpolator) { 

      this(list, initialDelay, direction, duration, interpolator, 10.0); 
     } 

     /** 
     * Preferred Constructor: Initializes the Marquee Object with your preferred settings 
     * 
     * @param observable list 
     * @param initialDelay - the amount of time before the marquee will begin scrolling 
     * after the application has loaded 
     * @param direction - an enum, i.e Direction.LEFT or Direction.RIGHT 
     * @param duration - usually in seconds i.e. Duration.seconds(10) 
     * @param interpolator - effects the translation behavior, i.e 
     * Interpolator.EASE_BOTH, or EASE_LINEAR 
     * @param nodeSpacing - a double value that determines how far apart 
     * each element in the marquee will be placed from one another 
     */ 
     public Marquee(ObservableList<Node> list, Duration initialDelay, Direction direction, Duration duration, Interpolator interpolator, double nodeSpacing) { 
      super(); 
      getChildren().addAll(list); 
      setSpacing(nodeSpacing); 
      delay = initialDelay; 
      this.direction = direction; 
      this.duration = duration; 
      this.interpolator = interpolator; 
     } 

     public enum Direction { 
      LEFT, RIGHT 
     }; 

     private Direction direction; 
     private TranslateTransition animation; 
     private Duration duration; 

     /** 
     * This begins the animation of the Marquee. By default this method 
     * calculates the width of the Marquee's parent and uses that as its 
     * start point. When the nodes inside the Marquee have reached the outer 
     * bounds of its parent the Marquee will stop and reset. Note: If the 
     * application is resized, the animation will need to be stopped and 
     * restarted. The Marquee will recalculate its translation requirements each 
     * cycle so if the user resizes it's parent, the Marquee will conform. 
     * 
     * @param duration 
     *   the amount of time the translation should take 
     */ 
     public void animate() { 

      animation = new TranslateTransition(duration, this); 
      double maxWidth = getBoundsInLocal().getMaxX(); 
      double minWidth = getBoundsInLocal().getMinX() - getContentsWidth(); 

      switch (direction) { 
      case LEFT: 
       animation.setToX(minWidth); 
       animation.setFromX(maxWidth); 
       break; 
      case RIGHT: 
       animation.setToX(maxWidth); 
       animation.setFromX(minWidth); 
       break; 
      default: 
       animation.setToX(minWidth); 
       animation.setFromX(maxWidth); 
       break; 
      } 

      animation.setCycleCount(1); 
      animation.setInterpolator(getInterpolator()); 
      animation.setDelay(delay); 
      animation.playFromStart(); 

      animation.setOnFinished(new EventHandler<ActionEvent>() { 
       @Override 
       public void handle(ActionEvent event) 
       { 
        stopAnimation(); 
        recycleAnimation(); 
       } 

      }); 
     } 

     private Duration delay; 
     public Duration getDelay() { 
      return delay; 
     } 

     public void setDelay(Duration delay) { 
      this.delay = delay; 
     } 

     private Interpolator interpolator; 

     /** 
     * How the Marquee transitions its content into and out of FOV. 
     * Options are: 
     * DISCRETE (DO NOT USE), EASE_IN, EASE_OUT, EASE_BOTH, and LINEAR (DEFAULT). 
     * Any change to the Interpolator will take affect after the current cycle 
     * ends. 
     * Suggested Usage: setInterpolator(Interpolator.LINEAR) 
     */ 
     public void setInterpolator(Interpolator interpolator) 
     { 
      this.interpolator = interpolator; 
     } 

     /** 
     * The Interpolator of the Marquee. 
     * @return Interpolator 
     */ 
     public Interpolator getInterpolator() 
     { 
      return interpolator; 
     } 

     public void recycleAnimation() 
     { 
      setDelay(Duration.ZERO); 
      animate(); 
     } 

     /** 
     * Stop animation of Marquee 
     */ 
     public void stopAnimation() { 
      animation.stop(); 
     } 

     /** 
     * Set the default spacing between nodes in the Marquee Default is set to 
     * 5.0 
     */ 
     public void setNodeSpacing(double value) { 
      setSpacing(value); 
     } 

     /** 
     * Get the current spacing between nodes in the Marquee 
     * 
     * @return double 
     */ 
     public double getNodeSpacing() { 
      return getSpacing(); 
     } 

     private int getContentsWidth() 
     { 
      int width = 0; 

      for(Node node : getChildrenUnmodifiable()) 
      { 
       width += node.boundsInLocalProperty().get().getWidth(); 
      } 

      return width; 
     } 

    } 

和我的主類

public class Main extends Application { 
    @Override 
    public void start(Stage primaryStage) { 
     try { 

      BorderPane root = new BorderPane(); 

      ObservableList<Node> labels = FXCollections.observableArrayList(); 
      labels.add(new Label("Test Label 1")); 
      labels.add(new Label("Test Label 2")); 

      Marquee marqueeLeft = new Marquee(labels, Duration.ZERO, Direction.LEFT, Duration.seconds(10), Interpolator.EASE_BOTH, 10.0); 
      root.setTop(marqueeLeft); 

      final ObservableList<Node> labels2 = FXCollections.observableArrayList(); 
      labels2.add(new Label("Test Label 3")); 
      labels2.add(new Label("Test Label 4")); 
      final Marquee marqueeRight = new Marquee(labels2, Duration.ZERO, Direction.RIGHT, Duration.seconds(10), Interpolator.EASE_BOTH, 10.0); 
      root.setBottom(marqueeRight); 
      marqueeLeft.animate(); 
      marqueeRight.animate(); 

      Button button = new Button(); 
      button.setOnAction(new EventHandler<ActionEvent>() { 
       @Override 
       public void handle(ActionEvent event) { 
        System.out.println("Workin"); 
        marqueeRight.add(new Label("Test Add Label")); 
       } 
      }); 

      root.setCenter(button); 

      Scene scene = new Scene(root,600,300); 
      scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm()); 
      primaryStage.setScene(scene); 
      primaryStage.show(); 


     } catch(Exception e) { 
      e.printStackTrace(); 
     } 
    } 

    public static void main(String[] args) { 
     launch(args); 
    } 
} 

我試圖等待加載字幕,直到顯示階段,試圖讓parentBounds,localBounds,你的名字後。它總是希望從0.0開始,2.0。

任何意見將不勝感激。

回答

2

在你Marquee類,動畫調用getContentWidth(),這只是讓每個節點的寬度的節點:

private int getContentsWidth(){ 
    int width = 0; 
    for(Node node : getChildrenUnmodifiable()){ 
     width += node.boundsInLocalProperty().get().getWidth(); 
    } 
    return width; 
} 

然後你實例化你的帳篷,並啓動動畫:

Marquee marqueeLeft = new Marquee(labels, Duration.ZERO, Direction.LEFT, Duration.seconds(10), Interpolator.EASE_BOTH, 10.0); 
marqueeLeft.animate(); 

然後你表演舞臺。

您的方法存在的問題只是在getWidth():它將始終返回0 直到您顯示階段和佈局節點。之後,它們將具有非零寬度。

現在,您必須等待第一個動畫循環。當動畫結束時再次呼籲animate()

public void recycleAnimation(){ 
    setDelay(Duration.ZERO); 
    animate(); 
} 

這第二次,所有的節點都在舞臺上,他們有一個有效的寬度,選取框開始移動。

解決方案:只要移動電話動畫:

Scene scene = new Scene(root,600,300); 
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm()); 
primaryStage.setScene(scene); 
primaryStage.show(); 

marqueeLeft.animate(); 
marqueeRight.animate(); 

而現在,在樣品中,寬度爲126像素,而跑馬燈將開始像您期望的移動。

編輯

萬一跑馬燈被後創建階段所示:

Scene scene = new Scene(root,600,300); 
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm()); 
primaryStage.setScene(scene); 
primaryStage.show(); 

Marquee marqueeLeft = new Marquee(labels, Duration.ZERO, Direction.LEFT, Duration.seconds(10), Interpolator.EASE_BOTH, 10.0); 
root.setTop(marqueeLeft); 
marqueeLeft.animate(); 

這也不行,因爲調用animate()後發生inmediately節點被添加到場景中,並且JavaFX應用程序線程沒有時間更新值。你可以閱讀關於JavaFX架構here

解決方案:讓場景圖一些時間來執行其任務:

root.setTop(marqueeLeft); 
root.setBottom(marqueeRight); 

Platform.runLater(new Runnable() { 

    @Override 
    public void run() { 
     marqueeLeft.animate(); 
     marqueeRight.animate(); 
    } 
}); 
+0

謝謝!我猜想當我在展示舞臺後試圖加載它時,我也在那裏做了所有的實例化。 – JeramyRR

+0

我已經編輯了我的答案,並解釋了爲什麼沒有工作和有效的解決方案。 –

+0

再次感謝!這是一個真正的幫助。現在,如果我能弄清楚如何讓我的標籤不會超出範圍...... – JeramyRR

1

你讓JavaFX的隱式處理的佈局傳遞期間,它在聖何塞佩雷達的答案,或者你的建議(如果你使用Java標準的脈衝過程8+)可以manually applyCSS trigger a layout pass

Pane parentNode = new Pane(); 
Scene scene = new Scene(parentNode); 
Node child = new Button("XYZZY"); 
parentNode.getChildren().add(child); 

System.out.println(button.getWidth()); // outputs 0 as width is not yet known. 

parentNode.applyCss(); 
parentNode.layout(); 

System.out.println(button.getWidth()); // outputs non-0 as width is now known. 
+1

確實也有效,但由於問題是用Java-7標記的,我不確定OP是否使用JavaFX 8. –

+0

我錯過了Java-7標記,thx注意到並且yes,applyCss方法在Java 7中不可用。 – jewelsea

+0

是的,目前正在使用Java 7.一旦我跳到Java 8,我會記住這一點。 – JeramyRR