Draft: Animations
A guide about QtQuick animators, animations and transitions.
Instant animations
Sometimes animations have to be disabled. This may be done for accessibility purposes, to conserve battery in Power Saving mode, or simply to honor user preferences. So, how to disable an animation in QtQuick?
In QtQuick it is rather inconvenient to use conditionals for components; for example, no one writes like this:
Behavior on x {
// Don't do this at home!
animation: Settings.disableAnimations ? null : myAnimation
}
NumberAnimation {
id: myAnimation
duration: 1000
}
Full code available here.
Animations are hard-wired to their Behavior the moment they are assigned. Trying to reset Behavior's animation back to null would throw an error "QML Behavior: Cannot change the animation assigned to a Behavior." (actually, it would fail only if/after the animation has been started at least once: you can reassign all you want before the Behavior is triggered — this is due to the animation property being "deferred", so its binding will only evaluate on first write to the intercepted property).
Being a property interceptor, Behavior as a whole can not be conditional either. In fact, whenever a property interceptor is present on a component, the QML runtime would create an extra meta-object subtype with overridden setters for the intercepted properties (this holds true both for regular and attached objects).
So instead it might be tempting to set animation duration to zero:
Behavior on x {
NumberAnimation {
// Don't do this at home either
duration: Settings.disableAnimations ? 0 : 1000
}
}
This may not work in some cases, because animations tend to be lazy (or buggy): they treat zero duration as being disabled, and won't do anything. So, properties may get stuck at starting (from) values, and event triggers never occur (so called "user control", like runningChanged(), stopped() signals etc.). For example, see https://invent.kde.org/plasma/libplasma/-/merge_requests/1077.
Disable Behavior completely:
Behavior {
enabled: !Settings.disableAnimations
}
Or, especially in case of a standalone animation or when disabling the whole animation group is not an option, use 1 millisecond duration instead:
NumberAnimation {
duration: Settings.disableAnimations ? 1 : 1000
}
The 1 ms trick is not unique to QML/QtQuick. Apparently, Web CSS also needs it:
@media (prefers-reduced-motion: reduce) {
*,
::before,
::after {
animation-duration: 0.001s !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001s !important;
}
}Note that rather than setting
animation: noneandtransition: none, we instead set theanimation-durationandtransition-durationproperties to a short enough duration that the motion itself is no longer noticable. This is to prevent issues in any case there is a dependency on the animation to run (such as when listening to theanimationendevent).
Efficiency
QtQuick animations are bound to the window refresh rate, so they won't trigger repaints more often than necessary (except PauseAnimation, more on that later). But also they are not integrated with window lifetime events or even with visibility of Items whose properties they animate, so they won't automatically stop running when a window is hidden or minimized or the Item becomes invisible (and thus potentially would keep triggering updates for some property bindings, even without actual repaints).
This is not an issue for short one-off animations like transitions in response to clicks, but it might become a performance concern for long-running or infinite animations like busy/loading indicators, image carousels etc., so in order to preserve CPU cycles use this trick:
import QtQuick
Item {
id: myItem
NumberAnimation on x {
loops: Animation.Infinite
running: myItem.visible && myItem.Window.visibility !== Window.Hidden
}
}
Item::visible does not depend on or inherit from Window visibility. An Item could be visible: true in a hidden (closed, minimized etc.) window. So, both properties should be tested.
TODO: SequentialAnimation with PauseAnimation with 1ms and how they magically change interval if a NumberAnimation is included.
User Control
The topic of Animation User Control never explicitly mentioned in the official documentation, only a few pieces of information are scattered around. Basically, not all animations can be directly controlled. Animation is a base abstract class with a lot of properties and methods, but what can be meaningfully set, invoked and handled depends on the context where the animation object is declared.
Only top-level standalone animations are user-controllable. User control is disabled for animations wrapped in a Transition, Behavior or animation group such as SequentialAnimation & ParallelAnimation.
Things affected by animation control include running, pausing, stopping the animation manually, and getting notified about its running state.
Signals like finished() won't be emitted from individual animations in a group!
SequentialAnimation with ScriptAction can be used as a workaround — although, be careful with a reversible Transition :
SequentialAnimation {
NumberAnimation {
// Don't do this, it won't work
onFinished: { /* ... */ }
}
ScriptAction {
// Do this instead
script: { /* ... */ }
}
}
Resources
Real-world examples include:
- BusyIndicator and indeterminate ProgressBar from PlasmaComponents3.
- Same BusyIndicator but from QQC2 Desktop Style.
- BusyIndicatorEngine in Breeze style (note that )
Official Qt documentation: