Java 创建简单的自定义滚动视图

Java 创建简单的自定义滚动视图,java,javafx,scroll,javafx-8,scrollview,Java,Javafx,Scroll,Javafx 8,Scrollview,这是一个关于滚动视图的一般性问题,我想学习滚动视图的基础知识以及如何自己实现滚动视图,因为它是大多数动态GUI的一部分。您可能会问,为什么不简单地使用平台提供的一个呢?我的答案是,除了学习新东西很有趣之外,看到你想要的定制方式也很好。简单地说,我只想创建一个简单的自定义滚动视图,并尝试了解它是如何在幕后工作的 接下来,我现在要介绍的只是我提出的UI的最简单示例。基本上,它是一个窗格,用作整个内容的视口,在其右边缘包含一个垂直滚动条,就像普通的滚动视图一样,但我只是添加了一个小的过渡,在鼠标悬停时

这是一个关于滚动视图的一般性问题,我想学习滚动视图的基础知识以及如何自己实现滚动视图,因为它是大多数动态GUI的一部分。您可能会问,为什么不简单地使用平台提供的一个呢?我的答案是,除了学习新东西很有趣之外,看到你想要的定制方式也很好。简单地说,我只想创建一个简单的自定义滚动视图,并尝试了解它是如何在幕后工作的

接下来,我现在要介绍的只是我提出的UI的最简单示例。基本上,它是一个窗格,用作整个内容的视口,在其右边缘包含一个垂直滚动条,就像普通的滚动视图一样,但我只是添加了一个小的过渡,在鼠标悬停时为滚动条的宽度设置动画

滚动容器类

我想看一个工作示例,展示滚动的基本艺术;就像处理拇指动画移动的正确方法一样,计算滚动条的拇指长度,最后计算移动内容所需的总单位或数量。我认为这三个部分是滚动视图核心的关键

附言 我还想看看onScroll事件在JavaFX中的用法,现在我只知道常用的鼠标事件。先谢谢你

使现代化 我在下面sir@fabian的答案中添加了一个BlockIncrement函数。它基本上只是将拇指移动到指针的当前位置,同时保持[0,1]范围值。所有的荣誉和感谢都归于他

这是为其他人谁正在寻找这样的想法 自定义滚动视图,希望您将来会发现此参考资料很有用


有几个等式允许您计算布局,所有这些等式都假定为contentHeight>viewportHeight:

v值表示拇指在垂直滚动条中的位置[0,1]0=最上面的位置,1=拇指底部在轨迹底部

topY = vValue * (contentHeight - viewportHeight)
thumbHeight / trackHeight = viewportHeight / contentHeight
thumbY = vValue * (trackHeight - thumbHeight)
还请注意,提供对子类的访问并在ScrollContainer之外添加内容是一种不好的做法,因为它需要此类的用户进行应该保留给类本身的修改。这样做很容易导致以下行中断ScrollContainer内容可能会隐藏拇指:

 // viewport.getChildren().add(0, content);
 viewport.getChildren().add(content);
最好直接扩展区域,并使用一种方法替换内容

public class ScrollContainer extends Region {

    private VerticalScrollBar scrollBar; // The scrollbar used for scrolling over the content from viewport
    private Rectangle rectangle; // Object for clipping the viewport to restrict overflowing content

    /**
     * Construct a new ScrollContainer
     */
    public ScrollContainer() {
        setOnScroll(evt -> {
            double viewportHeight = getHeight();
            double contentHeight = getContentHeight();
            if (contentHeight > viewportHeight) {
                double delta = evt.getDeltaY() / (viewportHeight - contentHeight);
                if (Double.isFinite(delta)) {
                    scrollBar.setValue(scrollBar.getValue() + delta);
                }
            }
        });

        scrollBar = new VerticalScrollBar();
        getChildren().add(scrollBar);

        rectangle = new Rectangle();
        setClip(rectangle);
    }

    private Node content;

    public void setContent(Node content) {
        if (this.content != null) {
            // remove old content
            getChildren().remove(this.content);
        }
        if (content != null) {
            // add new content
            getChildren().add(0, content);
        }
        this.content = content;
    }

    private double getContentHeight() {
        return content == null ? 0 : content.getLayoutBounds().getHeight();
    }

    @Override
    protected void layoutChildren() {
        super.layoutChildren();

        double w = getWidth();
        double h = getHeight();

        double sw = scrollBar.getWidth();

        double viewportWidth = w - sw;
        double viewportHeight = h;

        if (content != null) {
            double contentHeight = getContentHeight();
            double vValue = scrollBar.getValue();

            // position content according to scrollbar value
            content.setLayoutY(Math.min(0, viewportHeight - contentHeight) * vValue);
        }

        // Layout scrollbar to the edge of container, and fit the viewport's height as well
        scrollBar.resize(sw, h);
        scrollBar.setLayoutX(viewportWidth);

        // resize clip
        rectangle.setWidth(w);
        rectangle.setHeight(h);
    }

    /**
     * VerticalScrollBar
     */
    private class VerticalScrollBar extends Region {

        private double initialValue;
        private double initialY; // Initial mouse position when dragging the scrubber
        private Timeline widthTransition; // Transforms width of scrollbar on hover
        private Region scrubber; // Indicator about the content's visible area

        private double value;

        public double getValue() {
            return value;
        }

        private double calculateScrubberHeight() {
            double h = getHeight();
            return h * h / getContentHeight();
        }

        /**
         * Construct a new VerticalScrollBar
         */
        private VerticalScrollBar() {
            // Scrollbar's initial width
            setPrefWidth(7);

            widthTransition = new Timeline(
                    new KeyFrame(Duration.ZERO, new KeyValue(prefWidthProperty(), 7)),
                    new KeyFrame(Duration.millis(500), new KeyValue(prefWidthProperty(), 14))
            );

            scrubber = new Region();
            scrubber.setStyle("-fx-background-color: rgba(0,0,0, 0.25)");
            scrubber.setOnMousePressed(event -> {
                initialY = scrubber.localToParent(event.getX(), event.getY()).getY();
                initialValue = value;
            });
            scrubber.setOnMouseDragged(event -> {
                double currentY = scrubber.localToParent(event.getX(), event.getY()).getY();
                double sH = calculateScrubberHeight();
                double h = getHeight();

                // calculate value change and prevent errors
                double delta = (currentY - initialY) / (h - sH);
                if (!Double.isFinite(delta)) {
                    delta = 0;
                }

                // keep value in range [0, 1]
                double newValue = Math.max(0, Math.min(1, initialValue + delta));
                value = newValue;

                // layout thumb
                requestLayout();
            });
            getChildren().add(scrubber);

            // Animate scrollbar's width on mouse enter and exit
            setOnMouseEntered(event -> {
                widthTransition.setRate(1);
                widthTransition.play();
            });
            setOnMouseExited(event -> {
                widthTransition.setRate(-1);
                widthTransition.play();
            });
        }

        @Override
        protected void layoutChildren() {
            super.layoutChildren();

            double h = getHeight();
            double cH = getContentHeight();

            if (cH <= h) {
                // full size, if content does not excede viewport size
                scrubber.resize(getWidth(), h);
            } else {
                double sH = calculateScrubberHeight();

                // move thumb to position
                scrubber.setTranslateY(value * (h - sH));

                // Layout scrubber to fit the scrollbar's width
                scrubber.resize(getWidth(), sH);
            }
        }
    }
}

非常感谢你,老师!我非常感谢你花每一寸时间提供这个答案。我读过关于拇指高度和位置的公式,它与你的公式非常相似,所以我想这就像是最一般的公式,因为我看到了用JavaScript编写的不同实现和计算,但这让我搞不清楚应该遵循哪一个。再次感谢你!
topY = vValue * (contentHeight - viewportHeight)
thumbHeight / trackHeight = viewportHeight / contentHeight
thumbY = vValue * (trackHeight - thumbHeight)
 // viewport.getChildren().add(0, content);
 viewport.getChildren().add(content);
public class ScrollContainer extends Region {

    private VerticalScrollBar scrollBar; // The scrollbar used for scrolling over the content from viewport
    private Rectangle rectangle; // Object for clipping the viewport to restrict overflowing content

    /**
     * Construct a new ScrollContainer
     */
    public ScrollContainer() {
        setOnScroll(evt -> {
            double viewportHeight = getHeight();
            double contentHeight = getContentHeight();
            if (contentHeight > viewportHeight) {
                double delta = evt.getDeltaY() / (viewportHeight - contentHeight);
                if (Double.isFinite(delta)) {
                    scrollBar.setValue(scrollBar.getValue() + delta);
                }
            }
        });

        scrollBar = new VerticalScrollBar();
        getChildren().add(scrollBar);

        rectangle = new Rectangle();
        setClip(rectangle);
    }

    private Node content;

    public void setContent(Node content) {
        if (this.content != null) {
            // remove old content
            getChildren().remove(this.content);
        }
        if (content != null) {
            // add new content
            getChildren().add(0, content);
        }
        this.content = content;
    }

    private double getContentHeight() {
        return content == null ? 0 : content.getLayoutBounds().getHeight();
    }

    @Override
    protected void layoutChildren() {
        super.layoutChildren();

        double w = getWidth();
        double h = getHeight();

        double sw = scrollBar.getWidth();

        double viewportWidth = w - sw;
        double viewportHeight = h;

        if (content != null) {
            double contentHeight = getContentHeight();
            double vValue = scrollBar.getValue();

            // position content according to scrollbar value
            content.setLayoutY(Math.min(0, viewportHeight - contentHeight) * vValue);
        }

        // Layout scrollbar to the edge of container, and fit the viewport's height as well
        scrollBar.resize(sw, h);
        scrollBar.setLayoutX(viewportWidth);

        // resize clip
        rectangle.setWidth(w);
        rectangle.setHeight(h);
    }

    /**
     * VerticalScrollBar
     */
    private class VerticalScrollBar extends Region {

        private double initialValue;
        private double initialY; // Initial mouse position when dragging the scrubber
        private Timeline widthTransition; // Transforms width of scrollbar on hover
        private Region scrubber; // Indicator about the content's visible area

        private double value;

        public double getValue() {
            return value;
        }

        private double calculateScrubberHeight() {
            double h = getHeight();
            return h * h / getContentHeight();
        }

        /**
         * Construct a new VerticalScrollBar
         */
        private VerticalScrollBar() {
            // Scrollbar's initial width
            setPrefWidth(7);

            widthTransition = new Timeline(
                    new KeyFrame(Duration.ZERO, new KeyValue(prefWidthProperty(), 7)),
                    new KeyFrame(Duration.millis(500), new KeyValue(prefWidthProperty(), 14))
            );

            scrubber = new Region();
            scrubber.setStyle("-fx-background-color: rgba(0,0,0, 0.25)");
            scrubber.setOnMousePressed(event -> {
                initialY = scrubber.localToParent(event.getX(), event.getY()).getY();
                initialValue = value;
            });
            scrubber.setOnMouseDragged(event -> {
                double currentY = scrubber.localToParent(event.getX(), event.getY()).getY();
                double sH = calculateScrubberHeight();
                double h = getHeight();

                // calculate value change and prevent errors
                double delta = (currentY - initialY) / (h - sH);
                if (!Double.isFinite(delta)) {
                    delta = 0;
                }

                // keep value in range [0, 1]
                double newValue = Math.max(0, Math.min(1, initialValue + delta));
                value = newValue;

                // layout thumb
                requestLayout();
            });
            getChildren().add(scrubber);

            // Animate scrollbar's width on mouse enter and exit
            setOnMouseEntered(event -> {
                widthTransition.setRate(1);
                widthTransition.play();
            });
            setOnMouseExited(event -> {
                widthTransition.setRate(-1);
                widthTransition.play();
            });
        }

        @Override
        protected void layoutChildren() {
            super.layoutChildren();

            double h = getHeight();
            double cH = getContentHeight();

            if (cH <= h) {
                // full size, if content does not excede viewport size
                scrubber.resize(getWidth(), h);
            } else {
                double sH = calculateScrubberHeight();

                // move thumb to position
                scrubber.setTranslateY(value * (h - sH));

                // Layout scrubber to fit the scrollbar's width
                scrubber.resize(getWidth(), sH);
            }
        }
    }
}