1

我有一个 JavaFX 应用程序,其中包含大约 20 个标签。当用户单击标签时,我需要标签闪烁红色。为了让标签变成红色,我使用了一个我为 swing 开发但转换为 JavaFX 的线程。它工作了一段时间,但我最近将应用程序锁定跟踪到标签的动画。我制作动画的方式很简单:

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

tl 指向标签的 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

2 回答 2

4

我认为对 JavaFX 事件队列的工作方式有一点误解。

1) 在普通线程上运行 AnimateLabel 代码将导致Label#setStyle(...)代码在该线程上执行 - 这是非法的,可能会导致您的问题。

2) 根据您的第二个示例,完全在 JavaFX 事件队列上运行 AnimateLabel 代码意味着事件线程将被阻塞,直到动画完成。同时,应用程序不会更新,不会处理用户事件或重绘,就此而言。基本上,您在循环中更改标签样式,但您没有给事件队列时间来实际重绘标签,这就是您在屏幕上看不到任何内容的原因。

半正确的方法是两者的混合。在单独的线程中运行 AnimateLabel,但将调用包装Label#setStyle(...)Platform#runLater(...). 这样,您只会用相关工作来打扰事件线程,并让它自由地在其间做其他工作(例如更新 UI)。

正如我所说,这是一种半正确的方法,因为有一个内置工具可以更轻松地完成您想做的事情。您可能想查看 Transition 课程。它为自定义动画提供了一种简单的方法,甚至提供了一堆预构建的子类来为节点的最常见属性设置动画。

于 2012-12-09T12:21:36.120 回答
3

Sarcan 有一个很好的答案。

该答案仅提供了用于修改 css 样式的时间线方法的示例代码(基于zonski 的论坛帖子) 。该代码可以用作您在问题中发布的代码的替代品。

这种方法的一个优点是 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

于 2012-12-10T01:42:29.723 回答