Skip to content

Instantly share code, notes, and snippets.

@Col-E
Created June 24, 2018 23:59
Show Gist options
  • Save Col-E/7d31b6b8684669cf1997831454681b85 to your computer and use it in GitHub Desktop.
Save Col-E/7d31b6b8684669cf1997831454681b85 to your computer and use it in GitHub Desktop.
JavaFX ScrollPane with smooth scrolling
import javafx.animation.Animation.Status;
import javafx.animation.Interpolator;
import javafx.animation.Transition;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.VBox;
import javafx.util.Duration;
/**
* Scrollpane with kinda smooth transition scrolling.
*
* @author Matt
*/
public class SmoothishScrollpane extends ScrollPane {
private final static int TRANSITION_DURATION = 200;
private final static double BASE_MODIFIER = 1;
/**
* @param content
* Item to be wrapped in the scrollpane.
*/
public SmoothishScrollpane(Node content) {
// ease-of-access for inner class
ScrollPane scroll = this;
// set content in a wrapper
VBox wrapper = new VBox(content);
setContent(wrapper);
// add scroll handling to wrapper
wrapper.setOnScroll(new EventHandler<ScrollEvent>() {
private SmoothishTransition transition;
@Override
public void handle(ScrollEvent event) {
double deltaY = BASE_MODIFIER * event.getDeltaY();
double width = scroll.getContent().getBoundsInLocal().getWidth();
double vvalue = scroll.getVvalue();
Interpolator interp = Interpolator.LINEAR;
transition = new SmoothishTransition(transition, deltaY) {
@Override
protected void interpolate(double frac) {
double x = interp.interpolate(vvalue, vvalue + -deltaY * getMod() / width, frac);
scroll.setVvalue(x);
}
};
transition.play();
}
});
}
/**
* @param t
* Transition to check.
* @return {@code true} if transition is playing.
*/
private static boolean playing(Transition t) {
return t.getStatus() == Status.RUNNING;
}
/**
* @param d1
* Value 1
* @param d2
* Value 2.
* @return {@code true} if values signes are matching.
*/
private static boolean sameSign(double d1, double d2) {
return (d1 > 0 && d2 > 0) || (d1 < 0 && d2 < 0);
}
/**
* Transition with varying speed based on previously existing transitions.
*
* @author Matt
*/
abstract class SmoothishTransition extends Transition {
private final double mod;
private final double delta;
public SmoothishTransition(SmoothishTransition old, double delta) {
setCycleDuration(Duration.millis(TRANSITION_DURATION));
setCycleCount(0);
// if the last transition was moving inthe same direction, and is still playing
// then increment the modifer. This will boost the distance, thus looking faster
// and seemingly consecutive.
if (old != null && sameSign(delta, old.delta) && playing(old)) {
mod = old.getMod() + 1;
} else {
mod = 1;
}
this.delta = delta;
}
public double getMod() {
return mod;
}
@Override
public void play() {
super.play();
// Even with a linear interpolation, startup is visibly slower than the middle.
// So skip a small bit of the animation to keep up with the speed of prior
// animation. The value of 10 works and isn't noticeable unless you really pay
// close attention. This works best on linear but also is decent for others.
if (getMod() > 1) {
jumpTo(getCycleDuration().divide(10));
}
}
}
}
@ClementGre
Copy link

Hi,
thanks for this gist !
Why do the vertical scroll speed depends of the content width ?

Line 37: double width = scroll.getContent().getBoundsInLocal().getWidth();
Line 43: double x = interp.interpolate(vvalue, vvalue + -deltaY * getMod() / width, frac);

Thanks !

@Col-E
Copy link
Author

Col-E commented Jan 7, 2022

Its been so long since I made or used this that I don't remember. It could very well have just been a mistake that just so happened to seem like it worked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment