QML Animation with both "velocity" and infinite "loops" - qt

I'm trying to put together an animation in which I get to specify the velocity (rather than the duration) and which loops forever. I came up with two non-working examples:
FirstTry.qml
import Qt 4.7
Rectangle {
width: 100; height: 100
Text {
text: "hello"
NumberAnimation on x {
to: 50;
loops: Animation.Infinite;
duration: 50 * Math.abs(to - from)
}
}
}
I get the following runtime warning while hello goes nuts on the screen (fair enough).
QDeclarativeExpression: Expression "(function() { return 50 * Math.abs(to - from) })" depends on non-NOTIFYable properties:
QDeclarativeNumberAnimation::to
QDeclarativeNumberAnimation::from
SecondTry.qml
import Qt 4.7
Rectangle {
width: 100; height: 100
Text {
text: "hello"
SmoothedAnimation on x {
to: 50;
loops: Animation.Infinite;
velocity: 50
}
}
}
This is more of a mistery -- SmoothedAnimation simply refuses to loop! The Animation runs once and then that's it.
So I have the following questions:
Is there a legal way to specify the velocity in the first example? I understand SmoothedAnimation is derived from NumberAnimation, so maybe it's possible in QML, not just in C++.
Is there a way to make SmoothedAnimation loop? Is the second example not working a bug or am I missing something?
Is there any other way to achieve these two behaviours at the same time?

just add "from" parameter explicitly:
import Qt 4.7
Rectangle {
width: 100; height: 100
Text {
text: "hello"
NumberAnimation on x {
from: 0;
to: 50;
loops: Animation.Infinite;
duration: 50 * Math.abs(to - from)
}
}
}

This is what I did as a temporary solution, I'm not sure if it's adequate, but it seem to be doing just what I needed.
SmoothedAnimation on x {
to: 50;
//loops: Animation.Infinite;
velocity: 50
onComplete: { restart (); }
}
I'm still interested in the answers to the questions, though.

Even if SmoothedAnimation doesn't accept loops parameter, you can place it inside SequentialAnimation and apply loops to this external cover. As an effect your smoothed animation will be played continuosly.

Related

NumberAnimation does not take into account property change until stopped

I have a QML application with 2 rectangles: a large rectangle and a small rectangle inside the large one.
The small rectangle is animated and moves inside the large rectangle.
The animation is done by combining 2 NumberAnimation in a SequentialAnimation.
It works well, except that the to property of one of the NumberAnimation can change.
I would except the change of value to be applied immediately.
However, it is not taken into account until the animations are fully stopped and restarted.
Calling stop()/start() or restart() does not do anything.
I need to wait for the animation to actually finish and then start it again.
This can be demonstrated with the following QML code:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Window {
width: 640
height: 480
visible: true
RowLayout {
Rectangle {
id: topRect
width: 400
height: 400
border {
color: "red"
width: 2
}
Rectangle {
id: animatedRectangle
width: 100
height:100
color: "blue"
}
SequentialAnimation{
id: animation
loops: Animation.Infinite
running: cbAnimate.checked
alwaysRunToEnd: true
NumberAnimation {
id: forwardAnimation
target: animatedRectangle
property: "x"
to: sbWidth.value
duration: 2000
}
NumberAnimation {
id: backwardAnimation
target: animatedRectangle
property: "x"
to: 0
duration: 2000
}
}
}
ColumnLayout {
CheckBox {
id: cbAnimate
text: "Animate"
}
SpinBox {
id: sbWidth
value: 300
to: 400
}
SpinBox {
value: forwardAnimation.to
to: 999
}
}
}
}
Start the animation with the checkbox
Change the value of to with sbWidth
See in the other SpinBox that the value of to was changed
Observe that the animation is still using the old value
Stop the animation, wait for the Rectangle to stop moving, Start the animation
Observe that the animation is using the value set in step 2
Isn't there a way to make the animation use the new value of to immediately?
This behavior is particularly painful when a QML element is animated by default and the to value depends on the geometry of Items, as during the creation of the QML scene Qt will create and then resize Items. Meaning that animation started at creation time won't get their values updated during the resize.
TLDR
In this particular case the best solution is to use the workaround suggedted by #stephen-quan: animate a proxy property property real animatedX between 0.0 and 1.0. And then bind the property I want to animate to this animated property and do the extra computation in this binding: x: animatedX * sbWidth.value. Eliminating the need of changing the to property of the animation.
Details
The issue of animations not taking property change into account until restarted is a very old issue. It has been reported numerous times:
PropertyAnimation ignores from/to changes
Changing an Animation
duration has no effect
Since Qt 6.4, the state has slightly improved. Quoting Qt documentation:
Since Qt 6.4, it is possible to set the from, to, duration, and easing properties on a top-level animation while it is running. The animation will take the changes into account on the next loop.
However, it still does not affect the current loop and requires the animation to be top-level. So even with this improvement, I still need to animate a proxy property, ensuring changes are taken into account in real-time.
I made various changes to your sample.
I introduced from: 0 to your first NumberAnimation. This ensures that whenever you stop/start the animation, it will reset. Also, removing alwaysRunToEnd helps with that.
I introduced a new property double val: 0 property which will range from 0.0 to 1.0. This is what I used NumberAnimation on instead of x. The advantage is, we know that the NumberAnimation will happily move from 0.0 to 1.0 and back to 0.0 consistently.
Then, I introduced a formula linking val to x and takes into account of sbWidth.value.
To make it easier to change sbWidth.value I changed it from a SpinBox to a Slider.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
RowLayout {
Rectangle {
id: topRect
width: 400
height: 400
border {
color: "red"
width: 2
}
Rectangle {
id: animatedRectangle
property double val: 0.0
x: val * sbWidth.value
width: 100
height:100
color: "blue"
Label {
anchors.centerIn: parent
text: parent.x.toFixed(2)
color: "white"
}
}
SequentialAnimation{
id: animation
loops: Animation.Infinite
running: cbAnimate.checked
//alwaysRunToEnd: true
NumberAnimation {
id: forwardAnimation
target: animatedRectangle
property: "val"
from: 0
to: 1.0
duration: 2000
}
NumberAnimation {
id: backwardAnimation
target: animatedRectangle
property: "val"
to: 0
duration: 2000
}
}
}
ColumnLayout {
CheckBox {
id: cbAnimate
text: "Animate"
}
Slider {
id: sbWidth
value: 300
from: 100
to: 400
}
Text {
text: sbWidth.value.toFixed(2)
}
}
}
}
You can Try it Online!

Drawer animation in QML

How to change the animation (i.e duration and easing curve) of the Drawer type in QML?
I've tried this :
PropertyAnimation{
easing.type: Easing.InOutQuad
easing.amplitude: 1000
easing.period : 1000
}
But it has no effect.(Sorry but the diversity of animation types in QML has got me confused and I'm unable to try all possible options)
You'll need to override the Popup::enter transition as documented here:
https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#enter-prop
Note, the drawer implementation makes a lot of assumptions about how it gets on and off screen so it is easy to break it if you aren't careful.
You can see the default ones here:
https://github.com/qt/qtquickcontrols2/blob/dev/src/imports/controls/Drawer.qml
enter: Transition { SmoothedAnimation { velocity: 5 } }
exit: Transition { SmoothedAnimation { velocity: 5 } }
So, start from there and slowly tweak until you get what you want.

Temporarily disable (ignore / not display) the animation on a complex QML component

Is it possible to temporarily disable (ignore / not display) the animation on a complex QML component until a certain point in time? And later activate the animation and work as usual.
For example. A complex page on QML displays the data of the object, there are many small animations. When changing a data object, these animations should be ignored.
Rectangle {
anchors.fill: parent
property variant cppViewModel: MyCppViewModel {
onBeforDataObjectChanged: {
}
onAfterDataObjectChanged: {
}
}
Rectangle {
id: idRect1
Behavior on x { NumberAnimation { ... }}
Behavior on y { NumberAnimation { ... }}
x: cppViewModel.dataObject.offsetX
y: cppViewModel.dataObject.offsetY
scale: cppViewModel.dataObject.scale
Rectangle {
id: idRect2
width: cppViewModel.dataObject.width
heigth: cppViewModel.dataObject.heigth
Behavior on width { NumberAnimation { ... }}
Behavior on heigth { NumberAnimation { ... }}
ColumnLayout {
Rectangle {
Layout.preferredHeight: 100 * cppViewModel.dataObject.width1
Behavior on Layout.preferredHeight { NumberAnimation { duration: 500; easing.type: Easing.OutQuad; }}
//... Any number of children with animation
}
}
}
}
PropertyAnimation { target: idRect1; property: "scale"; from: 0.9; to: 1.0; ... }
}
If the values of the properties of the current data object change, then animation is needed. If the entire object changes to another, then the animation needs to be blocked.
To disable Animations, there are various ways, and the right one depends on how the Animation is started.
If the Animation is started by setting the running-property, you can simply add a && animationsEnabled
to the condition where animationsEnabled is a property, you have to define somewhere else and toggle it accordingly.
If you use the function: run() to start your Animation, the solution is to not do it.
If you use the Animation within a Behavior, you can use the Behaviors enabled-property to deactivate the Behavior and its Animation.
Finally, I can think of Transitions. Just as Behavior, Transition has an enabled-property, to deactivate it.
I hope I have not forgotten a way to animate and you will find the appropriate solution for your problem!

Property Binding on Animated Property vs Multiple Animations

Consider this example:
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
id: appWindow
width: 1024
height: 800
visible: true
Rectangle {
id: rect1
property bool active: true
opacity: active ? 1 : 0
height: 300 * opacity
width: 300 * opacity
Behavior on opacity { NumberAnimation { duration: 1000 } }
MouseArea { anchors.fill: parent; onClicked: parent.active = false }
color: 'cornflowerblue'
}
Rectangle {
id: rect2
property bool active: true
x: 305
opacity: active ? 1 : 0
height: active ? 300 : 0
width: active ? 300 : 0
Behavior on opacity { NumberAnimation { duration: 1000 } }
Behavior on height { NumberAnimation { duration: 1000 } }
Behavior on width { NumberAnimation { duration: 1000 } }
MouseArea { anchors.fill: parent; onClicked: parent.active = false }
color: 'steelblue'
}
}
I have two Rectangles with the same observable behavior: when clicked, they fade both in opacity and size.
Internally, it differs in the amount of Animations, that are running concurrently - either 1 or 3:
As of now, I mainly use the pattern form rect1 and only in cases where the bindings would get unneccessarily complex rect2. However I wonder, if the animation system has some magic, that optimizes the animation of a single property, while the binding might be less performant.
In which usecases it is beneficial to use pattern rect1 and when it would be wiser to use the method of rect2?
EDIT There is also a third option which moves, what possible, to the render thread via OpacityAnimator. Now I can't bind to the opacity anymore, as it will jump to 0 at the end of the animation.
Rectangle {
id: rect3
property bool active: true
opacity: active ? 1 : 0
height: active ? 300 : 0
x: 610
width: height
Behavior on opacity { OpacityAnimator { duration: 1000 } }
Behavior on height { NumberAnimation { duration: 1000 } }
MouseArea { anchors.fill: parent; onClicked: parent.active = false }
color: 'dodgerblue'
}
EDIT 2 To adress the Answer of Ansh Kumar:
This is an excerpt from the QML Profiler. You can see, that during the animation of rect2 there are neither bindings nor JavaScript running, unlike during the times where height and width are (efficiently) bound to the opacity in rect1 or the width is (efficiently) bound to the height in rect3.
Further the source of the animations shows little trace of JS. I couldn't examine it into all it's depths, but it seems, that only a ScriptAction gets a QQMLScriptString and the rest has only the cost of converting the input from var to the right type (if a type is specified by using a concrete animation such as NumberAnimation).
Further, as far as I can see, there is not a loop per animation involved, but all animations feature some kind of update()-function or so, that is called (when running/registered) by a single loop (AnimationTimer). But this is where I am already unsure about.
Now the question remains: Is the implementation of the animations more efficient than the optimized JS environment especially as multiple objects are created and stuff.
There are two types of bindings in QML: optimized and non-optimized bindings. It is a good idea to keep binding expressions as simple as possible, since the QML engine makes use of an optimized binding expression evaluator which can evaluate simple binding expressions without needing to switch into a full JavaScript execution environment. These optimized bindings are evaluated far more efficiently than more complex (non-optimized) bindings. The basic requirement for optimization of bindings is that the type information of every symbol accessed must be known at compile time.
Bindings are quickest when they know the type of objects and properties they are working with. Animating a property will cause any bindings which reference that property to be re-evaluated. Usually, this is what is desired. The opacity, height and width in rect2 are re-evaluated into a full JavaScript execution environment whereas in rect1; width and height goes through an optimized binding expression evaluator and optimized to give more efficient binding since their type of object is known at compile time. Check binding and also animations for more details.
EDIT
You were right about evaluation being done in C++ environment. I found following informations.
Rendering engine should achieve a consistent 60 frames-per-second refresh rate. 60 FPS means that there is approximately 16 milliseconds (exactly 16.6667 milliseconds) between each frame in which processing can be done, which includes the processing required to upload the draw primitives to the graphics hardware. This shows that the animation is in sync with the vertical refresh, so once every 16.66 ms, and exactly once pr frame.
while (animationIsRunning) {
processEvents();
advanceAnimations();
paintQMLScene();
swapAndBlockForNextVSync();
}
So, in rect1 you have set duration: 1000 and binded height with opacity (height: 300 * opacity) similarly width with opacity, so binding should be called around 60 times ? If you see QML profiler output of statistics you will find following
As expected number of calls are around 60 (exactly 63). Now if you change duration to 2000, number of calls will be doubled.
Since, 300 * opacity has to be calculated, so QML should call JavaScript environment around 60 times (when duration: 1000)
As expected it was called around 60 times.
What about the NumberAnimation, is it implemented in JavaScript or C++ ? Definitely, you were right about it being implemented in C++, Here is the link to its declaration . So, in rect1 we have used NumberAnimation one time and in rect2 we have used it 3 times. So, total of 4 instances of NumberAnimation should be created.
So, rect1 has a total of around 120 bindings and JavaScript calls whereas in in rect2 there is no binding and JavaScript calls, so animation of rect2 should be faster, but the question is, will there be any significant improvements? Since, free version of QtCreator does not comes with CPU analyzer I was not able to study that part of the question (CPU Usage Qt). If anyone has commercial version of Qt, please update me about my hypothesis. I really think that rect2 is the best for usage as number of calls are reduced.

ListView scrolling animation

I want to implement a scrolling animation for QML ListView. Here is a sample image:
Can anybody advise me for implementing this?
Thank you.
The ViewTransition provides a lot of interesting examples on how to animate a ListView for operations like populate (the transition for the initial items at component creation), add, remove (self-explanatory) as well as other operations.
Given a ListView you define an element Transition for each operation you want to animate. The animation framework can be exploited to create compound animations, by simply combining the basic animations to create the (more or less) complex behaviour you are interested in (see also here for an actual example).
Here a definition for a ListView (the first linked document provides some nice images):
ListView {
// data model, delegate, other usual stuff here...
// transitions for insertion/deletation of elements
add: Transition {
NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 500 }
NumberAnimation { property: "scale"; easing.type: Easing.OutBounce; from: 0; to: 1.0; duration: 750 }
}
addDisplaced: Transition {
NumberAnimation { properties: "y"; duration: 600; easing.type: Easing.InBack }
}
remove: Transition {
NumberAnimation { property: "scale"; from: 1.0; to: 0; duration: 200 }
NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 200 }
}
removeDisplaced: Transition {
NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.OutBack }
}
}
Finally, note that some behaviours can be obtained by using Shaders and combining animation on the elements and transitions on the delegate/elements of the delegate. A nice example is Tweet Search, in which a shading effect (see [ShaderEffect][5]) on the bar item is combined with a simple Transition on ListView add.
EDIT
Provide a customized scrolling like in the examples requires to take in account the position of the Items inside the ListView. A key to a working solution is to find a way to calculate the current position of the Item inside the visible part of the view and use that value to calculate the appropriate transformation. ListView derives from Flickable which has several useful properties for this purpose.
However, the y property of the Item is referred to the overall height of the content inside the ListView. To have its position w.r.t. the beginning of the visible area we can use the contentY property. A picture is worth a thousand words in this case:
The difference between y and contentY provides a value which can be used to calculate the required transformation factor (maybe in relation to the height of the ListView). Indeed, as the ListView is flicked, the two values and their difference change and so changes the transformation factor for a specific Item.
Such transformation covers only part of the problem. Once the flicking/movement ends the Items animation must be "finished" to make all the visible items usable. For this purpose we can exploit Binding and its when property to activate the finishing animation only when required, i.e. when flicking or dragging ends.
Given all this (boring) introduction, let's take in account the second animation (the simpler one). Here we can use scale to obtain the desired effect. The delegate code inside the ListView looks like the following:
ListView {
id: list
model: 100
spacing: 10
delegate: Rectangle {
id: itemDelegate
property int listY: y - list.contentY // stores the difference between the two values
width: parent.width
height: 50
border.color: "lightgray"
color: "red"
Binding {
target: itemDelegate
property: "scale"
value: 1 - listY / list.height / 2 // the "scale" property accepts values in the range [0, 1]
when: list.moving || list.flicking || list.dragging // ...when moved around
}
Binding {
target: itemDelegate
property: "scale"
value: 1 // flick finished --> scale to full size!
when: !(list.moving || list.dragging) // not moving or dragging any more
}
Behavior on scale {
NumberAnimation { duration: 100; to: 1}
enabled: !(list.flicking || list.dragging) // active only when flick or dragging ends!
}
}
}
The first Binding define the scaling factor on the basis of listY whereas the second one set the scaling to 1 but only when the ListView is not moving. The final Behavior is necessary to smooth the transition to the fully scaled Item.
The third effect can be obtained in a similar fashion with a Rotation:
ListView {
anchors.fill: parent
id: list
spacing: 10
model: 100
delegate: Rectangle {
id: itemDelegate
property int listY: y - list.contentY
property real angleZ: (90 * listY) / list.height // 0 - 90 degrees
transform: Rotation { origin.x: width / 2; origin.y: 30; axis { x: 1; y: 0; z: 0 } angle: angleZ}
//transform: Rotation { origin.x: 0; origin.y: 30; axis { x: 1; y: 1; z: 0 } angle: angleZ} <--- I like this one more!
width: parent.width
height: 50
border.color: "lightgray"
color: "red"
Binding {
target: itemDelegate
property: "angleZ"
value: 0
when: !(list.moving || list.dragging)
}
Behavior on angleZ {
NumberAnimation {duration: 200; to: 0}
enabled: !(list.flicking || list.dragging)
}
}
}
This time I've choosen to (arbitrarily) use only one Binding. The same could have been made for the first example, i.e. we could have written in the first delegate scale: 1 - listY / list.height / 2.
Following a similar approach you can also create the first animation and others. For the first animation I think that combining a Rotation with a Translate should suffice.
After many hours of work, research and #BaCaRoZzo's great help (Thanks #BaCaRoZzo), I finally found the right solution. Just use Component.onCompleted() event handler to run the animation associated with each delegate.
Here is an example, enjoy!
import QtQuick 2.3
ListView {
anchors.fill: parent
id: list
model: 100
cacheBuffer: 50
delegate: Rectangle {
id: itemDelegate
Component.onCompleted: showAnim.start();
transform: Rotation { id:rt; origin.x: width; origin.y: height; axis { x: 0.3; y: 1; z: 0 } angle: 0}// <--- I like this one more!
width: parent.width
height: 50
color: index % 2 === 0 ? "#EEE" : "#DDD"
SequentialAnimation {
id: showAnim
running: false
RotationAnimation { target: rt; from: 180; to: 0; duration: 800; easing.type: Easing.OutBack; property: "angle" }
}
}
}
A PathView displays data from models created from built-in QML types like ListModel and XmlListModel, or custom model classes defined in C++ that inherit from QAbstractListModel.
The view has a model, which defines the data to be displayed, and a delegate, which defines how the data should be displayed. The delegate is instantiated for each item on the path. The items may be flicked to move them along the path.

Resources