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
.
Provide a customized scrolling like in the examples requires to take in account the position of the Item
s 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 Item
s animation must be "finished" to make all the visible item
s 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.