我在我的第一个JavaFX项目上工作并且遇到listview自定义单元工厂的问题。这是我的代码
package ir.sadeghpro.instagram.cell;
import com.ibm.icu.util.PersianCalendar;
import ir.sadeghpro.insta.client.Comment;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextFlow;
import java.awt.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
public class DischargeComment extends ListCell<Comment> {
@FXML
private AnchorPane pane;
@FXML
private TextFlow lblComment;
@FXML
private Label lblDate;
@FXML
private Label lblTime;
@FXML
private Hyperlink lblUsername;
@FXML
private ImageView img;
public static String search = "";
private FXMLLoader mLLoader;
private static Map<String, Image> images = new HashMap<>();
@Override
protected void updateItem(Comment item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
if (mLLoader == null) {
mLLoader = new FXMLLoader(getClass().getClassLoader().getResource("cell/discharge_comment.fxml"));
mLLoader.setController(this);
try {
mLLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
ObservableList<Node> children = lblComment.getChildren();
lblComment.setTextAlignment(TextAlignment.JUSTIFY);
children.clear();
if (!search.isEmpty() && item.getText().contains(search)) {
int lastIndex = 0;
for (int index = item.getText().indexOf(search); index >= 0; index = item.getText().indexOf(search, index + 1)) {
Text text = new Text(item.getText().substring(lastIndex, index));
text.setTextAlignment(TextAlignment.LEFT);
children.add(text);
text = new Text(item.getText().substring(index, index + search.length()));
text.setTextAlignment(TextAlignment.LEFT);
text.setFill(Color.RED);
children.add(text);
lastIndex = index + search.length();
}
if (lastIndex < item.getText().length()) {
Text text = new Text(item.getText().substring(lastIndex));
text.setTextAlignment(TextAlignment.LEFT);
children.add(text);
}
} else {
children.add(new Text(item.getText()));
}
PersianCalendar persianCalendar = new PersianCalendar();
persianCalendar.setTimeInMillis(item.getTimestamp() * 1000L);
lblDate.setText(persianCalendar.get(Calendar.YEAR) + "/" + (persianCalendar.get(Calendar.MONTH) + 1) + "/" + persianCalendar.get(Calendar.DAY_OF_MONTH));
lblTime.setText(persianCalendar.get(Calendar.HOUR) + ":" + persianCalendar.get(Calendar.MINUTE));
lblUsername.setText(item.getOwnerUsername());
Image image;
if ((image = images.get(item.getOwnerId())) == null) {
img.setImage(null);
new Thread(() -> {
Image image1 = new Image(item.getOwnerProfilePicUrl());
images.put(item.getOwnerId(), image1);
Platform.runLater(() -> img.setImage(image1));
}).start();
} else {
img.setImage(image);
}
Circle clip = new Circle(25, 25, 25);
img.setClip(clip);
lblUsername.setOnMouseClicked(e->{
try {
Desktop.getDesktop().browse(new URI("https://www.instagram.com/" + item.getOwnerUsername()));
} catch (IOException | URISyntaxException exception) {
exception.printStackTrace();
}
});
setText(null);
setGraphic(pane);
setHeight(Region.USE_COMPUTED_SIZE);
}
}
}
我的问题在107-114行。在这行中,如果用户的图像在我下载之前没有下载并添加到hashmap图像接下来添加到视图并且它工作正常但是当滚动列表快速可能100图像下载并且因为在线程中下载图像添加到单元格ImageView甚至单元格消失并且不再显示例如我在索引10中有单元格X而在索引25中有单元格Y如果我在Y单元格中快速滚动X显示的某些时间图像
如果我不清楚解释,因为它是我的第一个JavaFX项目答案 0 :(得分:1)
(?=X)
缓存图像是一个好主意,但这样做错了。你加载&amp;将图像插入到不同线程上的地图中。由于您不同步访问,因此无法保证两个线程都以相同的方式查看映射。另外考虑private static Map<String, Image> images = new HashMap<>();
...
Image image;
if ((image = images.get(item.getOwnerId())) == null) {
img.setImage(null);
new Thread(() -> {
Image image1 = new Image(item.getOwnerProfilePicUrl());
images.put(item.getOwnerId(), image1);
Platform.runLater(() -> img.setImage(image1));
}).start();
} else {
img.setImage(image);
}
提供了一种异步加载Image
的方式这一事实,并不是必须自己创建一个线程。
此外,对于大量数据,您可能希望摆脱GUI当前未使用的图像。使用Image
s会是一个好主意
然而,主要问题是缺乏同步。如果您滚动得足够快,多个线程可能会为同一个单元格加载不同的图像,而您不知道,如果要启动的最后一个图像是最后一个执行SoftReference
的图像。可以并行加载来自相同源的多个图像
也无法重新使用缓存。如果应用程序的其他部分需要图像,则无法以这种方式重复使用它们。
我的建议:
Platform.runLater