How to add transition to ListView : positionViewAtEnd()/positionViewAtBeginning() - qt

I need some times to set my View of my ListView on lastItem or firstItem (positionViewAtEnd()/positionViewAtBeginning()).
However, it is a brutal jump and I would like to add a Transition.
I tried to add this :
Behavior on contentY
{
id: contentYAnimation
PropertyAnimation {duration: 300 }
}
But it doesn't change anything. Any suggestion?

Related

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

Change ListView's current index background color and not override other "states"

My Qt Quick Control 2 app is using Material Dark Theme. I would like to change background color of selected item in ListView. I know that I can do this in item delegate:
SwipeDelegate {
id: delegate
checkable: true
spacing: 0
width: parent.width
background: Rectangle {
color: index===currentIndex ? "red" : "transparent"
}
But with change above I'm losing defaults padding etc and also Material's radial animation on press and hold state is missing too. Is it possible to just change color only of selected item and keep original behavior for other states of item? Seems that I must reimplement missing things by my self
Maybe you should consider to implement highlight. The following worked for me:
ListView {
// ... Your stuff ...
highlight: Rectangle { color: "lightsteelblue"; }
focus: true
}

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!

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.

Get index of the delegate currently displayed - QML ListView

I created a ListView, which displays a couple of pages of content defined by the user (plain text). The page displayed is a delegate. Only one page is visible at a time. I decided to use it to get snapping to one item, in the same way the iOS' launcher works. The user simply flicks between the pages. (this is to be used on touch screens)
I need to have the index of the currently displayed page for some operation. currentIndex of the ListView always stays == 0. How can I get it?
For those who prefer code:
ListView
{
onCurrentIndexChanged: console.log(currentIndex) // this gets called only once - at startup
delegate: Column
{
// The page displayed, only one page at a time
}
}
Thanks
There are many ways to get the index of current item that is displayed in the screen. If you can get the x-y coordinate of current page, you can use indexAt method in ListView.
And in each delegate, you can find the index using index role within the scope of the delegate. The index is like a role you declared in your model, and is automatically assigned by ListView. For example,
ListView
{
delegate: Column
{
property int indexOfThisDelegate: index
//...
}
}
The index role is introduced here:
A special index role containing the index of the item in the model is also available to the delegate. Note this index is set to -1 if the item is removed from the model...
Another way is to explicitly assign value to the currentItem property in ListView, so the view can scroll by itself. Here is an simple example in Qt documentation, which is similar to your application.
I know this is quite old but I had the same problem and spend some time trying to find a way to get currentIndex that would work for me. In my case sometimes I need to change the width of my ListView so I have to recalculte currentIndex manualy every time I resize it.
But I found a highlightRangeMode property. When it's set to ListView.StrictlyEnforceRange then currentIndex is always updated automaticly and contains correct index of the currently visible item.
ListView {
highlightRangeMode: ListView.StrictlyEnforceRange
// ...
}
You can do like that:
QModelIndex index =this->indexAt(event->pos());
this ->setCurrentIndex(index);
You can use attached properties of ListView class (ListView). They are attached to each instance of the delegate.
See ListView.isCurrentItem or ListView.view example:
ListView {
width: 180; height: 200
Component {
id: contactsDelegate
Rectangle {
id: wrapper
width: 180
height: contactInfo.height
color: ListView.isCurrentItem ? "black" : "red"
Text {
id: contactInfo
text: name + ": " + number
color: wrapper.ListView.isCurrentItem ? "red" : "black"
}
}
}
model: ContactModel {}
delegate: contactsDelegate
focus: true
}

Resources