【发布时间】:2023-03-06 15:17:01
【问题描述】:
我使用两个 JavaFX Timeline 对象在 ListCell 内创建一个倒数计时器和一个进度条(即缩小的 JavaFX 矩形)。当计时器达到零时,矩形的宽度变为零,并且单元格从 ListView 中删除。但是,整个 ListView 会自动刷新,从而导致另一个单元格中的时间轴重置。下面是从两个单元格开始的样子:
在代码中,updateItem 方法从模型中获取秒数并设置计时器标签的文本。当从 ListView 中删除一个单元格时,将调用 updateItem 并使用包含字符串“20”的 CellData 对象来重置时间线。但我希望带有未完成计时器的单元继续;不是从头开始。这是最小的、可重现的例子:
public class AnimatedCells extends Application {
public class MyCell extends ListCell<CellData> {
private CellComponent cc;
private Timeline rectTimeline, timerTimeline;
private KeyValue rectWidth;
public MyCell() {
cc = new CellComponent();
rectTimeline = new Timeline();
timerTimeline = new Timeline();
rectWidth = new KeyValue( cc.getRect().widthProperty(), 0 );
}
@Override
protected void updateItem( CellData item, boolean empty ) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
if ( item.getData() != null ) {
System.out.println(item.getData());
cc.getTimer().setText( item.getData() );
rectTimeline.getKeyFrames().add( new KeyFrame( Duration.seconds( Double.parseDouble( cc.getTimer().getText() ) ), rectWidth ) );
timerTimeline.getKeyFrames().addAll(
new KeyFrame( Duration.seconds( 0 ),
event -> {
int timerSeconds = Integer.parseInt( cc.getTimer().getText() );
if ( timerSeconds > 0 ) {
cc.getTimer().setText( Integer.toString(--timerSeconds) );
}
else {
rectTimeline.stop();
timerTimeline.stop();
super.getListView().getItems().remove(this.getItem());
}
}),
new KeyFrame( Duration.seconds( 1 ) ) );
timerTimeline.setCycleCount( Animation.INDEFINITE );
setGraphic( cc.getCellPane() );
rectTimeline.play();
timerTimeline.play();
}
}
}
}
public class CellComponent {
@FXML
private Pane cellPane;
@FXML
private Label timer;
@FXML
private Rectangle rect;
public CellComponent() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/listcell.fxml"));
fxmlLoader.setController(this);
try
{
fxmlLoader.load();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public Pane getCellPane() {
return cellPane;
}
public void setCellPane(Pane cellPane) {
this.cellPane = cellPane;
}
public Label getTimer() {
return timer;
}
public void setTimer(Label timer) {
this.timer = timer;
}
public Rectangle getRect() {
return rect;
}
public void setRect(Rectangle rect) {
this.rect = rect;
}
}
public class CellData {
private String data;
public CellData() {
super();
}
public CellData(String data) {
super();
this.setData(data);
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
@FXML
private ListView<CellData> listView;
private ObservableList<CellData> observableList = FXCollections.observableArrayList();
public void initialize() {
observableList.add( new CellData("20") );
observableList.add( new CellData("10") );
listView.setItems( observableList );
listView.setCellFactory( listView -> {
MyCell cell = new MyCell();
return cell;
});
}
@Override
public void start(Stage stage) throws Exception {
Parent root = new FXMLLoader(getClass().getResource("/fxml/listmanager.fxml")).load();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
另外,为什么只有两行数据的时候,updateItem 会在启动时调用 3 次?上面代码中唯一的打印语句输出:
20
20
10
我使用的是 Java 11.0.6。
【问题讨论】:
-
updateItem()何时被调用的细节是一个故意不公开的实现细节;但一般来说,它可以在列表视图的生命周期内频繁调用。例如,当用户滚动列表单元格时,这些单元格将被重新用于显示不同的项目,并调用updateItem()以使用不同的项目更新单元格。在这种情况下,您显然不想重新启动计时器。通常,计时器的当前值是正在显示的 数据 的一部分,因此它应该是CellData类的一部分。 -
我认为这里真正的问题是“您真正希望计时器何时启动?”。显然不是在调用
updateItem()时(因为你不知道,也不可能真正知道什么时候发生),但你从不告诉我们什么时候发生。 -
我希望在启动时视图准备就绪时启动计时器。如果我要在运行时向列表中添加第三个单元格,我还希望启动一个计时器。
-
大概包括添加到列表中但由于滚动到视图之外而不可见的项目?
-
是的,如果单元格被添加到列表中但不可见,计时器应该启动。