Java 创建简单的自定义滚动视图
这是一个关于滚动视图的一般性问题,我想学习滚动视图的基础知识以及如何自己实现滚动视图,因为它是大多数动态GUI的一部分。您可能会问,为什么不简单地使用平台提供的一个呢?我的答案是,除了学习新东西很有趣之外,看到你想要的定制方式也很好。简单地说,我只想创建一个简单的自定义滚动视图,并尝试了解它是如何在幕后工作的 接下来,我现在要介绍的只是我提出的UI的最简单示例。基本上,它是一个窗格,用作整个内容的视口,在其右边缘包含一个垂直滚动条,就像普通的滚动视图一样,但我只是添加了一个小的过渡,在鼠标悬停时为滚动条的宽度设置动画 滚动容器类 我想看一个工作示例,展示滚动的基本艺术;就像处理拇指动画移动的正确方法一样,计算滚动条的拇指长度,最后计算移动内容所需的总单位或数量。我认为这三个部分是滚动视图核心的关键 附言 我还想看看onScroll事件在JavaFX中的用法,现在我只知道常用的鼠标事件。先谢谢你 使现代化 我在下面sir@fabian的答案中添加了一个BlockIncrement函数。它基本上只是将拇指移动到指针的当前位置,同时保持[0,1]范围值。所有的荣誉和感谢都归于他 这是为其他人谁正在寻找这样的想法 自定义滚动视图,希望您将来会发现此参考资料很有用Java 创建简单的自定义滚动视图,java,javafx,scroll,javafx-8,scrollview,Java,Javafx,Scroll,Javafx 8,Scrollview,这是一个关于滚动视图的一般性问题,我想学习滚动视图的基础知识以及如何自己实现滚动视图,因为它是大多数动态GUI的一部分。您可能会问,为什么不简单地使用平台提供的一个呢?我的答案是,除了学习新东西很有趣之外,看到你想要的定制方式也很好。简单地说,我只想创建一个简单的自定义滚动视图,并尝试了解它是如何在幕后工作的 接下来,我现在要介绍的只是我提出的UI的最简单示例。基本上,它是一个窗格,用作整个内容的视口,在其右边缘包含一个垂直滚动条,就像普通的滚动视图一样,但我只是添加了一个小的过渡,在鼠标悬停时
有几个等式允许您计算布局,所有这些等式都假定为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);
}
}
}
}