JavaFX Glitches 动画非标准小部件
Posted
技术标签:
【中文标题】JavaFX Glitches 动画非标准小部件【英文标题】:JavaFX Glitches animating non standard widget 【发布时间】:2015-03-02 11:35:15 【问题描述】:我试图用 JavaFX 证明一个可扩展的小部件,它与 TitledPane
的不同之处在于标签周围的边框随着小部件的扩展而增长。我找到了一种方法。
但是,我在展开/折叠一两次后出现了一个奇怪的故障。
这是一个短视频的链接,其中问题出现在第三和第四扩展:
Short Youtube Concept / Glitch Video
我已经用完了可以尝试让它正常运行的东西。
代码如下,对它的古怪表示歉意,希望在我重构它之前让它工作。
class ExpansionManager
enum LayoutState
INITIALIZE,
ANIMATING,
IDLE,
REQUEST_ANIMATION
LayoutState layoutState = LayoutState.INITIALIZE;
Double fromWidth = 0.0;
Double fromHeight = 0.0;
Double stepWidth = 0.0;
Double stepHeight = 0.0;
Double toWidth = 0.0;
Double toHeight = 0.0;
public class ExpandableTitledList extends VBox
private Label title = new Label();
private ListProperty<String> listItem = new SimpleListProperty<>();
private ListView listView = new ListView<>(listItem);
Timeline timeline;
WritableValue<Double> writableHeight = new WritableValue<Double>()
@Override
public Double getValue()
return expansionManager.stepHeight;
@Override
public void setValue(Double value)
expansionManager.stepHeight = value;
requestLayout();
;
WritableValue<Double> writableWidth = new WritableValue<Double>()
@Override
public Double getValue()
return expansionManager.stepWidth;
@Override
public void setValue(Double value)
expansionManager.stepWidth = value;
requestLayout();
;
private boolean expanded = false;
ExpansionManager expansionManager = new ExpansionManager();
// private Dimension2D contractedDimension;
// private Dimension2D expandedDimension;
public ExpandableTitledList()
setTitle("boom");
// title.layout();
// System.out.println(title.getLayoutBounds().getWidth());
// set down right caret
listItem.setValue(FXCollections.observableArrayList("one", "two"));
Insets theInsets = new Insets(-3, -5, -3, -5);
Border theBorder = new Border(
new BorderStroke(
Color.BLACK,
BorderStrokeStyle.SOLID,
new CornerRadii(4),
new BorderWidths(2),
theInsets
)
);
// expandedDimension = new Dimension2D(200,200);
setBorder(theBorder);
getChildren().addAll(title);
title.setOnMouseClicked((event) ->
System.out.println("mouse clicked");
if (this.expanded) contract();
else expand();
);
@Override
protected void layoutChildren()
System.out.println(expansionManager.layoutState);
if (expansionManager.layoutState == ExpansionManager.LayoutState.INITIALIZE)
super.layoutChildren();
expansionManager.layoutState = ExpansionManager.LayoutState.IDLE;
else if (expansionManager.layoutState == ExpansionManager.LayoutState.ANIMATING)
super.layoutChildren();
else if (expansionManager.layoutState == ExpansionManager.LayoutState.REQUEST_ANIMATION)
setCache(false);
listView.setCache(false);
expansionManager.layoutState = ExpansionManager.LayoutState.ANIMATING;
System.out.println("from : " + expansionManager.fromWidth + ", "+ expansionManager.fromHeight);
System.out.println("to : " + expansionManager.toWidth + ", "+ expansionManager.toHeight);
timeline = new Timeline();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO,
new KeyValue(writableHeight, expansionManager.fromHeight),
new KeyValue(writableWidth, expansionManager.fromWidth)),
new KeyFrame(Duration.millis(100),
new KeyValue(writableHeight, expansionManager.toHeight),
new KeyValue(writableWidth, expansionManager.toWidth))
);
timeline.play();
timeline.setOnFinished((done) ->
System.out.println("done");
expansionManager.layoutState = ExpansionManager.LayoutState.IDLE;
timeline = null;
);
else
System.out.println("idle");
super.layoutChildren();
@Override
protected double computePrefHeight(double width)
if (expansionManager.layoutState == ExpansionManager.LayoutState.INITIALIZE)
expansionManager.fromHeight = super.computePrefHeight(width);
return expansionManager.fromHeight;
else if (expansionManager.layoutState == ExpansionManager.LayoutState.ANIMATING)
return expansionManager.stepHeight;
else if (expansionManager.layoutState == ExpansionManager.LayoutState.REQUEST_ANIMATION)
expansionManager.fromHeight = getHeight();
expansionManager.stepHeight = expansionManager.fromHeight;
expansionManager.toHeight = super.computePrefHeight(width);
return expansionManager.fromHeight;
else
return expansionManager.toHeight;
@Override
protected double computePrefWidth(double height)
if (expansionManager.layoutState == ExpansionManager.LayoutState.INITIALIZE)
expansionManager.fromWidth = super.computePrefWidth(height);
return expansionManager.fromWidth;
else if (expansionManager.layoutState == ExpansionManager.LayoutState.ANIMATING)
return expansionManager.stepWidth;
else if (expansionManager.layoutState == ExpansionManager.LayoutState.REQUEST_ANIMATION)
expansionManager.fromWidth = getWidth();
expansionManager.stepWidth = expansionManager.fromWidth;
expansionManager.toWidth = super.computePrefWidth(height);
return expansionManager.fromWidth;
else
System.out.println("BANG BANG BANG");
return expansionManager.toWidth;
// @Override
// protected double computeMinWidth(double height)
// return computePrefWidth(height);
//
//
// @Override
// protected double computeMinHeight(double width)
// return computePrefHeight(width);
//
//
// @Override
// protected double computeMaxWidth(double height)
// return computePrefWidth(height);
//
//
// @Override
// protected double computeMaxHeight(double width)
// return computePrefHeight(width);
//
private void expand()
System.out.println(expansionManager.layoutState);
// if(contractedDimension == null)
// contractedDimension = new Dimension2D(this.getWidth(), this.getHeight());
// setPrefSize(expandedDimension.getWidth(), expandedDimension.getHeight());
expansionManager.layoutState = ExpansionManager.LayoutState.REQUEST_ANIMATION;
this.getChildren().setAll(title, listView);
expanded = true;
private void contract()
// this.setPrefSize(contractedDimension.getWidth(), contractedDimension.getHeight());
expansionManager.layoutState = ExpansionManager.LayoutState.REQUEST_ANIMATION;
this.getChildren().setAll(title);
expanded = false;
public String getTitle()
return title.getText();
public void setTitle(String title)
this.title.setText(title);
【问题讨论】:
我为我的答案添加了另一个解决方案,与您的方法一致,希望您对此感兴趣。 【参考方案1】:我知道ListView
是出现故障的原因。
基本上,除了第一次,每次您将列表添加到 VBox
时,您都可以在非常短的时间内在其容器外看到完整大小的列表,然后当时间线开始时,它会正确调整大小.
实际上,您可以在时间轴上添加延迟(例如一秒):
timeline.setDelay(Duration.millis(1000));
如果您第二次展开该框,您将看到整整一秒的问题:
该列表在VBox
之外可见,因为它没有调整大小以适应它。当动画开始时,它被调整大小并且问题消失了。
当时我尝试了几种方法来调整列表的大小,但均未成功。也许你可以解决它...
一个丑陋的解决方案是每次展开框时创建一个新的列表实例:
private void expand()
expansionManager.layoutState = ExpansionManager.LayoutState.REQUEST_ANIMATION;
// this works...
listView = new ListView<>(listItem);
this.getChildren().setAll(title,listView);
expanded = true;
寻找其他替代方案,我已将框 disableProperty()
绑定到时间线,因此在标题列表正在扩展或收缩时您无法单击。
所以另一种解决方案是将列表visibleProperty()
绑定到时间线,但您不会看到很好的增长效果。
还有第三种解决方案,也符合动画:在将列表添加到框之前将不透明度设置为 0:
private void expand()
expansionManager.layoutState = ExpansionManager.LayoutState.REQUEST_ANIMATION;
// this will avoid seeing the unresized listView
listView.setOpacity(0);
this.getChildren().setAll(title,listView);
expanded = true;
并添加一个新的KeyValue
将列表opacityProperty()
从0 增加到1 到时间线:
timeline.getKeyFrames().setAll(
new KeyFrame(Duration.ZERO,
new KeyValue(listView.opacityProperty(), 0),
new KeyValue(writableHeight, expansionManager.fromHeight.get()),
new KeyValue(writableWidth, expansionManager.fromWidth.get())),
new KeyFrame(Duration.millis(300),
new KeyValue(listView.opacityProperty(), 1),
new KeyValue(writableHeight, expansionManager.toHeight.get()),
new KeyValue(writableWidth, expansionManager.toWidth.get()))
);
现在您不会看到故障,并且在调整框大小时列表会很好地显示出来。事实上,我会增加第二个关键帧的持续时间。
编辑
我有另一种选择来避免“故障”。它还可以改善动画,因为当标题列表收缩时,列表也会可见。
当你收缩标题列表时,首先你删除列表,所以super.computePrefHeight(width)
和super.computePrefWidth(height)
得到小框的新大小。这有一个明显的缺点,即在下一次展开时,必须再次添加列表,并且会发生故障。
为了避免这种情况,我们不会删除该列表。首先,我们在ExpansionManager
上新建两个字段:
Double minWidth = 0.0;
Double minHeight = 0.0;
然后我们得到盒子的最小尺寸(在第一次扩展时),并在每次收缩时使用它:
@Override
protected double computePrefHeight(double width)
...
if (expansionManager.layoutState == ExpansionManager.LayoutState.REQUEST_ANIMATION)
if(expansionManager.minHeight==0d)
expansionManager.minHeight=getHeight();
expansionManager.fromHeight = getHeight();
expansionManager.stepHeight = expansionManager.fromHeight;
expansionManager.toHeight = expanded?super.computePrefHeight(width):
expansionManager.minHeight;
return expansionManager.fromHeight;
@Override
protected double computePrefWidth(double height)
...
if (expansionManager.layoutState == ExpansionManager.LayoutState.REQUEST_ANIMATION)
if(expansionManager.minWidth==0d)
expansionManager.minWidth=getWidth();
expansionManager.fromWidth = getWidth();
expansionManager.stepWidth = expansionManager.fromWidth;
expansionManager.toWidth = expanded?super.computePrefWidth(height):
expansionManager.minWidth;
return expansionManager.fromWidth;
最后,我们需要在任何收缩后隐藏列表,否则会看到一个小边框,并将expand()
和contract()
方法更改为调用requestLayout()
,因为box children列表不再修改(第一次通话除外):
@Override
protected void layoutChildren()
timeline.setOnFinished((done) ->
expansionManager.layoutState = ExpansionManager.LayoutState.IDLE;
listView.setVisible(expanded);
timeline = null;
);
private void expand()
expansionManager.layoutState = ExpansionManager.LayoutState.REQUEST_ANIMATION;
expanded = true;
listView.setVisible(true);
if(this.getChildren().size()==1)
this.getChildren().add(listView);
requestLayout();
private void contract()
expansionManager.layoutState = ExpansionManager.LayoutState.REQUEST_ANIMATION;
expanded = false;
requestLayout();
【讨论】:
以上是关于JavaFX Glitches 动画非标准小部件的主要内容,如果未能解决你的问题,请参考以下文章