Custom ListCell in a JavaFX ListView
In this Blog post I'll show you how to implement an custom ListView in JavaFX like the one below:
SOME PRE WORK
First, we need some Data. So I came up with a student class.
public class Student {
private static int studentIdAct = 0;
private int studentId;
private String name;
private GENDER gender;
enum GENDER {
MALE,
FEMALE
}
//... you get the idea
A JavaFX Application always consists out of a main class which extends Application.
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("/fxml/Main.fxml"));
primaryStage.setTitle("Turais");
primaryStage.setScene(new Scene(root, 640, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
In this class, the Main.fxml file is loaded. In our Main.fxml I defined a controller class for our Main.fxml.
public class Controller implements Initializable {
@FXML
private ListView<Student> listView;
private ObservableList<Student> studentObservableList;
public Controller() {
studentObservableList = FXCollections.observableArrayList();
//add some Students
studentObservableList.addAll(
new Student("John Doe", Student.GENDER.MALE),
new Student("Jane Doe", Student.GENDER.FEMALE),
new Student("Donte Dunigan", Student.GENDER.MALE),
new Student("Gavin Genna", Student.GENDER.MALE),
new Student("Darin Dear", Student.GENDER.MALE),
new Student("Pura Petty", Student.GENDER.FEMALE),
new Student("Herma Hines", Student.GENDER.FEMALE)
);
}
@Override
public void initialize(URL location, ResourceBundle resources) {
listView.setItems(studentObservableList);
listView.setCellFactory(studentListView -> new StudentListViewCell());
}
When you hop into the Main.fxml file you'll see that I've created an ListView in FXML and gave it the fx:id listview
. I add all the Students to an ObservableList, so when changes in that list occur, they get updated to the ListView.
My controller class implements Initalizable
, the method initialize is called after its root element has been completely loaded.
Set the ListCell to our ListView
Now, in my initalize method you see that I set the LisViews cell factory. In this example I do this with an lambda expression, but of course you can do it the old way.
The old way would be something like this:
listView.setCellFactory(new Callback<ListView<Student>, ListCell<Student>>() {
@Override
public ListCell<Student> call(ListView<Student> studentListView) {
return new StudentListViewCell();
}
});
Our custom StudentListViewCell
Fist of all you have to design your new ListCell. You can do this in SceneBuilder.
Our custom StudentListViewCell has to extend ListCell<Student>
In this class we also load an FXML file with our well known FXMLLoader
.
First we need to declare all the FXML Items we added in our custom cell.
@FXML
private Label label1;
@FXML
private Label label2;
@FXML
private FontAwesomeIconView fxIconGender;
@FXML
private GridPane gridPane;
Then we have to override the updateItem method.
@Override
protected void updateItem(Student student, boolean empty) {
super.updateItem(student, empty);
if(empty || student == null) {
setText(null);
setGraphic(null);
} else {
if (mLLoader == null) {
mLLoader = new FXMLLoader(getClass().getResource("/fxml/ListCell.fxml"));
mLLoader.setController(this);
try {
mLLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
label1.setText(String.valueOf(student.getStudentId()));
label2.setText(student.getName());
if(student.getGender().equals(Student.GENDER.MALE)) {
fxIconGender.setIcon(FontAwesomeIcon.MARS);
} else if(student.getGender().equals(Student.GENDER.FEMALE)) {
fxIconGender.setIcon(FontAwesomeIcon.VENUS);
} else {
fxIconGender.setIcon(FontAwesomeIcon.GENDERLESS);
}
setText(null);
setGraphic(gridPane);
}
}
And that's it ;)
Well now I explain what happens here.
First, the updateItem
method is called for every visible item in our listview. So you don't want much code in here, because if you scroll fast in your listview it could produce some lags.
So we only want something to happen if we have an student
and if our cell is not empty.
if(empty || student == null) {
setText(null);
setGraphic(null);
} else { //all the other code goes in the else block
//...
}
Then we have to load our custom fxml list cell.
if (mLLoader == null) {
mLLoader = new FXMLLoader(getClass().getResource("/fxml/ListCell.fxml"));
mLLoader.setController(this);
try {
mLLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
First we check if we don't have an FXMLLoader
. If we don't have one we load our custom cell, and set this class to our controller class for the cell.
After the cell has been loaded, we can do some simple logic in our ListCell. In my example I set the first label to the student id, the second label to the student name, and then I set the icon in my cell to the students gender.
label1.setText(String.valueOf(student.getStudentId()));
label2.setText(student.getName());
if(student.getGender().equals(Student.GENDER.MALE)) {
fxIconGender.setIcon(FontAwesomeIcon.MARS);
} else if(student.getGender().equals(Student.GENDER.FEMALE)) {
fxIconGender.setIcon(FontAwesomeIcon.VENUS);
} else {
fxIconGender.setIcon(FontAwesomeIcon.GENDERLESS);
}
setText(null);
setGraphic(gridPane);
At the end we have to set the text to null and set our GridPane as the Graphic.
Of course you could use everything else, for example an AnchorPane or something similar.
The code is available at GitHub - TuraisJavaFxExamples.