[英]JavaFx. How to resume?
我正在為文本編寫時間表:
用法:
Text text = new Text();
......
group.getChildren().addAll(text);
root.getChildren().addAll(group);
tl.play();
這很好。 如果要暫停並繼續播放動畫,請tl.pause();
和tl.play();
可以做到的。
現在,我要使動畫從頭開始,並使用tl.stop();
和tl.playFromStart();
但是這種組合的效果與tl.pause();
的效果相同tl.pause();
& tl.play();
。
我的問題是,為什么tl.playFromStart(); 無法正常工作,如何恢復動畫?
Timeline
的工作方式
Timeline
表示執行動畫的時間段。 Timeline
包含KeyFrame
的集合。 每個關鍵KeyFrame
Timeline
(您傳入的Duration
對象)中指定一個時間點 KeyValue
的集合,這些集合包括WritableValue
(例如, Property
)和該時間點那些WritableValue
的目標值 EventHandler<ActionEvent>
的形式指定要執行的動作 Timeline
具有currentTime
屬性,該屬性(當然)在Timeline
播放時隨着時間的流逝而向前推進。 pause()
將停止currentTime
,將其固定為當前值。 stop()
將停止的進展currentTime
並設置currentTime
回零。
如果Timeline
具有指定KeyValue
的KeyFrame
,則隨着currentTime
更改,在KeyValue
指定的WritableValue
將被設置為取決於currentTime
值。 (特別是,如果WritableValue
是可插值的,則將在指定該WritableValue
KeyValue
的兩個相鄰KeyFrames
之間插值。否則,該值將被設置為指定該WritableValue
的值的“最新” KeyFrame
。)
如果Timeline
具有指定動作的KeyFrame
( EventHandler<ActionEvent>
),則當currentTime
超過該KeyFrame
指定的時間時,將調用該動作。
為什么您的代碼無法與stop()
或playFromStart()
在您的情況下,您的KeyFrame
指定了一個動作,該動作將新的變換添加到節點的變換列表中。 請注意,這完全不依賴於currentTime
,只是每次currentTime
達到0.04 seconds
,都會添加一個新的轉換(加上您未顯示其實現的方法shiftAndScale
)。 因此,如果您在時間軸上stop()
,則currentTime
將重置為零,但是由於該原因,該節點不會發生任何事情。 (實際上,無論如何, currentTime
僅在0
到0.04
秒之間變化。)
您的代碼的其他問題
您的代碼存在問題,因為您存在內存泄漏。 Node
維護Transform
的ObservableList
。 您正在添加到此列表(經常),但從不刪除任何內容。 Node
非常智能:它保留了一個隱藏的矩陣,該矩陣是所有變換的凈效果; 當您添加新的轉換時,它將存儲在列表中,然后使用簡單的矩陣乘法更新“ net”矩陣。 因此,您在這里不會看到任何計算性能問題:從該角度來看,它可以很好地擴展。 但是,它確實存儲了所有單個轉換(例如,因為它支持稍后將其刪除),因此,如果讓此轉換運行足夠長的時間, 最終將耗盡內存。
您的代碼的另一個(也許是次要的)問題是,當您組合所有這些轉換時,您正在執行很多浮點運算。 任何舍入錯誤最終都會累積。 您應該嘗試找到一種避免舍入誤差累積的技術。
修復代碼的方法
要解決此問題,您有兩種選擇:
如果動畫是“自然循環”的(意味着動畫在某個固定時間(例如旋轉)后返回其初始狀態),則只需根據該自然持續時間定義Timeline
。 僅以旋轉為例,您可以執行以下操作:
double secondsPerCompleteCycle = (360.0 / 0.75) * 0.04 ;
Rotate rotation = new Rotate(0, new Point3D(1, 0, 0));
group.getTransforms().add(rotation);
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(secondsPerCompleteCycle),
new KeyValue(rotation.angleProperty(), 360, Interpolator.LINEAR)));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
現在timeline.stop()
會將currentTime
設置為零,這將具有將旋轉角度設置回其初始值(也為零)的效果。
如果動畫不是自然重復的,我將使用(整數類型)計數器以您選擇的任何時間單位跟蹤“當前幀”,然后將變換的值綁定到計數器。 使用相同的示例,您可以
double degreesPerFrame = 0.75 ;
LongProperty frameCount = new SimpleLongProperty();
Rotate rotation = new Rotate(0, new Point3D(1, 0, 0));
group.getTransforms().add(rotation);
rotation.angleProperty().bind(frameCount.multiply(degreesPerFrame));
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.04), e ->
frameCount.set(frameCount.get() + 1)));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
// to reset to the beginning:
timeline.stop();
frameCount.set(0L);
您還可以考慮使用AnimationTimer
,具體取決於您的確切要求。 不過,我會首先嘗試其中一種技術。
在您的情況下,代數變得非常復雜(無論如何對我來說都是非常復雜的)。 每個動作將三個變換添加到該節點。 平移,比例尺和繞x軸的旋轉。 這些的4x4矩陣表示為:
1 0 0 tx
0 1 0 ty
0 0 1 0
0 0 0 1
對於翻譯,
sx 0 0 0
0 sy 0 0
0 0 1 0
0 0 0 1
規模,以及
1 0 0 0
0 cos(t) -sin(t) 0
0 sin(t) cos(t) 0
0 0 0 1
旋轉。
雖然計算這三個的凈效應並不太難(只需將它們相乘),但計算任意次數應用這些凈矩陣就超出了我的范圍(也許...)。 此外,您在x方向上平移的量正在變化,這幾乎是不可能的。
因此,解決此問題的另一種方法是定義單個轉換並將其應用於節點,然后在每個事件上對其進行修改。 這看起來像
Affine transform = new Affine() ; // creates identity transform
node.getTransforms().add(transform);
Timeline timeline = new Timeline(Duration.seconds(0.04), event -> {
double shiftX = ... ;
double shiftY = ... ;
double scaleX = ... ;
double scaleY = ... ;
double angle = 0.75 ;
Affine change = new Affine();
change.append(new Translate(shiftX, shiftY));
change.append(new Scale(scaleX, scaleY));
change.append(new Rotate(angle, new Point3D(1, 0, 0)));
transform.append(change);
});
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
如上所述, stop()
和pause()
將具有(幾乎)相同的效果。 (唯一的區別是再次播放時第一次更新的時間,對於stop()
來說是0.04秒,對於pause()
來說會更短-暫停下一次更新之前所剩下的時間。) “重置”動畫,您只需
timeline.stop();
transform.setToIdentity(); // resets to beginning
注意,通過使用這種技術,該節點僅對其應用了一次變換;對於該節點,僅進行一次變換。 我們只是隨着進展而更新該轉換。 舍入誤差仍然會累積,但是至少代數是可行的:)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.