JavaFX listview загружает неправильное изображение в пользовательскую ячейку с потоком - PullRequest
0 голосов
/ 16 мая 2018

Я работаю над своим первым проектом JavaFX и у меня проблемы с фабрикой пользовательских ячеек списка. это мой код

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, даже ячейка исчезнет и больше не показывается, например, у меня есть ячейка X в индексе 10 и ячейка Y в индексе 25, если я быстро прокручиваю некоторое время, изображение X отображается в ячейке Y

извините, если я не объясню ясно, потому что это мой первый проект JavaFX

1 Ответ

0 голосов
/ 16 мая 2018
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 обеспечивает способ асинхронной загрузки Image, нет необходимости создавать поток самостоятельно.
Кроме того, для больших объемов данных вы можете избавиться от изображений, которые в настоящее время не используются в графическом интерфейсе. Использование SoftReference s было бы хорошей идеей.
Однако основной проблемой является отсутствие синхронизации. Если вы выполняете прокрутку достаточно быстро, несколько потоков могут загружать разные изображения для одной и той же ячейки, и вы не знаете, является ли последняя, ​​которая будет запущена, последней для выполнения Platform.runLater. Несколько изображений из одного источника могут быть загружены параллельно.
Также нет возможности повторно использовать ваш кеш. Если изображения нужны в какой-то другой части вашего приложения, их невозможно использовать таким образом.

Моя рекомендация:

...

import java.awt.Desktop; // importing more classes from awt than neccessary could result in problems
...

public class DischargeComment extends ListCell<Comment> {

    ...

    /**
     * Constructor to pass external cache
     * @param cache 
     */
    public DischargeComment(Map<String, SoftReference<Image>> cache) {
        if (cache == null) {
            throw new IllegalArgumentException();
        }
        this.cache = cache;
    }

    /**
     * constructor using the default cache
     */
    public DischargeComment() {
        this(getDefaultCache());
    }

    private final Map<String, SoftReference<Image>> cache;

    private static Map<String, SoftReference<Image>> defaultCache;

    private static final URL FXML_URL = DischargeComment.class.getResource("cell/discharge_comment.fxml");

    public static Map<String, SoftReference<Image>> getDefaultCache() {
        if (defaultCache == null) {
            defaultCache = new HashMap<>();
        }
        return defaultCache;
    }

    public static String search = "";
    private boolean loaded = false; // no need for a reference to fxmlloader here

    @Override
    protected void updateItem(Comment item, boolean empty) {
        super.updateItem(item, empty);

        if (empty || item == null) {
            setText(null);
            setGraphic(null);
        } else {
            if (!loaded) {
                FXMLLoader mLLoader = new FXMLLoader(FXML_URL);
                mLLoader.setController(this);

                try {
                    mLLoader.load();
                    img.setClip(new Circle(25, 25, 25));
                    loaded = true;
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }

            ...

            // use single access here
            // also use url as key
            cache.compute(item.getOwnerProfilePicUrl(), (key, value) -> {
                Image image = null;
                if (value != null) {
                    image = value.get();
                }
                if (image == null) {
                    image = new Image(key, true); // load image in background
                    value = new SoftReference<>(image);
                }
                img.setImage(image);
                return value;
            });

            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);
            setPrefHeight(Region.USE_COMPUTED_SIZE); // don't set height to -1
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...