In my current project with JavaFX, we need cells to flash when their value has been updated so that a user would notice the change. Although this sounds like an easy piece of code to write, it took three tries to get this right.
The first versions that I wrote used CSS manipulation and timer which updated the opacity value. This created a huge overhead since CSS handling in JavaFX is not effective and should be avoided in cell updateItem method if possible.
Eventually, I ended up with a solution that uses StackPane with BorderPane as a background and FadeTransition to handle the animation. Hopefully it helps if you are building something similar.
Conversation and examples can be found from Oracle forum:
https://community.oracle.com/thread/2312537
import java.util.Comparator; import javafx.animation.FadeTransition; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.ContentDisplay; import javafx.scene.control.TableCell; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.BorderPane; import javafx.scene.layout.CornerRadii; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.util.Duration; import com.sun.javafx.scene.control.skin.LabeledText; /** * * @author jaakkju * @param <S> * @param <T> */ public class FlashingTableCell<S, T> extends TableCell<S, T> { private static final Color INCREASE_HIGHLIGHT_COLOR = Color.rgb(0, 255, 0, 0.8); private static final Color DECREASE_HIGHLIGHT_COLOR = Color.rgb(255, 0, 0, 0.8); private static final Color HIGHLIGHT_COLOR = Color.rgb(255, 255, 0, 0.8); private static final Duration HIGHLIGHT_TIME = Duration.millis(300); private final Background bgIncrease = new Background(new BackgroundFill(INCREASE_HIGHLIGHT_COLOR, CornerRadii.EMPTY, Insets.EMPTY)); private final Background bgDecrease = new Background(new BackgroundFill(DECREASE_HIGHLIGHT_COLOR, CornerRadii.EMPTY, Insets.EMPTY)); private final Background bgChange = new Background(new BackgroundFill(HIGHLIGHT_COLOR, CornerRadii.EMPTY, Insets.EMPTY)); private final BorderPane background = new BorderPane(); private final LabeledText lblText = new LabeledText(this); private final FadeTransition animation = new FadeTransition(HIGHLIGHT_TIME, background); private final StackPane container = new StackPane(); private T prevValue; private S prevItem; final private Comparator<T> comparator; public FlashingTableCell(Comparator<T> comparator, Pos alignment) { super(); this.comparator = comparator; lblText.textProperty().bindBidirectional(textProperty()); this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); setPadding(Insets.EMPTY); container.getChildren().addAll(background, lblText); container.setAlignment(alignment); setGraphic(container); } @Override protected void updateItem(T value, boolean empty) { super.updateItem(value, empty); S currentItem = getTableRow() != null && getTableRow().getItem() != null ? (S) getTableRow().getItem() : null; /* * We check that the value has been updated and that the row model/item * under the cell is the same. JavaFX table reuses cells so item is not * always the same! */ boolean valueChanged = (prevValue == null && value != null) || (value != null && (prevValue.hashCode() != value.hashCode())); boolean sameItem = currentItem != null && prevItem != null && currentItem == prevItem; if (valueChanged && sameItem) { if (comparator != null) { int compare = comparator.compare(value, prevValue); if (compare > 0) { background.setBackground(bgIncrease); } else if (compare < 0) { background.setBackground(bgDecrease); } } else { background.setBackground(bgChange); } animation.setFromValue(1); animation.setToValue(0); animation.setCycleCount(1); animation.setAutoReverse(false); animation.playFromStart(); } prevValue = value; prevItem = currentItem; } }