简体   繁体   中英

Consecutive animation in TornadoFX?

After reading through the documentation, I'm still a bit confused on how to execute an animation after another one has completed. I have a timeline like so:

timeline {
  keyframe(Duration.seconds(0.5)) {
    keyvalue(firstImg.scaleXProperty(), 1.0, interpolator = Interpolator.EASE_BOTH)
    keyvalue(firstImg.scaleYProperty(), 1.0, interpolator = Interpolator.EASE_BOTH)
    keyvalue(firstImg.rotateProperty(), 0.0, interpolator = Interpolator.EASE_BOTH)
  }

  keyframe(Duration.seconds(0.5)) {
    keyvalue(secondImg.scaleXProperty(), 1.0, interpolator = Interpolator.EASE_BOTH)
    keyvalue(secondImg.scaleYProperty(), 1.0, interpolator = Interpolator.EASE_BOTH)
    keyvalue(secondImg.rotateProperty(), 0.0, interpolator = Interpolator.EASE_BOTH)
  }

  keyframe(Duration.seconds(0.5)) {
    keyvalue(thirdImg.scaleXProperty(), 1.0, interpolator = Interpolator.EASE_BOTH)
    keyvalue(thirdImg.scaleYProperty(), 1.0, interpolator = Interpolator.EASE_BOTH)
    keyvalue(thirdImg.rotateProperty(), 0.0, interpolator = Interpolator.EASE_BOTH)
  }

  keyframe(Duration.seconds(0.5)) {
    keyvalue(fourthImg.scaleXProperty(), 1.0, interpolator = Interpolator.EASE_BOTH)
    keyvalue(fourthImg.scaleYProperty(), 1.0, interpolator = Interpolator.EASE_BOTH)
    keyvalue(fourthImg.rotateProperty(), 0.0, interpolator = Interpolator.EASE_BOTH)
  }
}

This runs them all at once, but I would like to run each animation after the other one has finished! I can't quite figure out how to do this.. ( sorry if this is obvious, I am very new to Kotlin and Java in general! )

I see that the keyframe has an onFinished property but I can't quite figure out what I'm supposed to actually set it to. Is there a better way to do this? Thank you!

Based on the structure proposed by @tornadofx-fan I've added builders for sequentialTransition and parallelTransition, so starting from TornadoFX 1.7.9 you can do the same like this:

class TransitionViews: View() {
   val r1 = Rectangle(20.0, 20.0, Color.RED)
   val r2 = Rectangle(20.0, 20.0, Color.YELLOW)
   val r3 = Rectangle(20.0, 20.0, Color.GREEN)
   val r4 = Rectangle(20.0, 20.0, Color.BLUE)

   override val root = vbox {
       button("Animate").action {
           sequentialTransition {
               timeline {
                   keyframe(0.5.seconds) {
                       keyvalue(r1.translateXProperty(), 50.0, interpolator = Interpolator.EASE_BOTH)
                   }
               }
               timeline {
                   keyframe(0.5.seconds) {
                       keyvalue(r2.translateXProperty(), 100.0, interpolator = Interpolator.EASE_BOTH)
                   }
               }
               timeline {
                   keyframe(0.5.seconds) {
                       keyvalue(r3.translateXProperty(), 150.0, interpolator = Interpolator.EASE_BOTH)
                   }
               }
               timeline {
                   keyframe(0.5.seconds) {
                       keyvalue(r4.translateXProperty(), 200.0, interpolator = Interpolator.EASE_BOTH)
                   }
               }
           }
       }
       pane {
           add(r1)
           add(r2)
           add(r3)
           add(r4)
       }
   }
}

The timeline builder inside of these transitions don't automatically play, while the transition itself automatically plays when the builder is completed. You can pass play=false to the transition builder to disable autoplay.

Also note the usage of 0.5.seconds to generate the Duration objects :)

There's a JavaFX class "SequentialTransition" that will run your timelines in sequence. You'll need to disable the TornadoFX autoplay with a flag passed into the timeline builder. Check out ParallelTransition if you want to run these all at once using a similar coding pattern.

class STTest : View("My View") {

    val r1 = Rectangle(20.0, 20.0, Color.RED)
    val r2 = Rectangle(20.0, 20.0, Color.YELLOW)
    val r3 = Rectangle(20.0, 20.0, Color.GREEN)
    val r4 = Rectangle(20.0, 20.0, Color.BLUE)

    override val root = vbox {

        button("Animate") {
            setOnAction {

                val t1 = timeline(false) {
                    keyframe(Duration.seconds(0.5)) {
                        keyvalue(r1.translateXProperty(), 50.0, interpolator = Interpolator.EASE_BOTH)
                    }
                }
                val t2 = timeline(false) {
                    keyframe(Duration.seconds(0.5)) {
                        keyvalue(r2.translateXProperty(), 100.0, interpolator = Interpolator.EASE_BOTH)
                    }
                }
                val t3 = timeline(false) {
                    keyframe(Duration.seconds(0.5)) {
                        keyvalue(r3.translateXProperty(), 150.0, interpolator = Interpolator.EASE_BOTH)
                    }
                }
                val t4 = timeline(false) {
                    keyframe(Duration.seconds(0.5)) {
                        keyvalue(r4.translateXProperty(), 200.0, interpolator = Interpolator.EASE_BOTH)
                    }
                }

                /* functions look better
                val st = SequentialTransition()
                st.children += t1
                st.children += t2
                st.children += t3
                st.children += t4

                st.play()
                */  

                t1.then(t2).then(t3).then(t4).play()

            }
        }
        pane {
            add(r1)
            add(r2)
            add(r3)
            add(r4)
        }
    }

}

In this case where you're just setting scales and rotations, there are some nice helpers already in the library. This should work for you:

val time = 0.5.seconds
firstImg.scale(time, Point2D(1.0, 1.0), play = false).and(firstImg.rotate(time, 0.0, play = false))
        .then(secondImg.scale(time, Point2D(1.0, 1.0), play = false).and(secondImg.rotate(time, 0.0, play = false)))
        .then(thirdImg.scale(time, Point2D(1.0, 1.0), play = false).and(thirdImg.rotate(time, 0.0, play = false)))
        .then(fourthImg.scale(time, Point2D(1.0, 1.0), play = false).and(fourthImg.rotate(time, 0.0, play = false)))
        .play()

The play = false everywhere is required since these helpers were designed for simple one-off auto-playing animations.

Edit

After a discussion in Slack , these may be simplified in a future release, so the above may eventually be as easy as

val time = 0.5.seconds
listOf(
    firstImg.scale(time, 1 p 1) and firstImg.rotate(time, 0),
    secondImg.scale(time, 1 p 1) and secondImg.rotate(time, 0),
    thirdImg.scale(time, 1 p 1) and thirdImg.rotate(time, 0),
    fourthImg.scale(time, 1 p 1) and fourthImg.rotate(time, 0)
).playSequential()

Watch the release notes for more info!

Another Edit

Looks like I was over complicating things a bit. You can just use this if you like it more:

val time = 0.5.seconds
SequentialTransition(
    firstImg.scale(time, Point2D(1.0, 1.0), play = false).and(firstImg.rotate(time, 0.0, play = false)).
    secondImg.scale(time, Point2D(1.0, 1.0), play = false).and(secondImg.rotate(time, 0.0, play = false)),
    thirdImg.scale(time, Point2D(1.0, 1.0), play = false).and(thirdImg.rotate(time, 0.0, play = false)),
    fourthImg.scale(time, Point2D(1.0, 1.0), play = false).and(fourthImg.rotate(time, 0.0, play = false))
).play()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM