The app should render scroll pane content 'faster' to increase scrolling speed.
You can achieve it reducing the number of objects to be rendered or/and decreasing the number of 'repaint' method invocation (layoutChildren in JFX). As far as I understand the code snippet, you were about to decrease the number of repaints because changing increment params allows repainting the content fewer times.
blockIncrement
(if the track of the bar is clicked)
unitIncrement
(if Up, Down, Right, Left buttons are clicked)
When you click scroll pane buttons the method ScrollPane.layoutChildren()
is invoked.
It makes ScrollBar reset increments (ScrollPaneSkin.updateVerticalSB(), ScrollPaneSkin.updateHorizontalSB()
).
So you need to store previous increment values and filter 'out of the box' scrolling events.
Source:
public class UnitIncrementScrollPaneSkin extends ScrollPaneSkin {
private double prevVerticalBlockIncrement;
private double prevHorizontalBlockIncrement;
private double prevVerticalUnitIncrement;
private double prevHorizontalUnitIncrement;
public UnitIncrementScrollPaneSkin(final ScrollPane scrollPane) {
super(scrollPane);
filterScrollEvents();
}
@Override
protected void layoutChildren(final double x, final double y, final double w, final double h) {
prevVerticalUnitIncrement = getVerticalScrollBar().getUnitIncrement();
prevHorizontalUnitIncrement = getHorizontalScrollBar().getUnitIncrement();
prevVerticalBlockIncrement = getVerticalScrollBar().getBlockIncrement();
prevHorizontalBlockIncrement = getHorizontalScrollBar().getBlockIncrement();
super.layoutChildren(x, y, w, h);
getVerticalScrollBar().setUnitIncrement(prevVerticalUnitIncrement);
getHorizontalScrollBar().setUnitIncrement(prevHorizontalUnitIncrement);
getVerticalScrollBar().setBlockIncrement(prevVerticalBlockIncrement);
getHorizontalScrollBar().setBlockIncrement(prevHorizontalBlockIncrement);
}
private void filterScrollEvents() {
getSkinnable().addEventFilter(ScrollEvent.SCROLL, event -> {
if (event.getDeltaX() < 0) {
getHorizontalScrollBar().increment();
} else if (event.getDeltaX() > 0) {
getHorizontalScrollBar().decrement();
}
if (event.getDeltaY() < 0) {
getVerticalScrollBar().increment();
} else if (event.getDeltaY() > 0) {
getVerticalScrollBar().decrement();
}
event.consume();
});
}
public ScrollBar getVerticalScrollBar() {
return vsb;
}
public ScrollBar getHorizontalScrollBar() {
return hsb;
}
}
Usage:
@FXML
public void initialize() {
final double initialInc = 0.2;
UnitIncrementScrollPaneSkin skin = new UnitIncrementScrollPaneSkin(scrollPane);
skin.getVerticalScrollBar().setUnitIncrement(initialInc);
skin.getVerticalScrollBar().setBlockIncrement(initialInc);
skin.getHorizontalScrollBar().setUnitIncrement(initialInc);
skin.getHorizontalScrollBar().setBlockIncrement(initialInc);
scrollPane.setSkin(skin);
}
Test App:
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="226.0" prefWidth="215.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.stackoverflow.app.scrollpane.SpeedUpScrollPaneController">
<children>
<ScrollPane fx:id="scrollPane" fitToHeight="true" fitToWidth="true" hbarPolicy="ALWAYS" prefHeight="200.0" prefWidth="200.0" vbarPolicy="ALWAYS">
<content>
<GridPane>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
</GridPane>
</content>
</ScrollPane>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
<children>
<Label text="V:" />
<Spinner fx:id="spinnerVertical" prefHeight="25.0" prefWidth="85.0">
<valueFactory>
<SpinnerValueFactory.DoubleSpinnerValueFactory amountToStepBy="0.1" max="1.0" min="0" />
</valueFactory>
</Spinner>
</children>
</HBox>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
<children>
<Label text="H:" />
<Spinner fx:id="spinnerHorizontal" prefHeight="25.0" prefWidth="85.0">
<valueFactory>
<SpinnerValueFactory.DoubleSpinnerValueFactory amountToStepBy="0.1" max="1.0" min="0" />
</valueFactory>
</Spinner>
</children>
</HBox>
<ListView fx:id="listViewLog" prefHeight="200.0" prefWidth="200.0" />
</children>
</VBox>
public class SpeedUpScrollPaneController {
@FXML
private ListView<String> listViewLog;
@FXML
private Spinner<Double> spinnerVertical;
@FXML
private Spinner<Double> spinnerHorizontal;
@FXML
private ScrollPane scrollPane;
@FXML
public void initialize() {
mockScrollPane();
initScrollPane();
}
private void mockScrollPane() {
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
final Label label = new Label(i + "," + j);
label.setMinWidth(60);
label.setMinHeight(30);
((GridPane) scrollPane.getContent()).add(label, i, j);
}
}
}
private void initScrollPane() {
final UnitIncrementScrollPaneSkin skin = new UnitIncrementScrollPaneSkin(scrollPane);
scrollPane.setSkin(skin);
initIncrement(0.2, spinnerHorizontal, skin.getHorizontalScrollBar());
initIncrement(0.2, spinnerVertical, skin.getVerticalScrollBar());
}
private void initIncrement(final double increment, final Spinner<Double> spinner, final ScrollBar scrollBar) {
scrollBar.unitIncrementProperty().addListener((source, prev, curr) -> {
final String vLog = String.format("%tr: %s unitIncrement=%f", Calendar.getInstance(),
scrollBar.getOrientation(), curr);
listViewLog.getItems().add(vLog);
listViewLog.scrollTo(vLog);
});
scrollBar.blockIncrementProperty().addListener((source, prev, curr) -> {
final String vLog = String.format("%tr: %s blockIncrement=%f", Calendar.getInstance(),
scrollBar.getOrientation(), curr);
listViewLog.getItems().add(vLog);
listViewLog.scrollTo(vLog);
});
spinner.valueProperty().addListener((source, prev, curr) -> {
scrollBar.setUnitIncrement(curr);
scrollBar.setBlockIncrement(curr);
});
Platform.runLater(() -> {
scrollBar.setUnitIncrement(increment);
scrollBar.setBlockIncrement(increment);
spinner.getValueFactory().setValue(scrollBar.getUnitIncrement());
});
}
}
- 0.0 -> no scrolling
- 0.1 -> scrolling on 10%
- 0.5 -> scrolling on 50%
- 1.0 -> scrolling on 100%
- Screencast
PS.
If you have a lot of objects to be scrolled you could try the approach:
- Create image bitmap representing scroll pane content
- Show image bitmap on start scrolling
- Move image at X,Y (based on
ScrollEvent.getDeltaX()
, ScrollEvent.getDeltaY()
)
- Hide image, show scroll pane nodes with new coordinates on finish scrolling