我认为这是一个简单的问题,但我找不到答案。我有一个与JavaFX Scene对象关联的ImageView对象,我想从磁盘加载大图像,并使用ImageView依次显示它们。我一直在试图找到一种重复检查Image对象的好方法,当它在后台加载完成后,将它设置为ImageView,然后开始加载一个新的Image对象。我提出的代码(下面)有时会工作,有时则不会。我很确定我遇到了JavaFX和线程的问题。它有时加载第一张图像并停止。变量“processing”是类中的布尔实例变量。
在后台加载JavaFX中的图像并在加载后将其设置为ImageView的正确方法是什么?
public void start(Stage primaryStage) {
...
ImageView view = new ImageView();
((Group)scene.getRoot()).getChildren().add(view);
...
Thread th = new Thread(new Thread() {
public void run() {
while(true) {
if (!processing) {
processing = true;
String filename = files[count].toURI().toString();
Image image = new Image(filename,true);
image.progressProperty().addListener(new ChangeListener<Number>() {
@Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number progress) {
if ((Double) progress == 1.0) {
if (! image.isError()) {
view.setImage(image);
}
count++;
if (count == files.length) {
count = 0;
}
processing = false;
}
}
});
}
}
}
});
}
答案 0 :(得分:5)
我实际上认为,对于满足您的应用程序要求而言,可能是一种更好的通用方法,而不是您尝试使用的方法,但这是我实施您描述的方法的最佳答案。 / p>
创建有界BlockingQueue
以在加载图片时保留图像。队列的大小可能需要一些调整:太小而你不会有任何&#34;缓冲区&#34; (所以你不能利用加载速度比平均速度快的任何东西),太大而你可能会消耗太多内存。 BlockingQueue
允许您从多个线程安全地访问它。
创建一个简单地循环并加载每个图像同步的线程,即在每个图像加载时线程阻塞,并将它们存放在BlockingQueue
中。
由于您想尝试每个FX帧最多显示一次图像(即60fps),请使用AnimationTimer
。这有一个handle
方法,在FX应用程序线程上的每个帧渲染上调用,因此您只需poll()
BlockingQueue
即可实现它,如果图像可用,则设置它在ImageView
。
这是一个SSCCE。我还指出了如何在显示每个图像一段固定时间的情况下执行此操作,因为我认为这是一个更常见的用例,可能会帮助其他人寻找类似的功能。
import java.io.File;
import java.net.MalformedURLException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.animation.AnimationTimer;
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
public class ScreenSaver extends Application {
@Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
Button startButton = new Button("Choose image directory...");
startButton.setOnAction(e -> {
DirectoryChooser chooser= new DirectoryChooser();
File dir = chooser.showDialog(primaryStage);
if (dir != null) {
File[] files = Stream.of(dir.listFiles()).filter(file -> {
String fName = file.getAbsolutePath().toLowerCase();
return fName.endsWith(".jpeg") | fName.endsWith(".jpg") | fName.endsWith(".png");
}).collect(Collectors.toList()).toArray(new File[0]);
root.setCenter(createScreenSaver(files));
}
});
root.setCenter(new StackPane(startButton));
primaryStage.setScene(new Scene(root, 800, 800));
primaryStage.show();
}
private Parent createScreenSaver(File[] files) {
ImageView imageView = new ImageView();
Pane pane = new Pane(imageView);
imageView.fitWidthProperty().bind(pane.widthProperty());
imageView.fitHeightProperty().bind(pane.heightProperty());
imageView.setPreserveRatio(true);
Executor exec = Executors.newCachedThreadPool(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});
final int imageBufferSize = 5 ;
BlockingQueue<Image> imageQueue = new ArrayBlockingQueue<Image>(imageBufferSize);
exec.execute(() -> {
int index = 0 ;
try {
while (true) {
Image image = new Image(files[index].toURI().toURL().toExternalForm(), false);
imageQueue.put(image);
index = (index + 1) % files.length ;
}
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// This will show a new image every single rendering frame, if one is available:
AnimationTimer timer = new AnimationTimer() {
@Override
public void handle(long now) {
Image image = imageQueue.poll();
if (image != null) {
imageView.setImage(image);
}
}
};
timer.start();
// This wait for an image to become available, then show it for a fixed amount of time,
// before attempting to load the next one:
// Duration displayTime = Duration.seconds(1);
// PauseTransition pause = new PauseTransition(displayTime);
// pause.setOnFinished(e -> exec.execute(createImageDisplayTask(pause, imageQueue, imageView)));
// exec.execute(createImageDisplayTask(pause, imageQueue, imageView));
return pane ;
}
private Task<Image> createImageDisplayTask(PauseTransition pause, BlockingQueue<Image> imageQueue, ImageView imageView) {
Task<Image> imageDisplayTask = new Task<Image>() {
@Override
public Image call() throws InterruptedException {
return imageQueue.take();
}
};
imageDisplayTask.setOnSucceeded(e -> {
imageView.setImage(imageDisplayTask.getValue());
pause.playFromStart();
});
return imageDisplayTask ;
}
public static void main(String[] args) {
launch(args);
}
}