答案 0 :(得分:1)
Image
类在其图像数据方面基本上是不可变的,因为您可以在构造时指定图像数据的源,然后不能通过API随后修改它。
ImageView
类提供在UI中显示图像的功能。 ImageView
类是可变的,因为您可以更改它显示的图像。
您需要实施的基本策略"平铺图像"功能是创建一个虚拟化容器,它有一个" cell"或"瓷砖"它们被重用来显示不同的内容。这实际上是在JavaFX中实现ListView
,TableView
和TreeView
等控件的方式。您可能也对Tomas Mikula Flowless实现同样的想法感兴趣。
所以要实现"平铺图像"功能,您可以使用ImageView
s数组作为"单元格"或"瓷砖"。您可以将它们放在窗格中并在窗格中实现平移/滚动,当图像视图滚出视图时,通过将图像从一个图像视图移动到另一个图像视图来重用ImageView
,仅为图像视图加载新图像需要它的瓷砖。显然,任何图像视图不再引用的图像都有资格以通常的方式进行垃圾收集。
可能还有其他方法可以实现此目的,例如使用WritableImage
并使用PixelWriter
在需要时更新像素数据。最好的方法可能取决于哪种方式对于图像数据的实际格式最为方便;不同的策略之间可能没有什么性能差异。
如果要从服务器或数据库加载图像,则应在后台执行此操作。如果从URL加载图像,则Image
类提供直接执行此操作的功能。如果从输入流(例如,从数据库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);
}
}
完整代码(带有线程处理)here,完整版本没有previous revision中的线程
显然可以添加更多功能(和性能增强),例如,您可以允许调整窗格大小(更新:上面链接的gist的最新版本执行此操作),并在创建或删除切片时窗格更改大小等。但这应该作为此功能的基本模板。
答案 1 :(得分:0)
最佳做法是在显示图像之前加载图像!
如果您想摆脱图像,只需将图像设置为null即可!但是,您将重新初始化该图像,以便能够查看!我不推荐这个!
如果您将重复使用该图像,请保留内存! 加载一次并在无限的imageViews上使用它!
答案 2 :(得分:-1)
不,Image
合同中没有此类功能。图像可以在后台加载,但一旦加载,就无法卸载。
如果使用ImageView
,那么您应该明确地为其分配Image
,但JavaFX并未提供一种方法让您知道实际显示ImageView
的时间。
为了实现所需的接近ImageView
,我应该使用Prism
来高度利用已弃用的API,包括NGImageView
类。