ARTICLE AD BOX
I am experiencing significant performance issues and UI freezing in my JavaFX application when navigating through pages of a gallery-style view.
I am running this on an AMD Ryzen with Integrated Graphics.
Despite using a Task for background loading, I am seeing major stuttering and memory growth. Here are the logs from my console during navigation:
PPSRenderer: scenario.effect - createShader: LinearConvolveShadow_4 Search: test Growing pool D3D Vram Pool target to 294 896 105 Growing pool D3D Vram Pool target to 313 327 111 Growing pool D3D Vram Pool target to 365 021 337 Growing pool D3D Vram Pool target to 387 835 170 Growing pool D3D Vram Pool target to 434 904 361 Growing pool D3D Vram Pool target to 462 085 883 Growing pool D3D Vram Pool target to 504 627 097 Growing pool D3D Vram Pool target to 536 166 290Here is my code:
public class SearchView { ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.getDefault()); private final AlbumService albumService; private final SearchViewModel searchViewModel; private ScrollPane scrollPane; private FlowPane flowPane; private FlowPane chipsPane; private Button backButton = new Button("Back"); private TextField searchTextField = new TextField(); private Button clearButton = new Button("Clear"); private int currentPage = 1; private boolean isLoading = false; private String selectedOption = "Album"; public SearchView(AlbumService albumService, SearchViewModel searchViewModel) { this.albumService = albumService; this.searchViewModel = searchViewModel; } public VBox show() { return createSearchBox(); } private VBox createSearchBox() { VBox searchBox = new VBox(); searchBox.setPadding(new Insets(10)); flowPane = new FlowPane(); flowPane.setHgap(15); flowPane.setVgap(15); flowPane.setCursor(Cursor.DEFAULT); scrollPane = new ScrollPane(flowPane); scrollPane.setFitToWidth(true); scrollPane.setPannable(true); scrollPane.setStyle("-fx-background-color: transparent;"); scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); chipsPane = new FlowPane(); chipsPane.setPadding(new Insets(10)); chipsPane.setHgap(8); chipsPane.setVgap(8); refreshChips(); HBox chips = new HBox(chipsPane); chips.setAlignment(Pos.CENTER_LEFT); chips.setPadding(new Insets(0, 10, 0, 0)); chips.setFillHeight(false); scrollPane.vvalueProperty().addListener((obs, oldVal, newVal) -> { if (newVal.doubleValue() >= 1.0 && !isLoading) { currentPage++; performSearch(searchTextField.getText()); } }); VBox.setVgrow(scrollPane, Priority.ALWAYS); searchBox.getChildren().addAll(createSearchToolbar(), chips, scrollPane); searchBox.setAlignment(Pos.TOP_CENTER); return searchBox; } private HBox createSearchToolbar() { HBox toolbar = new HBox(); toolbar.setAlignment(Pos.CENTER_LEFT); toolbar.setSpacing(15); Utils.iconLoader(backButton, "/png/back.png"); Utils.iconLoader(clearButton, "/png/close.png"); clearButton.setVisible(false); clearButton.setManaged(false); HBox customSearchField = new HBox(); customSearchField.setAlignment(Pos.CENTER_LEFT); customSearchField.setSpacing(0); customSearchField.setPadding(new Insets(5, 10, 5, 10)); customSearchField.setStyle("-fx-background-color: #3b3b3b; -fx-background-radius: 20;"); Image image = new Image(Objects.requireNonNull(SearchView.class.getResourceAsStream("/png/search.png"))); ImageView imageView = new ImageView(image); imageView.setFitHeight(20); imageView.setFitWidth(20); imageView.setPreserveRatio(true); searchTextField.setPromptText(bundle.getString("search")); searchTextField.setStyle("-fx-font-size: 20; -fx-background-color: transparent; -fx-text-fill: #ffffff"); changeIconColor(imageView, Color.web("#ffffff")); searchTextField.textProperty().addListener((observable, oldValue, newValue) -> { boolean hasText = (newValue != null && !newValue.trim().isEmpty()); clearButton.setVisible(hasText); clearButton.setManaged(hasText); }); backButton.setOnAction(event -> ViewNavigator.loadView(new CollectionView(albumService).show())); clearButton.setOnAction(event -> searchTextField.clear()); customSearchField.getChildren().addAll(imageView, searchTextField, clearButton); HBox.setHgrow(customSearchField, Priority.ALWAYS); HBox.setHgrow(searchTextField, Priority.ALWAYS); HBox.setHgrow(clearButton, Priority.ALWAYS); searchTextField.setOnKeyPressed(event -> { if (event.getCode() == KeyCode.ENTER) { String query = searchTextField.getText(); if (!query.isEmpty()) { currentPage = 1; flowPane.getChildren().clear(); performSearch(query); } } }); toolbar.getChildren().addAll(backButton, customSearchField); return toolbar; } private void performSearch(String query) { isLoading = true; System.out.println("Search: " + query); Task<List<VBox>> searchTask = new Task<>() { @Override protected List<VBox> call() throws Exception { List<SearchAlbum> albums = albumService.searchAlbums(query, currentPage); List<VBox> nodes = new ArrayList<>(); for (SearchAlbum album : albums) { VBox albumNode = AlbumItem.loadImage( album.name(), album.artist(), album.image().get(2).text(), () -> { AlbumDetailView albumDetailView = new AlbumDetailView( album.name(), album.artist(), albumService); ViewNavigator.loadView(albumDetailView.show()); } ); nodes.add(albumNode); } return nodes; } }; searchTask.setOnSucceeded(event -> { flowPane.getChildren().addAll(searchTask.getValue()); isLoading = false; }); searchTask.setOnFailed(event -> { System.out.println("Something went wrong: " + searchTask.getException()); searchTask.getException().printStackTrace(); isLoading = false; }); new Thread(searchTask).start(); } private void refreshChips() { chipsPane.getChildren().clear(); HBox albumChip = Chip.textChip("Album", "/png/check.png", () -> { selectedOption = "Album"; refreshChips(); }, selectedOption.equals("Album")); HBox artistChip = Chip.textChip(bundle.getString("artist"), "/png/check.png", () -> { selectedOption = bundle.getString("artist"); refreshChips(); }, selectedOption.equals(bundle.getString("artist"))); chipsPane.getChildren().addAll(albumChip, artistChip); } } public class AlbumItem { public static VBox loadImage(String albumName, String artistName, String imageUrl, Runnable clickAction) { Label albumLabel = new Label(albumName); Label artistLabel = new Label(artistName); ImageView albumImageView = new ImageView(); albumLabel.setMaxWidth(140); artistLabel.setMaxWidth(140); try { if (imageUrl != null && !imageUrl.isEmpty()) { Image image = new Image(imageUrl, true); albumImageView.setImage(image); } else { setDefaultImage(albumImageView); } } catch (Exception ex) { setDefaultImage(albumImageView); } albumImageView.setFitWidth(150); albumImageView.setFitHeight(150); albumImageView.setPreserveRatio(true); roundedCorner(albumImageView); VBox vBox = new VBox(5, albumImageView, albumLabel, artistLabel); vBox.setAlignment(Pos.CENTER); vBox.setOnMouseClicked(event -> { if (clickAction != null) { clickAction.run(); } }); vBox.setCursor(Cursor.HAND); return vBox; } private static void setDefaultImage(ImageView imageView) { var resource = AlbumItem.class.getResourceAsStream("/png/album_error.png"); if (resource != null) { Image defaultImage = new Image(resource); imageView.setImage(defaultImage); imageView.setFitHeight(150); imageView.setFitWidth(150); imageView.setPreserveRatio(false); imageView.setSmooth(true); imageView.setCache(true); } else { System.out.println("Błąd: Nie znaleziono pliku /png/album_error.png w folderze resources!"); } } public static void roundedCorner(ImageView imageView) { Rectangle clip = new Rectangle(150, 150); clip.setArcHeight(20); clip.setArcWidth(20); imageView.setClip(clip); imageView.setEffect(new DropShadow(10, Color.rgb(0, 0, 0, 0.2))); } public static void roundedCorner(ImageView imageView, int v, int v1) { Rectangle clip = new Rectangle(v, v1); clip.setArcHeight(20); clip.setArcWidth(20); imageView.setClip(clip); imageView.setEffect(new DropShadow(10, Color.rgb(0, 0, 0, 0.2))); } }