我使用两个JavaFX Timeline对象在ListCell中创建一个倒计时计时器和一个进度条(即缩小的JavaFX矩形)。当计时器达到零时,矩形的宽度变为零,单元格将从ListView中删除。但是,整个ListView会自动刷新,导致另一个单元格中的时间线重置。以下是从两个单元格开始的情况:
在代码中,updateItem方法从模型中获取秒数,并设置计时器标签的文本。从ListView中删除单元格后,将使用包含字符串“20”的CellData对象调用updateItem,该字符串将重置时间线。但我希望有未完成计时器的电池继续运行;不是从头开始。下面是一个最小的、可重复的例子:
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三次?上述代码中的lone print语句输出:
20
20
10
我使用的是Java 11.0.6。
如果有人感兴趣,我最终使用了一个单列、多行的GridPane。没有updateItem
方法可以处理,所以我可以更好地控制一切。结果是,网格中的每一行都独立运行,这就是我想要的。我可以在同一行有多个倒计时和进度条。更重要的是,我可以添加或删除一行,而无需重置所有正在运行的时间线。GridPane API也更容易使用。
单元格的工作是确定如何显示数据。不能在单元格的updateItem
方法中更改基础数据,因为单个单元格实例可以用于多个值。
从Cell的留档:
由于TreeView、ListView、TableView和其他此类控件可能用于显示难以置信的大量数据,因此不可能为控件中的每个项创建实际的单元格。我们只使用很少的单元来表示非常大的数据集。每个电池都是“回收”的,或者重复使用。
您需要将所有时间线移动到CellData对象中。单元格不能修改这些时间线或ListView的项目。
使数据对象具有可观察的属性是惯例,也是有用的,因此可视化组件可以将可视化属性绑定到它们。
CellData对象不应该知道包含它们的ListView。您可以为每个CellData实例提供一个消费者或其他函数,用于删除数据。
因此,对于数据类,您需要这样的内容:
public static class CellData {
private final StringProperty data;
private final ReadOnlyDoubleWrapper secondsRemaining;
private final Timeline timeline = new Timeline();
public CellData(Consumer<? super CellData> finished) {
data = new SimpleStringProperty(this, "data");
secondsRemaining =
new ReadOnlyDoubleWrapper(this, "secondsRemaining");
data.addListener((o, oldData, newData) -> {
try {
double startTime = Double.parseDouble(newData);
timeline.stop();
timeline.getKeyFrames().setAll(
new KeyFrame(Duration.ZERO,
new KeyValue(secondsRemaining, startTime)),
new KeyFrame(Duration.seconds(startTime),
new KeyValue(secondsRemaining, 0.0))
);
timeline.setOnFinished(e -> finished.accept(this));
timeline.play();
} catch (NumberFormatException e) {
System.err.println(
"Cannot start timer for invalid seconds value: " + e);
Platform.runLater(() -> finished.accept(this));
}
});
}
public CellData(String data,
Consumer<? super CellData> finished) {
this(finished);
this.setData(data);
}
public StringProperty dataProperty() {
return data;
}
public String getData() {
return data.get();
}
public void setData(String data) {
this.data.set(data);
}
public ReadOnlyDoubleProperty secondsRemainingProperty() {
return secondsRemaining.getReadOnlyProperty();
}
public double getSecondsRemaining() {
return secondsRemaining.get();
}
}
一个实例将使用如下内容构建:
new CellData(secondsText,
c -> list.getItems().remove(c)));
这使得cell类更加简单:
public static class MyCell extends ListCell<CellData> {
private CellComponent cc;
public MyCell() {
cc = new CellComponent();
}
@Override
protected void updateItem(CellData item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
cc.getTimer().textProperty().unbind();
cc.getTimer().textProperty().bind(
item.secondsRemainingProperty().asString("%.0f"));
cc.getRect().widthProperty().unbind();
cc.getRect().widthProperty().bind(
item.secondsRemainingProperty().multiply(10));
setText(null);
setGraphic(cc.getCellPane());
}
}
}
把它们放在一起看起来是这样的。(为了简单性和再现性,我用内联代码替换了FXML。)
import java.util.function.Consumer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.animation.Timeline;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.beans.property.StringProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import javafx.util.converter.DoubleStringConverter;
public class AnimatedCells
extends Application {
@Override
public void start(Stage stage) {
ListView<CellData> list = new ListView<>();
list.setCellFactory(l -> new MyCell());
TextField secondsField = new TextField();
Button newTimerButton = new Button("Create");
newTimerButton.setOnAction(e -> {
list.getItems().add(
new CellData(secondsField.getText(),
c -> list.getItems().remove(c)));
});
secondsField.setOnAction(e -> {
list.getItems().add(
new CellData(secondsField.getText(),
c -> list.getItems().remove(c)));
});
HBox newButtonPane = new HBox(6, secondsField, newTimerButton);
newButtonPane.setPadding(new Insets(12));
stage.setScene(new Scene(
new BorderPane(list, null, null, newButtonPane, null)));
stage.setTitle("Animated Cells");
stage.show();
}
public static class MyCell extends ListCell<CellData> {
private CellComponent cc;
public MyCell() {
cc = new CellComponent();
}
@Override
protected void updateItem(CellData item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
cc.getTimer().textProperty().unbind();
cc.getTimer().textProperty().bind(
item.secondsRemainingProperty().asString("%.0f"));
cc.getRect().widthProperty().unbind();
cc.getRect().widthProperty().bind(
item.secondsRemainingProperty().multiply(10));
setText(null);
setGraphic(cc.getCellPane());
}
}
}
public static class CellData {
private final StringProperty data;
private final ReadOnlyDoubleWrapper secondsRemaining;
private final Timeline timeline = new Timeline();
public CellData(Consumer<? super CellData> finished) {
data = new SimpleStringProperty(this, "data");
secondsRemaining =
new ReadOnlyDoubleWrapper(this, "secondsRemaining");
data.addListener((o, oldData, newData) -> {
try {
double startTime = Double.parseDouble(newData);
timeline.stop();
timeline.getKeyFrames().setAll(
new KeyFrame(Duration.ZERO,
new KeyValue(secondsRemaining, startTime)),
new KeyFrame(Duration.seconds(startTime),
new KeyValue(secondsRemaining, 0.0))
);
timeline.setOnFinished(e -> finished.accept(this));
timeline.play();
} catch (NumberFormatException e) {
System.err.println(
"Cannot start timer for invalid seconds value: " + e);
Platform.runLater(() -> finished.accept(this));
}
});
}
public CellData(String data,
Consumer<? super CellData> finished) {
this(finished);
this.setData(data);
}
public StringProperty dataProperty() {
return data;
}
public String getData() {
return data.get();
}
public void setData(String data) {
this.data.set(data);
}
public ReadOnlyDoubleProperty secondsRemainingProperty() {
return secondsRemaining.getReadOnlyProperty();
}
public double getSecondsRemaining() {
return secondsRemaining.get();
}
}
public static class CellComponent {
private final Pane cellPane;
private final Label timer;
private final Rectangle rect;
public CellComponent() {
// For the sake of example, I'm building this in code rather than
// with FXML.
//FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/listcell.fxml"));
//fxmlLoader.setController(this);
//try
//{
// fxmlLoader.load();
//}
//catch (IOException e)
//{
// throw new RuntimeException(e);
//}
rect = new Rectangle(1, 40);
rect.setArcWidth(20);
rect.setArcHeight(20);
rect.setStyle(
"-fx-fill: red; -fx-stroke: black; -fx-stroke-width: 2;");
timer = new Label(" ");
timer.setStyle("-fx-font-size: 18pt; -fx-alignment: center;");
cellPane = new HBox(24, timer, rect);
cellPane.setStyle("-fx-alignment: center-left;");
}
public Pane getCellPane() {
return cellPane;
}
public Label getTimer() {
return timer;
}
public Rectangle getRect() {
return rect;
}
}
public static class Main {
public static void main(String[] args) {
Application.launch(AnimatedCells.class, args);
}
}
}
我设法创建了一个自定义列表视图,并为每一行添加了一个删除按钮。删除按钮运行良好,当我单击按钮时,该行会从数据库中删除。问题是,当我删除一行时,它会保留在列表中,直到我通过移动到另一个片段来刷新片段并返回,然后它从列表中消失。我想要的是,当我删除一行时,它会立即从列表视图中消失。我尝试了一些解决方案来刷新整个片段或删除完成后的列表视图,但它们不起作用! 这是我为适配器类中的删除按钮编写的代码 之后
我将设置为常量,设置为 但表视图单元格仍未调整为我的单元格的详细信息文本标签的大小。我哪里出错了? UITableViewCell类:
我希望我的swift代码在每次按下按钮时都添加一个新的tableview单元格。你可以在下面的gif中看到我想要的东西。这段代码应该在func-tableView(_tableView:UITableView,cellForRowAt-indexPath:indexPath)中添加按钮- 在此输入图像描述
如何自我调整uItableview单元格元素。 自动调整UITableViewCell大小。
使用WAMP在Windows 7上工作。 更改视图代码不会反映在浏览器中。 我试过: > php工匠缓存:清除 php artisan视图:清除 作曲家杜普莫托洛德 删除了存储/框架/视图下的所有内容 重新启动计算机 在我的php中。ini操作缓存已禁用: opcache.enable=0 没有任何帮助,这是非常令人沮丧的。 我错过了什么?
我有一个使用rowedit的p:datatable,可以正常工作,但是当您删除一个条目时,datatable没有更新。 xhtml