为了找到监听器双重触发的原因,我做了一些调试,遵循layoutChildren
calls.
长话短说:每次点击Add Book
按钮,不是两个,而是三个变化itemProperty()
。要找到答案,请在侦听器和控制台上添加一些打印updateItem
method:
itemProperty().addListener((obs,oldv,newv)->{
System.out.println("change: "+getIndex()+", newv "+(newv != null?newv.getIsbn():"null")+", oldv: "+(oldv != null?oldv.getIsbn():"null"));
}
protected void updateItem(Book item, boolean empty){
super.updateItem(item, empty);
System.out.println("update item: "+(item!=null?item.getIsbn():"null"));
}
对于第一本书“123”,这是跟踪:
update item: null
change: 0, newv 123, oldv: null
update item: 123
change: -1, newv null, oldv: 123
update item: null
update item: null
change: 0, newv 123, oldv: null
update item: 123
由于新单元的创建,第一个更改按预期发生。但一旦创建,就会调用VirtualFlow.releaseCell()
索引为-1,最后all细胞被重新重建addLeadingCells()
and addTrailingCells()
in VirtualFlow.layoutChildren()
.
因此,根据设计,这些调用是无法避免的,这意味着如果您想确保只调用一次动画,则动画不应该位于侦听器中。
动画片
我提出了一个解决方案,意味着获取单元格并运行动画:
addButton.setOnAction((event)->{
data.add(new Book(String.valueOf(newBookIndex++),"test"));
VirtualFlow vf=(VirtualFlow)lv.lookup(".virtual-flow");
BookCell cell = (BookCell)vf.getCell(lv.getItems().size()-1);
cell.runAnimation();
});
这不是非常可取,因为使用VirtualFlow
,私有API。
通过查找 CSS 选择器可以避免这种情况indexed-cells
反而:
addButton.setOnAction((event)->{
data.add(new Book(String.valueOf(newBookIndex),"test"));
lv.lookupAll(".indexed-cell").stream()
.map(n->(BookCell)n)
.filter(c->c.getBook().getIsbn().equals(String.valueOf(newBookIndex)))
.findFirst().ifPresent(BookCell::runAnimation);
newBookIndex++;
});
请注意,由于我们使用查找,因此应在显示舞台后添加按钮的事件处理程序。
EDIT
OP 报告了滚动方面的奇怪问题。我不确定这是否是一个错误,但顶部的单元格是动画的,而不是底部的。
为了解决这个问题,我提出了这个解决方法,删除BookCell
动画,并为单元格创建一个动画,使用VirtualFlow
获取单元格并在必要时滚动。
首先,我们尝试查找滚动条是否不可见:我们使用淡入淡出过渡。但如果我们有滚动条,现在我们需要调用show()
滚动到最后一个单元格。一个短PauseTransition
在启动淡入淡出过渡之前完成这个技巧。
addButton.setOnAction((event)->{
addButton.setDisable(true);
data.add(new Book(String.valueOf(newBookIndex),"test"));
VirtualFlow vf=(VirtualFlow)lv.lookup(".virtual-flow");
if(!lv.lookup(".scroll-bar").isVisible()){
FadeTransition f = new FadeTransition();
f.setDuration(Duration.seconds(1));
f.setFromValue(0);
f.setToValue(1);
f.setNode(vf.getCell(lv.getItems().size()-1));
f.setOnFinished(t->{
newBookIndex++;
addButton.setDisable(false);
});
f.play();
} else {
PauseTransition p = new PauseTransition(Duration.millis(20));
p.setOnFinished(e->{
vf.getCell(lv.getItems().size()-1).setOpacity(0);
vf.show(lv.getItems().size()-1);
FadeTransition f = new FadeTransition();
f.setDuration(Duration.seconds(1));
f.setFromValue(0);
f.setToValue(1);
f.setNode(vf.getCell(lv.getItems().size()-1));
f.setOnFinished(t->{
newBookIndex++;
addButton.setDisable(false);
});
f.play();
});
p.play();
}
});
更新的代码
最后,这是我使用过的所有代码:
public class Main extends Application {
private int newBookIndex = 2;
public final ObservableList<Book> data = FXCollections.observableArrayList(
new Book("123","Hugo"), new Book("456","Harry Potter"));
private final ListView<Book> lv = new ListView<>();
@Override
public void start(Stage primaryStage) {
Button addButton = new Button("Add Book");
lv.setCellFactory(param->new BookCell());
lv.setItems(data);
Scene myScene = new Scene(new VBox(10,lv,addButton), 200, 200);
myScene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(myScene);
primaryStage.show();
addButton.setOnAction((event)->{
addButton.setDisable(true);
data.add(new Book(String.valueOf(newBookIndex),"test"));
VirtualFlow vf=(VirtualFlow)lv.lookup(".virtual-flow");
if(!lv.lookup(".scroll-bar").isVisible()){
FadeTransition f = new FadeTransition();
f.setDuration(Duration.seconds(1));
f.setFromValue(0);
f.setToValue(1);
f.setNode(vf.getCell(lv.getItems().size()-1));
f.setOnFinished(t->{
newBookIndex++;
addButton.setDisable(false);
});
f.play();
} else {
PauseTransition p = new PauseTransition(Duration.millis(20));
p.setOnFinished(e->{
vf.getCell(lv.getItems().size()-1).setOpacity(0);
vf.show(lv.getItems().size()-1);
FadeTransition f = new FadeTransition();
f.setDuration(Duration.seconds(1));
f.setFromValue(0);
f.setToValue(1);
f.setNode(vf.getCell(lv.getItems().size()-1));
f.setOnFinished(t->{
newBookIndex++;
addButton.setDisable(false);
});
f.play();
});
p.play();
}
});
}
class BookCell extends ListCell<Book>{
private final Text text = new Text();
private final HBox h = new HBox(text);
{
getStyleClass().add("book-list-cell");
}
@Override
protected void updateItem(Book item, boolean empty){
super.updateItem(item, empty);
if(item!=null && !empty){
text.setText(item.getIsbn());
setGraphic(h);
} else {
setGraphic(null);
setText(null);
}
}
}
class Book {
private Book(String isbn, String name) {
this.isbn.set(isbn);
this.name.set(name);
}
private final StringProperty name = new SimpleStringProperty();
public String getName() {
return name.get();
}
public void setName(String value) {
name.set(value);
}
public StringProperty nameProperty() {
return name;
}
private final StringProperty isbn = new SimpleStringProperty();
public String getIsbn() {
return isbn.get();
}
public void setIsbn(String value) {
isbn.set(value);
}
public StringProperty isbnProperty() {
return isbn;
}
}
public static void main(String[] args) {
launch(args);
}
}