Share the post "How To Create A ListView With Title Header That Expands & Collapses In Java FX"
There is no component that does this within the API of Java FX and Google searches gave me nothing so instead, I created my own.
The concept is the same with tables where rows are grouped and row headers give you the option to expand and collapse them.
For List View, I had to make my own CellFactory in order to make this work. I was surprised that I was able to make one. If this was Swing, it might have been different.
This is how my component looks like.
I created an entity class that contains the title and description. Let us call the class, Note. The class has an extra boolean parameter which I use told hold the state of the title if it is expanded or not.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public class Note { private String title, note; // set to true by default private boolean hidden = true; public Note(String title, String note) { this.title = title; this.note = note; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getNote() { return note; } public void setNote(String note) { this.note = note; } public void setHidden(boolean hidden) { this.hidden = hidden; } public boolean isHidden() { return hidden; } } |
And this is how I configured my List View.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
listview.setCellFactory(new Callback<ListView<Note>, ListCell<Note>>() { @Override public ListCell<Note> call(final ListView<Note> lv) { ListCell<Note> cell = new ListCell<Note>(){ @Override protected void updateItem(final Note item, boolean empty) { super.updateItem(item, empty); final VBox vbox = new VBox(); setGraphic(vbox); if (item != null && getIndex() > -1) { final Label labelHeader = new Label(item.getTitle()); labelHeader.setGraphic(createArrowPath(height, false)); labelHeader.setGraphicTextGap(10); labelHeader.setId("tableview-columnheader-default-bg"); labelHeader.setPrefWidth(listview.getWidth() - 10); labelHeader.setPrefHeight(height); labelHeader.setOnMouseClicked(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent me) { item.setHidden(item.isHidden() ? false : true); if (item.isHidden()) { labelHeader.setGraphic(createArrowPath(height, false)); vbox.getChildren().remove(vbox.getChildren().size() - 1); } else { labelHeader.setGraphic(createArrowPath(height, true)); vbox.getChildren().add(new Label(item.getNote())); } } }); vbox.getChildren().add(labelHeader); } } }; return cell; } }); |
For each entry in the list, I used a vertical box to display the contents. When the title is clicked to expand the description, the VBox will add a child so that it will be shown. If it is collapse, it will be removed.
Simple, right? Cell factories are very handy and flexible in such a way that you can render what you want displayed to the user any way you want to.
If you noticed, there is a method called createArrowPath(). That is a convenience method I made to return an SVGPath object to be used as the arrow icon beside the title header. It takes a parameter height which should be the height of the List View.
|
1 2 3 4 5 6 7 8 9 10 11 |
private SVGPath createArrowPath(int height, boolean up) { SVGPath svg = new SVGPath(); int width = height / 4; if (up) svg.setContent("M" + width + " 0 L" + (width * 2) + " " + width + " L0 " + width + " Z"); else svg.setContent("M0 0 L" + (width * 2) + " 0 L" + width + " " + width + " Z"); return svg; } |