How to subclass Behavior in QML - qt

I have a number of objects on my screen that I change the opacity of, and to make this opacity change animated instead of instantaneous, I add this Behavior to each of the objects:
Behavior on opacity {
NumberAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
I am attempting to subclass this (not sure if "subclass" is the correct term here). I've created a file named FadeBehavior.qml with these contents:
import QtQuick 2.3
import QtQuick.Controls 1.3 as Controls
Behavior on opacity {
id: fadeBehavior
NumberAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
Then, instead of adding the Behavior to each object, I'm adding:
FadeBehavior { }
But this is not working (sorry I can't add more information than "not working" - this is an inherited app and I have not been able to run it in debug mode; when I make a mistake in my QML file, all that happens is that my window comes up as a blank one-inch square).
It seems as if the on opacity in the first line is the part Qt doesn't like. In FadeBehavior.qml on opacity is underlined in red, expecting token {. Is there some other syntax for specifying the name of the property the Behavior is attached to?

Here is one work around that would work.
Subclass all of the standard types that you need (Rectangle, Item, Image, etc.) like this
Item {
Behavior on opacity {
NumberAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
}
}
Save this as MyItem.qml and change every Item to MyItem.

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!

QML Remove Animation of ListView Item with a QSqlTableModel

I'm using a list view with a QSqlTableModel in the background. I use also a SwipeDelegate for the list view elements to provide a "delete button", as shown here:
When the button is now pressed, the the item is deleted from the database using this code in the QSqlTableModel subclass:
void qsoModel::deleteQSO(int id) {
removeRow(id);
submitAll();
}
Side question: When I understood it correctly, removeRow is implicitly calling beginRemoveRows(...) and endRemoveRows()?
For the ListView I used a remove Transition:
ListView {
id: listView
anchors.fill: parent
model: qsoModel
//...
delegate: QSOItem {}
remove: Transition {
NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 400 }
NumberAnimation { property: "scale"; from: 1.0; to: 0.0; duration: 400 }
}
However, If I press the delete button, the animation is not shown. the list element just disappears fastly. Any ideas why this is happening?
The complete source code can be found in this commit: https://github.com/myzinsky/cloudLogOffline/tree/b501d41a0f23d40139cfca9a6d4f724f4ab251b2
From looking at the code on github it looks like the model is executing the beginRemoveRows() and endRemoveRows() methods so I think this won't be the issue. And you say the records are removed from the database so I think the problem is more related to qml.
So regarding the qml animations there are few things:
First thing, if you want the opacity and scale animation parallel you will need to wrap them in a ParallelAnimation. Second thing is you will need to specify a transition for removeDisplaced otherwise the items that would be moved up in the list when you delete an item from the list are just being pushed to the new position covering the record that is executing the animation for removal.
You should be able to see this if you assigned a transition to the removeDisplaced looking like this.
removeDisplaced:Transition{
NumberAnimation{
property:"y" // Specifying "y" so the displaced records slowly proceed upwards
duration:5000 // 5s should be enough to see what's actually happening
easing.type: Easing.InOutQuad
}
}
EDIT:
The problem I mentioned in the original post wasn't the only problem. The problem is that the removeRow() function doesn't call beginRemoveRows() and endRemoveRows()! If you wrap those calls around the removeRow() call you will see animations happening. But you will still need to assign a transition to removeDisplaced to see everything happening.
EDIT by Question Author
The idea is to update the model after the animation from QML side:
remove: Transition {
SequentialAnimation {
NumberAnimation {
property: "opacity"
from: 1.0
to: 0
duration: 400
}
ScriptAction {
script: {
qsoModel.select()
}
}
}
}
I just call select when the animation is done from the QML side

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!

QML Behaviour without "on" specified

According to Animation documentation in section "Default Animation as Behaviors", they say that
There are several methods of assigning behavior animations to properties.
One of them is that we should be able to use Behaviour without on property but I don't succeed in having it working.
Here is my example code. I have a colored circle, and changing the color should trigger the ColorAnimation but it doesn't
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
width: 75; height: 75; radius: width
id: ball
color: "salmon"
Behavior {
ColorAnimation { target: ball; duration: 100 }
}
}
Component.onCompleted: timer.start()
Timer{
id: timer
onTriggered: {ball.color = "blue" }
interval: 1000
}
}
If I add on color, it works. I also tried to add property: "color" into ColorAnimation definition but nothing happens.
After browsing the docs a bit more I do not think there is another way to specify the property for Behaviors in QML, even though the animation docs suggest so.
Behavior is a Property Modifier Type more specific a property value write interceptor. Currently Behavior is the only one, see:
http://doc.qt.io/qt-5/qtqml-cppintegration-definetypes.html
Writing Behavior without the on <property> only defines a new Behavior component. To use it, it must be applied on a property. Code from Qt 5 documentation:
// FadeBehavior.qml
import QtQuick 2.15
Behavior {
...
}
Then use that Behavior:
Text {
FadeBehavior on text {}
}

Resources