據我所知,沒有API來做到這一點。由於單元重用機制(在Cell
documentation中描述),添加此功能有點棘手。
您需要在列表視圖中使用單元格工廠,以跟蹤列表中哪些項目具有活動單元格,以及這些單元格的邊界是什麼(因爲在滾動期間可能有一個項目可能仍有單元格,但該單元格可能會完全滾動到視圖外)。您可以使用ObservableMap
將項目映射到其單元格的邊界。當單元格重新使用時,您需要更新地圖,刪除舊項目併爲新項目添加地圖條目,並且如果單元格的邊界發生更改或者單元格在場景內移動,還要更新地圖。
下面是一個這樣做的例子。我把核心功能放到了一個自定義的單元工廠中,並暴露了可觀察的地圖。作爲一個例子,我創建了一個包含100個項目的ListView<Integer>
,並演示了創建第二個列表視圖的功能,其中的內容是具有完全顯示的單元格的項目。第二個列表視圖顯示這些項目及其邊界(轉換爲包含單元格的列表視圖中的座標)。
從理論上講,這是非常高性能的,因爲它通過觀察大量數據來更新第二個列表視圖;這只是因爲它只對每個單元(而不是每個單元)都這樣做。在我的系統上,它似乎運行良好,並且由於性能僅是單元數量的函數,因此它應該適用於大型列表。
import java.util.function.Function;
import java.util.stream.Collectors;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener.Change;
import javafx.collections.ObservableMap;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TrackCellsInListView extends Application {
@Override
public void start(Stage primaryStage) {
// main list view:
ListView<Integer> listView = new ListView<>();
for (int i = 1 ; i <= 100; i++) {
listView.getItems().add(i);
}
// create a cell factory that tracks items which have cells and the bounds of their cells:
TrackingListCellFactory<Integer> cellFactory = new TrackingListCellFactory<>(i -> "Item "+i);
listView.setCellFactory(cellFactory);
// map from items with cells to bounds of the cells (in scene coordinates):
ObservableMap<Integer, Bounds> boundsByItem = cellFactory.getBoundsByItem();
// list view which will display which items have cells that are completely displayed
// (i.e. whose bounds are completely contained in the list view bounds):
ListView<Integer> visibleCells = new ListView<>();
// cell factory for second list cell displays item and its bounds (translated to
// list view coordinates):
visibleCells.setCellFactory(lv -> {
ListCell<Integer> cell = new ListCell<>();
cell.textProperty().bind(Bindings.createStringBinding(
() -> {
if (cell.getItem()==null) {
return null ;
}
Bounds b = boundsByItem.get(cell.getItem());
if (b == null) {
return null ;
}
Bounds bounds = listView.sceneToLocal(b);
return String.format("%d: [%.1f, %.1f, %.1f, %.1f]", cell.getItem(),
bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY());
}, cell.itemProperty(), boundsByItem));
return cell ;
});
// keep list of items in second list view up to date by observing map:
boundsByItem.addListener((Change<? extends Integer, ? extends Bounds> c) -> {
Bounds listBounds = listView.localToScene(listView.getBoundsInLocal());
visibleCells.getItems().setAll(
boundsByItem.keySet().stream()
.filter(s -> listBounds.contains(boundsByItem.get(s)))
.sorted()
.collect(Collectors.toList()));
System.out.println();
});
// usual UI setup:
Scene scene = new Scene(new HBox(5, listView, visibleCells));
primaryStage.setScene(scene);
primaryStage.show();
}
private static class TrackingListCellFactory<T> implements Callback<ListView<T>, ListCell<T>> {
// function for mapping item to text to display:
private Function<T,String> textFunction ;
// map items which have cells to bounds of those cell in scene coordinates:
private ObservableMap<T, Bounds> boundsByItem = FXCollections.observableHashMap();
TrackingListCellFactory(Function<T,String> textFunction) {
this.textFunction = textFunction ;
}
// default text function just calls toString():
TrackingListCellFactory() {
this(T::toString);
}
public ObservableMap<T, Bounds> getBoundsByItem() {
return boundsByItem ;
}
@Override
public ListCell<T> call(ListView<T> param) {
//create cell that displays text according to textFunction:
ListCell<T> cell = new ListCell<T>() {
@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
setText(item == null ? null : textFunction.apply(item));
}
};
// add and remove from map when cell is reused for different item:
cell.itemProperty().addListener((obs, oldItem, newItem) -> {
if (oldItem != null) {
boundsByItem.remove(oldItem);
}
if (newItem != null) {
boundsByItem.put(newItem, cell.localToScene(cell.getBoundsInLocal()));
}
});
// update map when bounds of item change
ChangeListener<Object> boundsChangeHandler = (obs, oldValue, newValue) -> {
T item = cell.getItem() ;
if (item != null) {
boundsByItem.put(item, cell.localToScene(cell.getBoundsInLocal()));
}
};
// must update either if cell changes bounds, or if cell moves within scene (e.g.by scrolling):
cell.boundsInLocalProperty().addListener(boundsChangeHandler);
cell.localToSceneTransformProperty().addListener(boundsChangeHandler);
return cell;
}
}
public static void main(String[] args) {
launch(args);
}
}
沒有您需要的API的API。 Tomas Mikula的第三方庫[Flowless](https:// github。com/TomasMikula/Flowless)提供了一些類似於你正在尋找的功能(我認爲)。 –
請注意,在很多問題中,您會將數據與數據視圖混淆。例如:「我嘗試向'ObservableList'添加一個'ListChangeListener' ...該事件處理程序似乎沒有給出特定行的x,y座標」。問題在於列表中的項目沒有座標(*單元格*有座標,而不是項目):該項目可能甚至沒有任何相應的單元格或任何UI。請參閱['Cell'文檔](http://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Cell.html)。這些都不會阻止'getVisibleRowIndices()'API;但它只是不存在 –
另請參見http://stackoverflow.com/questions/18965871/how-to-get-position-of-an-item-in-listview-in-javafx –