-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changeable in animation speed of JFXRippler #773
Comments
The right spot for this is line 539 in class JFXRippler: |
Hello, Regards, |
I'm making progress with the implementation. Are there any settings for the code formatter? |
yes we are using google standard. Feel free to share your progress in a PR, so we can start reviewing the implementation. Regards, |
OK I changed and added some things but I have to cleanup and refactor. So I thought about what we could use for a simple animation experience. I used some CSS animations from this site https://daneden.github.io/animate.css/ @keyframes tada {
from {
transform: scale3d(1, 1, 1);
}
10%,
20% {
transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
}
30%,
50%,
70%,
90% {
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
}
40%,
60%,
80% {
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
}
to {
transform: scale3d(1, 1, 1);
}
}
.tada {
animation-name: tada;
} This is the java JFXAnimation representation: JFXAnimation.builder()
.from()
.action(b -> b.target(Node::scaleXProperty, Node::scaleYProperty).endValue(1))
.percent(10, 20)
.action(b -> b.target(Node::scaleXProperty, Node::scaleYProperty).endValue(0.9))
.action(b -> b.target(Node::rotateProperty).endValue(-3))
.percent(30, 50, 70, 90)
.action(b -> b.target(Node::scaleXProperty, Node::scaleYProperty).endValue(1.1))
.action(b -> b.target(Node::rotateProperty).endValue(3))
.percent(40, 60, 80)
.action(b -> b.target(Node::scaleXProperty, Node::scaleYProperty).endValue(1.1))
.action(b -> b.target(Node::rotateProperty).endValue(-3))
.to()
.action(b -> b.target(Node::scaleXProperty, Node::scaleYProperty).endValue(1))
.action(b -> b.target(Node::rotateProperty).endValue(0))
.config(b ->b.duration(Duration.seconds(1.3)).interpolator(Interpolator.EASE_BOTH).infiniteCycle()); So you can use the "from()", "to()" or "percent(...)" method for defining the different keyframes. The "JFXAnimationValue" builder has also some specials 😉 new KeyValue(scaleXProperty(), 1, Interpolator.EASE_IN)
new KeyValue(scaleYProperty(), 1,Interpolator.EASE_IN) or with the action methods: ...
.action(b -> b.target(Node::scaleXProperty).endValue(1).interpolator(Interpolator.EASE_IN))
.action(b -> b.target(Node::scaleYProperty).endValue(1).interpolator(Interpolator.EASE_IN))
... So for an easier handling the "target(...)" method can also use varargs for such cases like: .action(b -> b.target(Node::scaleXProperty, Node::scaleYProperty).endValue(1)) There is also the possibility to define a "onFinish(...)" handler or an "animateWhen(...)" condition even if you have no animation target like: .action(b -> b.target(Node::scaleXProperty, Node::scaleYProperty).endValue(1).onFinish((node, event) -> System.out.println("Hello")))
.action(b -> b.noTargets().onFinish((node, event) -> System.out.println("Visible")).animateWhen(Node::isVisible)) If you are done with the animation actions the last part of the building process is the configuration of the timeline like: ...
.config(b -> b.duration(Duration.seconds(1.3)).interpolator(Interpolator.EASE_BOTH).infiniteCycle()); So the duration is the total duration of the animation. After the configuration part you just call the ".build(T node)" method with your Node as parameter. Another feature which I implemented is a SVG Rippler cutout so you can define a "SVGGlyph" which is subtracted or intersected with a Rippler circle. I also made some minor changes e.g. for the RegexValidator because I had also defined one by myself and doesn't know that there already exist one on the master branch. You can find the code changes on my fork: |
Hello @schlegel11 , very nice work 👍 well done. I did a quick review for the code and it looks pretty good, I like how you restructured the animation creation process to be similar to web css 👍. I believe it's more straight forward than the standard way of creating animations. However, the thing is we already have JFXAnimationTimer/JFXKeyValue/JFXKeyFrame in JFoenix ( this implementation is based on AnimationTimer which adds some flexibility to the animation, so you can have conditional KeyValues / changeable end value / injection of external properties to be animated ). So we need to make it clear somehow to differentiate between these 2 methods, otherwise it might be confusing for the user. Regarding the cutout, It's a very cool idea ;) never thought of it before. I noticed that you changed the Ripple class so it extends Path instead of Circle, I need to do a full review to make sure it's well structured. All in all, it looks very good, great job 👍 |
Hi, But yes you are totally right. I saw JFXAnimationTimer/JFXKeyValue/JFXKeyFrame classes and thought about how can I integrate them with my approach. In the first version of JFXAnimation I tried to use the JFXKeyValue but due to some not so good cast's for the KeyVaule I implemented the new generic JFXAnimationValue. But yes I liked the JFXKeyValue approach with the Supplier as return values and also the animateCondition. That's why I also implemented the animateWhen method or for laziness the Suppliers 😉 Ah and yes for the cutout I had to replace the Circle extension for the Ripple with the Path extension and use the Circle for the cutout which ends always in a Path object:wink: |
Hi, can you specify the not so good casts of JFXKeyValue? Also I noticed in JFXAnimationKeyValue, all the suppliers/functions are dependent on the animation node, which I don't think is a good dependency. |
Hi, JFXKeyValue a =
JFXKeyValue.builder()
.setTarget(rippler.scaleXProperty())
.setEndValue("works but makes no sense")
.build();
//return type of "getTarget" is unchecked.
new KeyValue(a.getTarget(), a.getEndValue()); I also have to deal with the case that I could set some type to a specific WritableValue and it's only discovered at runtime. JFXAnimation.JFXAnimationValue<?, Number> b =
JFXAnimation.JFXAnimationValue.builder()
.target(rippler.scaleXProperty())
//Method cant be resolved cause Number is required.
.endValue("dont work cause need Number type")
.build();
new KeyValue(b.getFirstTarget().orElse(/*something if there are no targets*/), b.getEndValue()); In this case I can't set a wrong type to a specific WritableValue. JFXAnimation.JFXAnimationValue<?, Number> b ... because in this case I don't need a reference to my Node (in this case a Rippler). So the Rippler instance is in my actual method scope and I can directly specify my target ... .target(rippler.scaleXProperty()) ... But if I want to predefine my animation lazily as a template for any possible target which is related to an unknown object instance I could do this: public static final JFXAnimation.Builder<Node> FLASH =
JFXAnimation.builder(JFXRippler.class)
.from()
.percent(50)
.action(b -> b.target(JFXRippler::opacityProperty).endValue(0))
.percent(25, 75)
.action(b -> b.target(JFXRippler::opacityProperty).endValue(1))
.config(b -> b.duration(Duration.seconds(1.5)).interpolator(Interpolator.EASE_BOTH)); In this case all I know is that I want my animation used with a e.g JFXRippler type and I need to modify some JFXRippler/Node type properties. |
I improved the JFXAnimation a little bit so I removed the I also added the possibility to add more instances of animation nodes which can be used for modify other KeyValue targets. So I rely on it that if a Animation is defined at least one main animation node could be used like this normal case: public static final JFXAnimation.Builder<Node> FLASH =
JFXAnimation.builder()
.from()
.percent(50)
.action(b -> b.target(Node::opacityProperty).endValue(0))
.percent(25, 75)
.action(b -> b.target(Node::opacityProperty).endValue(1))
.config(b -> b.duration(Duration.seconds(1.5)).interpolator(Interpolator.EASE_BOTH)); If I call "FLASH.build(myNodeObject)" one node object is used. public static final JFXAnimation.Builder<Node> FLASH =
JFXAnimation.builder()
.from()
.percent(50)
.action(b -> b.target(Node::opacityProperty).endValue(0))
//here I want also animate some Rectangle object so I define the type of it and key.
.action(b -> b.withClass(Rectangle.class, "rect").target(Rectangle::translateXProperty).endValue(70))
.percent(25, 75)
.action(b -> b.target(Node::opacityProperty).endValue(1))
.config(b -> b.duration(Duration.seconds(1.5)).interpolator(Interpolator.EASE_BOTH)); Rectangle rectangle = new Rectangle();
//This rectangle object should be used in the animation on a "withClass(Rectangle.class, "rect")" call.
//In all other cases the "myMainNodeObject" could be simply used.
FLASH.build(myMainNodeObject, new Pair<>("rect", rectangle)); |
I also added now a builder function for building a JFXAnimationTimer. //default build a Timeline
Timeline timeline = FLASH.build(node);
//or by specify the Timeline builder
Timeline timeline = FLASH.build(JFXAnimation::buildTimeline, node);
//build a JFXAnimationTimer
JFXAnimationTimer timer = FLASH.build(JFXAnimation::buildAnimationTimer, node);
To my surprise I have also seen that |
I made some refactor steps. I think those were all the adjustments I had in mind so far. |
Regarding type safety for JFXKeyValue, I agree with you, it's better to make JFXKeyValue builder type safe. Moreover, how about combining these two classes JFXKeyValue/JFXAnimationValue? so it would be less confusing for the user. One JFXKeyValue class and its builder instead of having multiple classes for the same purpose. |
I updated JFXKeyValue builder to ensure type safety and pushed the fix |
Ah great that's a really good enhancement. // Here the Node object is the helper object which properties are used.
.action(b -> b.target(Node::opacityProperty).endValue(0)) Also there are some other new methods like "ignoreAnimation()", "onFinish(...)", "withClass(...)" to make the creation process easier. All in all I would say they doesn't share the same purpose because the JFXAnimationCreator and also the JFXAnimationCreatorValue and the other classes are part of a general builder for a Timeline or for a AnimationTimer or for some other Animation related construct. On the other hand the JFXKeyValue class is a specific Builder or part of a specific construct in this case the JFXAnimationTimer. It's comparable with the KeyValue class which is specifically used for the Timeline class. |
Maybe we need another name for the JFXAnimationCreatorValue class. Something like JFXAnimationCreatorAction because it's related to the action method in the builder process. |
I guess, maybe we need a better naming. I'm usually bad at naming but how about JFXAnimationTemplate? also I have some questions regarding the code:
|
Hi,
.action(b -> b.target(Node::opacityProperty).endValue(0)) or without method reference: .action(b -> b.target(node -> node.opacityProperty()).endValue(0)) you can directly use the properties from your node or whatever.
I refactored all the names that it makes hopefully a little more sense 😉 Ah and I also changed the JFXKeyValue so that the |
Hi @schlegel11, Also I noticed you are using default methods, this will break the mobile build. We can't have default methods on mobile just yet. |
Also if you can provide some examples that cover all cases it would appreciated. |
Hi, The //This action uses a Node type as animation object. For the layoutX and Y properties.
JFXAnimationTemplateAction<Node, Number> action =
JFXAnimationTemplateAction.builder()
.target(Node::layoutXProperty, Node::layoutYProperty)
.endValue(10)
.interpolator(Interpolator.LINEAR)
//In the builder method we have to provide the specific object for the Node type in this case a JFXRippler rippler1 object. The key can be ignored.
.build(key -> rippler1);
//This action uses a specific JFXSlider type.
JFXAnimationTemplateAction<JFXSlider, Number> action2 =
JFXAnimationTemplateAction.builder(JFXSlider.class)
.target(JFXSlider::translateXProperty, JFXSlider::translateYProperty)
.endValue(10)
.interpolator(Interpolator.LINEAR)
//We must build with a JFXSlider object.
.build(key -> slider);
//In this action we don't use the lazy build ability and can use the rippler1 object directly.
JFXAnimationTemplateAction<?, Number> action3 =
JFXAnimationTemplateAction.builder(JFXSlider.class)
.target(rippler1.translateXProperty(), rippler1.translateYProperty())
.endValue(10)
.interpolator(Interpolator.LINEAR)
//We don't use a lazy object in the target methods so we don't set it here (internally it's set to null)
.build();
//This action overrides the default Node type for the animation object with a JFXSlider type.
JFXAnimationTemplateAction<JFXSlider, Number> action =
// The builder method gives us just the default Node type.
JFXAnimationTemplateAction.builder()
// The withAnimationObject defines that the target methods should use the lazy object of JFXSlider type and not the Node type which is the default one. Furthermore a specific key is set.
.withAnimationObject(JFXSlider.class, "slider")
.target(JFXSlider::translateXProperty, JFXSlider::translateYProperty)
.endValue(10)
.interpolator(Interpolator.LINEAR)
//With the key it is possible to use e.g. a HashMap for providing different types and animation objects.
.build(key -> myMap.get(key)); The JFXAnimationTemplateConfig config =
JFXAnimationTemplateConfig.builder().duration(Duration.seconds(5)).cycleCount(2).build(); The Ah ok that's the reason why the default methods were disabled 😉 I will replace the methods with general implementations. Yes you are right some examples would be good. |
Yes I noticed the similarity between JFXAnimationTemplate and Timeline. but my question was, what's the point of having a builder for JFXAnimationTemplate when it only contains its builder as a field? I mean JFXAnimationTemplate doesn't declare actions/config directly as fields, instead actions/config are stored inside JFXAnimationTemplate.Builder which is declared as a field in JFXAnimationTemplate. so I was wondering why not making the JFXAnimationTemplate as the builder itself instead of creating inner class called Builder inside JFXAnimationTemplate. I'm not sure whether the builder pattern is applied here correctly or not. |
Ah ok I got it 😊 |
Ok I removed the default methods. |
@schlegel11 it looks good to me, however one more thing I noticed in In case we merged all builders, and we have 1 builder for |
Hi, You are right we could replace the Function with a Consumer in the ...
.action(b -> b.animateWhen(....).onFinish(...))
... but also this case which works but makes no sense: ...
.action(b -> b.animateWhen(....).onFinish(...).build())
... Furthermore the Function param can act as a Supplier e.g. if we don't want to use the given builder instance but our own: ...
.action(b -> JFXAnimationTemplateAction.builder().animateWhen(....).onFinish(...))
... The Consumer just can handle the given reference but no new instance. |
I see your point, maybe its better to have a function here. |
Hi, The cooperation with you was a lot of fun for me and very pleasant. |
Hi, Same for me :) thank you for such a cool feature :) |
Glad to see @schlegel11 already working on this. I am working on a JavaFX based POS system with JFeonix, i wanted to use this feature :) |
Hi @schlegel11 |
Hello, Greetings |
That's cool maybe we can add it to the main demo too :) well done @schlegel11 👍 |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
It would be great if there is a method where you can set a duration speed of an animation like:
rippler.setInAnimationSpeed(Duration d);
or something like that :)
The text was updated successfully, but these errors were encountered: