在Javafx中为新的ListView条目设置动画

时间:2014-11-25 15:30:34

标签: java listview animation 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();
    }
}

我试图在itemProperty中添加一个监听器,但是我得到了一些奇怪的行为。首先,每次添加条目时,监听器都会触发两次: enter image description here 这导致动画播放两次。即使在这种情况下几乎不引人注意,这可能有点尴尬。

此外,在列表开始滚动后,动画有时重复显示可见条目: enter image description here

说实话,如果项目再次可见,我不一定会想到动画重复,但就目前而言,单元格动画是非常不可靠的。

我知道这些细胞实际上是受控的,并且会随着时间的推移重复使用。但我很难找到一种可靠的方法来确定屏幕上是否出现非空单元格。

问题

  1. 为什么听众会两次开火?
  2. 观察屏幕上单元格出现/消失的最佳方法是什么
  3. 有没有更好的方法将动画添加到ListView?
  4. 完整代码

    它可能会有所帮助,所以这里是我的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);
        }
    }
    

    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%;
    }
    

1 个答案:

答案 0 :(得分:5)

为了找到监听器双重触发的原因,我在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"));
}

对于第一本书“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,最后所有使用addLeadingCells()和{{1}再次重建单元格在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(); }); 私有API以来,这不太合适。

通过查找CSS选择器VirtualFlow可以避免这种情况:

indexed-cells

请注意,由于我们正在使用查找,因此应在显示该阶段后添加该按钮的事件处理程序。

修改

OP滚动报告了一些奇怪的问题。我不确定这是不是一个错误,但顶部单元格是动画而不是底部单元格。

要解决此问题,我已经采用此解决方法,删除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++; }); 动画,并为单元格创建一个动画,使用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();
    }
});