ListView scrolling animation - qt

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.

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 Animation: How to stop infinite animation in exact position?

I'm trying to stop infinite animation from function where position is known.
I need animation to animate until the x = pos and then to stop.
NumberAnimation on x {
id : animationObject
loops: Animation.Infinite
from: 0
to: 500
running: true
duration: 3000
}
I have tried to stop it in this way, but it doesn't help.
function stop(pos) {
animationObject.loops = 1;
animationObject.to = pos;
}
You can set an expression as value of the property running to be true if the position is not reached and false otherwise.
The x property is also associated to the signal onXChanged that will be triggered each time the item moved. You can also use it if you don't want/can't change the animation definition:
Rectangle {
anchors.fill: parent
Rectangle {
id: rect
width: 100
height: 100
color: "red"
property int bound: 50
NumberAnimation on x {
id : animationObject
loops: Animation.Infinite
from: 0
to: 100
running: Math.abs(rect.x - bound) > 1
duration: 3000
}
onXChanged: console.log(rect.x, Math.abs(rect.x - bound) > 1)
}
}
In the code above, the animation will be stopped when the rectangle reachs the X coordinate defined by the property bound.
As the value will be a floated number, we can't use the equality and have to use a margin error (1 in my code).

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!

How can I get the drop effect of a DragArea to animate towards the DropArea that received it?

Hope this makes some sense as a question. In my app, I have a DragArea defined which I use to start dragging things over top of various Rectangles that each contain a DropArea. Everything is working fine in my code except for a cosmetic effect that I would like to change.
In QML, when you start dragging from a DragArea and eventually drop, the animation effect is such that the thing you're dragging animates (while fading out) back to the spot from which you started dragging. This happens even when you drop over a DropArea that successfully captures the drop.
What I would like to do is have the drop effect animate towards the DropArea that received the drop - so that it appears I am dragging-and-dropping things into the Rectangle. Is there any way to do this?
I'm guessing that this in some way involves the .source and .target properties of these areas, but no luck so far in having any effect on where the drop animation goes.
By default, QML will give you no cosmetic behavior for drag and drop whatsoever. The drag target will begin at the drag start location, and will end wherever it is dropped, regardless of whether the drag is accepted or not.
Thus I assume the behavior you describe is implemented in your user code, which you have not disclosed. Regardless, what you want to do is quite easy, it involves tracking the position the drag originates at and it ends at, so you can use the two coordinates to animate the position.
In the following example the red rectangle can be dragged, and if dropped outside of a drop area it will animate from its current to its initial position, whereas if dropped in the yellow rectangle, it will animate from its initial to its drop position.
Window {
width: 600
height: 600
visible: true
Rectangle {
width: 200
height: 200
color: "yellow"
DropArea {
anchors.fill: parent
onEntered: drag.source.accepted = true
onExited: drag.source.accepted = false
}
}
Rectangle {
id: rect
width: 50
height: 50
color: "red"
x: parent.width * 0.5
y: parent.height * 0.5
Drag.active: mouseArea.drag.active
property point begin
property point end
property bool accepted : false
MouseArea {
id: mouseArea
anchors.fill: parent
drag.target: parent
onPressed: rect.begin = Qt.point(rect.x, rect.y)
onReleased: {
rect.end = Qt.point(rect.x, rect.y)
aX.from = rect.accepted ? rect.begin.x : rect.end.x
aX.to = rect.accepted ? rect.end.x : rect.begin.x
aY.from = rect.accepted ? rect.begin.y : rect.end.y
aY.to = rect.accepted ? rect.end.y : rect.begin.y
anim.start()
}
ParallelAnimation {
id: anim
NumberAnimation { id: aX; target: rect; property: "x"; duration: 200 }
NumberAnimation { id: aY; target: rect; property: "y"; duration: 200 }
}
}
}
}

Pathview Issue in QML

I'm designing the Spinner control (or You can Scollable list of items). Its working fine as far as the functionality is concerned
The main issue is the i want to create a circular motion feel in scrolling the items. So to give that effect in the scrolling list we decided to have preceding & trailing item size comparatively small than current item
I'm really struggling to get the different size of the items. Can any one suggest me how to proceed with the same.
Below is my code snippet
ContentModel.qml
import QtQuick 1.1
Rectangle {
property alias model: view.model
property alias delegate: view.delegate
property real itemHeight: height/5
clip: true
PathView {
id: view
anchors.fill: parent
//number of items visible on the path at any one time.
pathItemCount: height/itemHeight
// Ensuring the selected componenet to be at the center
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
// select maximum distance from the path that initiate mouse dragging
dragMargin: view.width
//Declare the path of list
path: Path {
startX: view.width/2; startY: -itemHeight/2
PathLine { x: view.width/2; y: view.pathItemCount*itemHeight + itemHeight/.8}
}
}
}
The main.qml snippet
main.qml
.......
ContentModel{
id: ContentModel_spinner
width: ContentModel_scroll.width; height: ContentModel_scroll.height
focus: true
model: 20
delegate: Text { font.pixelSize: index === ContentModel_spinner.currentIndex ? sec_spinner.height/4 : ContentModel_spinner.height/4.5; text: formatindex(index); height: ContentModel_scroll.height }
}
Check the tutorial here. They have given examples with different shapes of path views.

Resources