2012-12-09 101 views
1

我有一個JavaFX應用程序,它內有大約20個標籤。當用戶點擊標籤時,我需要標籤閃爍紅色。爲了使標籤閃爍紅色,我使用的是我實際爲swing開發的線程,但轉換爲JavaFX。它工作了一段時間,但我最近將應用程序鎖定追蹤到標籤的動畫。我做動畫的方式很簡單:Java FX線程安全暫停/休眠

new Thread(new AnimateLabel(tl,idx)).start(); 

t1指向一個ArrayList的標籤,idx指向它的索引。另一個標籤有一個點擊事件附加到它,當你點擊,它創建線程動畫標籤(使其閃爍)。

出於某種原因,這會導致應用程序鎖定,如果有被壓了​​很多標籤。

我敢肯定這是一個線程安全問題的JavaFX。我有另一個線程共享JavaFX應用程序線程像這樣:

TimerUpdater tu = new TimerUpdater(mDS); 
Timeline incHandler = new Timeline(new KeyFrame(Duration.millis(130),tu)); 
incHandler.setCycleCount(Timeline.INDEFINITE); 
incHandler.play(); 

TimerUpdater會不斷更新標籤,即使是閃爍的那些文字。

這裏的標籤動畫師:

private class AnimateLabel implements Runnable 
{ 
    private Label lbl; 
    public AnimateLabel(Label lbl, int myIndex) 
    { 
     // if inAnimation.get(myIndex) changes from myAnim's value, stop, 
     // another animation is taking over 
     this.lbl = lbl; 
    } 

    @Override 
    public void run() { 
     int r, b, g; 
     r=255; 
     b=0; 
     g=0; 

     int i = 0; 
     while(b <= 255 && g <= 255) 
     { 
      RGB rgb = getBackgroundStyle(lbl.getStyle()); 
      if(rgb != null) 
      { 
       if(rgb.g < g-16) { return; } 
      } 
      lbl.setStyle("-fx-color: #000; -fx-background-color: rgb("+r+","+g+","+b+");"); 
      try { Thread.sleep(6); } 
      catch (Exception e){} 
      b += 4; 
      g += 4; 
      ++i; 
     } 
     lbl.setStyle("-fx-color: #000; -fx-background-color: fff;"); 
    } 
} 

我會運行此這樣:

javafx.application.Platform.runLater(new AnimateLabel(tl, idx)); 

然而,的Thread.sleep(6)將被忽略。有沒有辦法在稍後的運行中暫停以控制動畫的速度,同時與javafx.application共享線程?

問候, 達斯汀

回答

4

我覺得有的JavaFX的事件隊列是如何工作的意見稍有分歧。

1)在普通線程上運行AnimateLabel代碼將導致Label#setStyle(...)代碼在該線程上執行 - 這是非法的並可能導致您的問題。

2)運行AnimateLabel代碼完全在JavaFX的事件隊列,按您的第二示例中,意味着事件線程將被阻止,直到動畫完成。同時,應用程序不會更新,不會處理用戶事件或重繪,就此而言。基本上,你要在一個循環內改變標籤樣式,但是你並沒有給事件隊列時間來重新繪製標籤,這就是爲什麼你不會在屏幕上看到任何東西。

半正確的做法是兩者的混合物。在單獨的線程中運行AnimateLabel,但將調用Label#setStyle(...)包裝在Platform#runLater(...)中。這樣,您只會在事件線程中進行相關工作,並且可以自由地在其間進行其他工作(例如更新UI)。

正如我所說的,這是半正確的做法,因爲有一個內置的工具,做你想做的事,更容易的方式。你可能想看看Transition類。它爲自定義動畫提供了一個簡單的方法,甚至還提供了一些預構建的子類來動畫化一個Node的最常見的屬性。

+0

我想如果我只是做1個循環每16millis,擺脫了Thread.Sleep,並改寫環路如此容納它,它將起作用。沒有時間嘗試,但我會讓你知道。感謝您指出Transition,當我尋找用於製作動畫的FX類時,我沒有找到這種方式,這當然是更好的方法...... – SigSeg

3

Sarcan具有優良的答案。

這個答案只是提供示例代碼(基於zonski's forum post)的Timeline方法來修改CSS樣式。該代碼可以用來代替您在問題中發佈的代碼。

這種方法的一個優點是JavaFX庫處理所有線程問題,確保您的所有代碼都在JavaFX線程上執行並消除了您可能遇到的任何線程安全問題。

import javafx.animation.*; 
import javafx.application.Application; 
import javafx.beans.property.*; 
import javafx.beans.value.*; 
import javafx.event.EventHandler; 
import javafx.scene.Scene; 
import javafx.scene.control.Label; 
import javafx.scene.input.MouseEvent; 
import javafx.scene.layout.TilePane; 
import javafx.stage.Stage; 
import javafx.util.Duration; 

public class CssFlash extends Application { 

    private Label flashOnClick(final Label label) { 
     label.setStyle(String.format("-fx-padding: 5px; -fx-background-radius: 5px; -fx-background-color: lightblue;")); 

     DoubleProperty opacity = new SimpleDoubleProperty(); 
     opacity.addListener(new ChangeListener<Number>() { 
      @Override public void changed(ObservableValue<? extends Number> source, Number oldValue, Number newValue) { 
       label.setStyle(String.format("-fx-padding: 5px; -fx-background-radius: 5px; -fx-background-color: rgba(255, 0, 0, %f)", newValue.doubleValue())); 
      } 
     }); 

     final Timeline flashAnimation = new Timeline(
       new KeyFrame(Duration.ZERO, new KeyValue(opacity, 1)), 
       new KeyFrame(Duration.millis(500), new KeyValue(opacity, 0))); 
     flashAnimation.setCycleCount(Animation.INDEFINITE); 
     flashAnimation.setAutoReverse(true); 

     label.setOnMouseClicked(new EventHandler<MouseEvent>() { 
      @Override public void handle(MouseEvent t) { 
       if (Animation.Status.STOPPED.equals(flashAnimation.getStatus())) { 
        flashAnimation.play(); 
       } else { 
        flashAnimation.stop(); 
        label.setStyle(String.format("-fx-padding: 5px; -fx-background-radius: 5px; -fx-background-color: lightblue;")); 
       } 
      } 
     });   

     return label; 
    } 

    @Override public void start(Stage stage) throws Exception { 
     TilePane layout = new TilePane(5, 5); 
     layout.setStyle("-fx-background-color: whitesmoke; -fx-padding: 10;"); 

     for (int i = 0; i < NUM_LABELS; i++) { 
      layout.getChildren().add(flashOnClick(new Label("Click to flash"))); 
     }  

     Scene scene = new Scene(layout); 
     stage.setScene(scene); 
     stage.show(); 
    } 

    private static final int NUM_LABELS = 20; 
    public static void main(String[] args) { Application.launch(args); } 
} 

JavaFX的8將允許您設置使用Java API的區域的背景,所以,如果你願意,你可以完成相同的事情,而不CSS。

與幾個標籤的示例程序輸出點擊,在閃爍的各種狀態: clicktoflash