如何按需加载javafx.scene.image.Image?

是否可以丢弃已加载的Image内容,然后再将其加载? 是否可以按需加载?

我可以让ImageView仅在节目中加载它的图像吗?

Image类在其图像数据方面基本上是不可变的,在某种意义上,您可以在构造时指定图像数据的源,然后不能通过API随后修改它。

ImageView类提供在UI中显示图像的function。 ImageView类是可变的,因为您可以更改它显示的图像。

实现“平铺图像”function所需的基本策略是创建一个虚拟化容器 ,该容器具有一组“单元格”或“图块”,可以重复使用它们来显示不同的内容。 这实际上是如何在JavaFX中实现ListViewTableViewTreeViewListView 。 您可能也对Tomas Mikula的Flowless实现同样的想法感兴趣。

因此,要实现“平铺图像”function,可以使用ImageView的数组作为“单元格”或“图块”。 您可以将它们放置在窗格中并在窗格中实现平移/滚动,当图像视图滚出视图时,通过将图像从一个图像视图移动到另一个图像视图来重用ImageView ,仅为需要它的图块加载新图像。 显然,任何图像视图不再引用的图像都有资格以通常的方式进行垃圾回收。

可能还有其他方法可以实现此目的,例如使用WritableImage并使用PixelWriter在需要时更新像素数据。 哪种方法效果最好可能取决于哪种方式对于图像数据的实际格式最为方便; 不同策略之间的性能差异可能很小。

如果要从服务器或数据库加载图像,则应在后台执行此操作。 如果从URL加载图像,则Image类提供直接执行此操作的function。 如果从输入流(例如,从数据库BLOB字段)加载,则需要自己实现后台线程。

这是基本的想法(没有线程):

 import java.util.Random; import javafx.application.Application; import javafx.beans.property.DoubleProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.CornerRadii; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.stage.Stage; public class PanningTilesExample extends Application { private static final int TILE_WIDTH = 100; private static final int TILE_HEIGHT = 100; private static final int PANE_WIDTH = 800; private static final int PANE_HEIGHT = 800; // amount scrolled left, in pixels: private final DoubleProperty xOffset = new SimpleDoubleProperty(); // amount scrolled right, in pixels: private final DoubleProperty yOffset = new SimpleDoubleProperty(); // number of whole tiles shifted to left: private final IntegerProperty tileXOffset = new SimpleIntegerProperty(); // number of whole tiles shifted up: private final IntegerProperty tileYOffset = new SimpleIntegerProperty(); private final Pane pane = new Pane(); // for enabling dragging: private double mouseAnchorX; private double mouseAnchorY; // array of ImageViews: private ImageView[][] tiles; private final Random rng = new Random(); @Override public void start(Stage primaryStage) { // update number of tiles offset when number of pixels offset changes: tileXOffset.bind(xOffset.divide(TILE_WIDTH)); tileYOffset.bind(yOffset.divide(TILE_HEIGHT)); // create the images views, etc. This method could be called // when the pane size changes, if you want a resizable pane with fixed size tiles: build(); // while tile offsets change, allocate new images to existing image views: tileXOffset.addListener( (obs, oldOffset, newOffset) -> rotateHorizontal(oldOffset.intValue() - newOffset.intValue())); tileYOffset.addListener( (obs, oldOffset, newOffset) -> rotateVertical(oldOffset.intValue() - newOffset.intValue())); // Simple example just has a fixed size pane: pane.setMinSize(PANE_WIDTH, PANE_HEIGHT); pane.setPrefSize(PANE_WIDTH, PANE_HEIGHT); pane.setMaxSize(PANE_WIDTH, PANE_HEIGHT); // enable panning on pane (just update offsets when dragging): pane.setOnMousePressed(e -> { mouseAnchorX = e.getSceneX(); mouseAnchorY = e.getSceneY(); }); pane.setOnMouseDragged(e -> { double deltaX = e.getSceneX() - mouseAnchorX; double deltaY = e.getSceneY() - mouseAnchorY; xOffset.set(xOffset.get() + deltaX); yOffset.set(yOffset.get() + deltaY); mouseAnchorX = e.getSceneX(); mouseAnchorY = e.getSceneY(); }); // display in stage: Scene scene = new Scene(pane); primaryStage.setScene(scene); primaryStage.show(); } private void build() { // create array of image views: int numTileCols = (int) (PANE_WIDTH / TILE_WIDTH + 2); int numTileRows = (int) (PANE_HEIGHT / TILE_HEIGHT + 2); tiles = new ImageView[numTileCols][numTileRows]; // populate array: for (int colIndex = 0; colIndex < numTileCols; colIndex++) { final int col = colIndex; for (int rowIndex = 0; rowIndex < numTileRows; rowIndex++) { final int row = rowIndex; // create actual image view and initialize image: ImageView tile = new ImageView(); tile.setImage(getImage(col - tileXOffset.get(), row - tileYOffset.get())); tile.setFitWidth(TILE_WIDTH); tile.setFitHeight(TILE_HEIGHT); // position image by offset, and register listeners to keep it updated: xOffset.addListener((obs, oldOffset, newOffset) -> { double offset = newOffset.intValue() % TILE_WIDTH + (col - 1) * TILE_WIDTH; tile.setLayoutX(offset); }); tile.setLayoutX(xOffset.intValue() % TILE_WIDTH + (col - 1) * TILE_WIDTH); yOffset.addListener((obs, oldOffset, newOffset) -> { double offset = newOffset.intValue() % TILE_HEIGHT + (row - 1) * TILE_HEIGHT; tile.setLayoutY(offset); }); tile.setLayoutY(yOffset.intValue() % TILE_HEIGHT + (row - 1) * TILE_HEIGHT); // add image view to pane: pane.getChildren().add(tile); // store image view in array: tiles[col][row] = tile; } } } // tiles have been shifted off-screen in vertical direction // need to reallocate images to image views, and get new images // for tiles that have moved into view: // delta represents the number of tiles we have shifted, positive for up private void rotateVertical(int delta) { for (int colIndex = 0; colIndex < tiles.length; colIndex++) { if (delta > 0) { // top delta rows have shifted off-screen // shift top row images by delta // add new images to bottom rows: for (int rowIndex = 0; rowIndex + delta < tiles[colIndex].length; rowIndex++) { // stop any background loading we no longer need if (rowIndex < delta) { Image current = tiles[colIndex][rowIndex].getImage(); if (current != null) { current.cancel(); } } // move image up from lower rows: tiles[colIndex][rowIndex].setImage(tiles[colIndex][rowIndex + delta].getImage()); } // fill lower rows with new images: for (int rowIndex = tiles[colIndex].length - delta; rowIndex < tiles[colIndex].length; rowIndex++) { tiles[colIndex][rowIndex].setImage(getImage(-tileXOffset.get() + colIndex, -tileYOffset.get() + rowIndex)); } } if (delta < 0) { // similar to previous case... } } } // similarly, rotate images horizontally: private void rotateHorizontal(int delta) { // similar to rotateVertical.... } // get a new image for tile represented by column, row // this implementation just snapshots a label, but this could be // retrieved from a file, server, or database, etc private Image getImage(int column, int row) { Label label = new Label(String.format("Tile [%d,%d]", column, row)); label.setPrefSize(TILE_WIDTH, TILE_HEIGHT); label.setMaxSize(TILE_WIDTH, TILE_HEIGHT); label.setAlignment(Pos.CENTER); label.setBackground(new Background(new BackgroundFill(randomColor(), CornerRadii.EMPTY , Insets.EMPTY))); // must add label to a scene for background to work: new Scene(label); return label.snapshot(null, null); } private Color randomColor() { return Color.rgb(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256)); } public static void main(String[] args) { launch(args); } } 

这里有完整的代码(带有线程处理),完整版本没有先前版本中的线程

显然可以在这里添加更多function(和性能增强),例如,您可以允许调整窗格大小(更新:上面链接的gist的最新版本执行此操作),并在窗格更改大小时创建或删除切片等等。但这应该作为此function的基本模板。

最佳做法是在显示图像之前加载图像!

如果你想摆脱图像,只需将图像设置为null! 但是,您将重新初始化该图像以便能够查看! 我不推荐这个!

如果您将重用该图像,请保持记忆! 加载一次并在无限的imageViews上使用它!

不, Image合约中没有这样的function。 图像可以在后台加载,但一旦加载,就无法卸载。

如果使用ImageView ,则应明确地为其分配Image ,但JavaFX不提供让您知道ImageView实际显示的方法。

为了实现所需的ImageView ,我要分叉它并高度利用Prism弃用API,包括NGImageView类。