Shr*_*ser 5 java animation listview javafx javafx-8
嗨,
我正在尝试编写一个应用程序,ListView中的每个新条目都会被动画化.这是我的代码:
public class BookCell extends ListCell<Book>
{
private Text text;
private HBox h;
public BookCell()
{
this.text = new Text();
this.h = new HBox();
this.h.getChildren().add(text);
super.getStyleClass().add("book-list-cell");
super.itemProperty().addListener((obs,oldv,newv)->{
if(newv != null )
{
if(getIndex() == this.getListView().getItems().size()-1 )
{
//why does this get called twice for each update?
System.out.println("isbn = "+newv.getIsbn().get() + " lastIndexOf=" + this.getListView().getItems().lastIndexOf(newv)+" Index="+getIndex()+" size="+this.getListView().getItems().size());
runAnimation();
}
}
});
this.getChildren().add(h);
}
@Override
protected void updateItem(Book item, boolean empty)
{
super.updateItem(item, empty);
if(!empty)
super.setGraphic(h);
text.setText(item == null ? "" : item.getIsbn().get());
}
public void runAnimation()
{
FadeTransition f = new FadeTransition();
f.setFromValue(0);
f.setToValue(1);
f.setNode(this);
f.play();
}
}
Run Code Online (Sandbox Code Playgroud)
我试图在itemProperty中添加一个监听器,但是我得到了一些奇怪的行为.首先,每次添加条目时,监听器都会触发两次:
这导致动画播放两次.即使在这种情况下几乎不可察觉,这可能有点尴尬.
此外,在列表开始滚动之后,有时会为可见条目重复动画:

说实话,如果一个项目再次可见,我不一定会想到动画重复,但就目前而言,单元格动画是非常不可靠的.
我知道这些细胞实际上是受控的,并且会随着时间的推移重复使用.但是我很难找到一种可靠的方法来确定屏幕上是否出现非空单元格.
它可能会有所帮助,所以这里是我的CSS和主文件.
Main.java
public class Main extends Application
{
static int newBookIndex = 1;
public static final ObservableList<Book> data = FXCollections.observableArrayList();
@Override
public void start(Stage primaryStage)
{
try
{
GridPane myPane = (GridPane)FXMLLoader.load(getClass().getResource("quotes.fxml"));
ListView<Book> lv = (ListView<Book>) myPane.getChildren().get(0);
Button addButton = (Button) myPane.getChildren().get(1);
addButton.setText("Add Book");
addButton.setOnAction((event)->{
data.add(new Book(String.valueOf(newBookIndex++),"test"));
});
lv.setEditable(true);
data.addAll(
new Book("123","Hugo"),
new Book("456","Harry Potter")
);
lv.setItems(data);
lv.setCellFactory((param)->return new BookCell());
Scene myScene = new Scene(myPane);
myScene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(myScene);
primaryStage.show();
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
Run Code Online (Sandbox Code Playgroud)
application.css(基于https://github.com/privatejava/javafx-listview-animation)
.book-list-cell:odd{
-fx-background-image: url('./wooden2.png') ,url('./gloss.png');
-fx-background-repeat: repeat,no-repeat;
}
.book-list-cell:empty {
-fx-background-image: url('./gloss.png');
-fx-background-repeat: repeat;
}
.book-list-cell {
-fx-font-size:45px;
-fx-font-family: 'Segoe Script';
}
.book-list-cell:selected,.book-list-cell:selected:hover{
-fx-border-color:linear-gradient(to bottom,transparent,rgb(22,22,22,1));
-fx-border-width:2 2 2 10px;
-fx-effect:innershadow( three-pass-box,#764b0c,30,0.3,0,0);
}
.book-list-cell:hover{
-fx-border-color:rgb(255,255,255,0.7);
-fx-border-width:1 1 1 10px;
}
.book-list-cell{
-fx-background-image: url('./wooden.png') ,url('./gloss.png');
-fx-background-repeat: repeat,no-repeat;
-fx-background-position:left top;
-fx-background-size: auto,100% 40%;
}
Run Code Online (Sandbox Code Playgroud)
为了找到监听器双重触发的原因,我在layoutChildren调用之后做了一些调试.
长话短说:对于每次点击Add Book按钮,没有两个,但有三个变化itemProperty().要找到答案,请在侦听器和updateItem方法上向控制台添加一些打印:
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"));
}
Run Code Online (Sandbox Code Playgroud)
对于第一本书"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
Run Code Online (Sandbox Code Playgroud)
由于创建了新的单元格,第一次更改按预期发生.但是,一旦它的创建,这是随后调用VirtualFlow.releaseCell()与指数-1,最后所有的细胞与重建一遍addLeadingCells(),并addTrailingCells()在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();
});
Run Code Online (Sandbox Code Playgroud)
由于使用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++;
});
Run Code Online (Sandbox Code Playgroud)
请注意,由于我们正在使用查找,因此应在显示阶段后添加按钮的事件处理程序.
编辑
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();
}
});
Run Code Online (Sandbox Code Playgroud)
更新的代码
最后,这是我使用过的所有代码:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1528 次 |
| 最近记录: |