在 JFXListView 上启用平滑滚动 (#4809)

This commit is contained in:
Glavo
2025-11-17 15:42:55 +08:00
committed by GitHub
parent e4f10d7cd8
commit 9dfc6155c4
3 changed files with 79 additions and 0 deletions

View File

@@ -26,6 +26,7 @@ import com.jfoenix.effects.JFXDepthManager;
import javafx.scene.control.ListCell;
import javafx.scene.control.skin.VirtualFlow;
import javafx.scene.layout.Region;
import org.jackhuang.hmcl.ui.FXUtils;
// https://github.com/HMCL-dev/HMCL/issues/4720
public class JFXListViewSkin<T> extends ListViewSkin<T> {
@@ -38,6 +39,7 @@ public class JFXListViewSkin<T> extends ListViewSkin<T> {
flow = (VirtualFlow<ListCell<T>>) getChildren().get(0);
JFXDepthManager.setDepth(flow, listView.depthProperty().get());
listView.depthProperty().addListener((o, oldVal, newVal) -> JFXDepthManager.setDepth(flow, newVal));
FXUtils.smoothScrolling(flow);
}
@Override

View File

@@ -42,6 +42,7 @@ import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.*;
import javafx.scene.control.skin.VirtualFlow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.*;
@@ -394,6 +395,11 @@ public final class FXUtils {
ScrollUtils.addSmoothScrolling(scrollPane);
}
public static void smoothScrolling(VirtualFlow<?> virtualFlow) {
if (AnimationUtils.isAnimationEnabled())
ScrollUtils.addSmoothScrolling(virtualFlow);
}
/// If the current environment is JavaFX 23 or higher, this method returns [Labeled#textTruncatedProperty()];
/// Otherwise, it returns `null`.
public static @Nullable ReadOnlyBooleanProperty textTruncatedProperty(Labeled labeled) {

View File

@@ -25,7 +25,9 @@ import javafx.animation.Animation.Status;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.EventHandler;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.skin.VirtualFlow;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.util.Duration;
@@ -139,6 +141,21 @@ final class ScrollUtils {
smoothScroll(scrollPane, speed, trackPadAdjustment);
}
/// @author Glavo
public static void addSmoothScrolling(VirtualFlow<?> virtualFlow) {
addSmoothScrolling(virtualFlow, 1);
}
/// @author Glavo
public static void addSmoothScrolling(VirtualFlow<?> virtualFlow, double speed) {
addSmoothScrolling(virtualFlow, speed, 7);
}
/// @author Glavo
public static void addSmoothScrolling(VirtualFlow<?> virtualFlow, double speed, double trackPadAdjustment) {
smoothScroll(virtualFlow, speed, trackPadAdjustment);
}
private static final double[] FRICTIONS = {0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
private static final Duration DURATION = Duration.millis(3);
@@ -207,4 +224,58 @@ final class ScrollUtils {
timeline.setCycleCount(Animation.INDEFINITE);
}
/// @author Glavo
private static void smoothScroll(VirtualFlow<?> virtualFlow, double speed, double trackPadAdjustment) {
if (!virtualFlow.isVertical())
return;
final double[] derivatives = new double[FRICTIONS.length];
Timeline timeline = new Timeline();
Holder<ScrollDirection> scrollDirectionHolder = new Holder<>();
final EventHandler<MouseEvent> mouseHandler = event -> timeline.stop();
final EventHandler<ScrollEvent> scrollHandler = event -> {
if (event.getEventType() == ScrollEvent.SCROLL) {
ScrollDirection scrollDirection = determineScrollDirection(event);
if (scrollDirection == ScrollDirection.LEFT || scrollDirection == ScrollDirection.RIGHT) {
return;
}
scrollDirectionHolder.value = scrollDirection;
double currentSpeed = isTrackPad(event, scrollDirection) ? speed / trackPadAdjustment : speed;
derivatives[0] += scrollDirection.intDirection * currentSpeed;
if (timeline.getStatus() == Status.STOPPED) {
timeline.play();
}
event.consume();
}
};
virtualFlow.addEventHandler(MouseEvent.MOUSE_PRESSED, mouseHandler);
virtualFlow.addEventFilter(ScrollEvent.ANY, scrollHandler);
timeline.getKeyFrames().add(new KeyFrame(DURATION, event -> {
for (int i = 0; i < derivatives.length; i++) {
derivatives[i] *= FRICTIONS[i];
}
for (int i = 1; i < derivatives.length; i++) {
derivatives[i] += derivatives[i - 1];
}
double dy = derivatives[derivatives.length - 1];
int cellCount = virtualFlow.getCellCount();
IndexedCell<?> firstVisibleCell = virtualFlow.getFirstVisibleCell();
double height = firstVisibleCell != null ? firstVisibleCell.getHeight() * cellCount : 0.0;
double delta = height > 0.0
? dy / height
: (scrollDirectionHolder.value == ScrollDirection.DOWN ? 0.001 : -0.001);
virtualFlow.setPosition(Math.min(Math.max(virtualFlow.getPosition() + delta, 0), 1));
if (Math.abs(dy) < 0.001) {
timeline.stop();
}
}));
timeline.setCycleCount(Animation.INDEFINITE);
}
}