QML change path in PathView with NumberAnimation of its items - qt

I have a PathView with Rectangle as the delegate. I change the PathView.path to another path and I want to play animation of moving PathView items to their new positions. Something like this:
PathView {
id: pathView
delegate: Rectangle {
Behavior on x {
NumberAnimation {
duration: 5000
}
}
}
path: path1
}
But it doesn't work. Is it possible to do somehow?

Unfortunately, changing the "PathView.path" to another "Path" will destroy and recreate delegates just like changing a model.
A workaround can be done with "states". You create multiple blank PathLine and set its x and y values from states. You give the animation from "transitions"
This Sample code will initially have 3 data items in the model. In between the animation, it reloads the model with 5 data and still works in a continuous fashion without any glitch to the animation.
PathView {
id: pathView
anchors.fill: parent
anchors.margins: 100
model: 3
path: Path {
id: pathLines
PathLine { id: pl1 }
PathLine { id: pl2 }
PathLine { id: pl3 }
PathLine { id: pl4 }
PathLine { id: pl5 }
}
state: 'zigzag'
states: [
State {
name: "zigzag"
PropertyChanges { target: pathLines; startX: 0; startY: 100; }
PropertyChanges { target: pl1; x: 200; y: 300; }
PropertyChanges { target: pl2; x: 500; y: 100; }
PropertyChanges { target: pl3; x: 600; y: 300; }
PropertyChanges { target: pl4; x: 800; y: 100; }
PropertyChanges { target: pl5; x: 1000; y: 300; }
},
State {
name: "close"
PropertyChanges { target: pathLines; startX: pathView.width/2; startY: pathView.height/2; }
PropertyChanges { target: pl1; x: pathView.width/2; y: pathView.height/2; }
PropertyChanges { target: pl2; x: pathView.width/2; y: pathView.height/2; }
PropertyChanges { target: pl3; x: pathView.width/2; y: pathView.height/2; }
PropertyChanges { target: pl4; x: pathView.width/2; y: pathView.height/2; }
PropertyChanges { target: pl5; x: (pathView.width/2) + 1; y: pathView.height/2; } // Note: "+1" to fix disappearance bug
},
State {
name: "open"
PropertyChanges { target: pathLines; startX: pathView.width/2; startY: pathView.height/4; }
PropertyChanges { target: pl1; x: pathView.width/2; y: pathView.height/4; }
PropertyChanges { target: pl2; x: pathView.width/2; y: pathView.height/4; }
PropertyChanges { target: pl3; x: pathView.width/2; y: pathView.height/4; }
PropertyChanges { target: pl4; x: pathView.width/2; y: pathView.height/4; }
PropertyChanges { target: pl5; x: pathView.width/2 + 1; y: pathView.height/4; } // Note: "+1" to fix disappearance bug
},
State {
name: "triangle"
PropertyChanges { target: pathLines; startX: 200; startY: 500; }
PropertyChanges { target: pl1; x: 400; y: 700; }
PropertyChanges { target: pl2; x: 600; y: 500; }
PropertyChanges { target: pl3; x: 350; y: 500; }
PropertyChanges { target: pl4; x: 300; y: 500; }
PropertyChanges { target: pl5; x: 250; y: 500; }
}
]
transitions: [
Transition {
to: 'zigzag'
SmoothedAnimation { properties: "x,y,startX,startY"; easing.type: Easing.InOutQuad; duration: 2000; onFinished: pathView.state = 'triangle' }
},
Transition {
to: 'triangle'
SmoothedAnimation { properties: "x,y,startX,startY"; easing.type: Easing.InOutQuad; duration: 2000; }
},
Transition {
to: 'close'
SmoothedAnimation { properties: "x,y,startX,startY"; easing.type: Easing.InOutQuad; duration: 2000; }
onRunningChanged: {
if (!running) {
pathView.model = 5
pathView.state = 'open'
}
}
},
Transition {
from: "close"
to: 'open'
SmoothedAnimation { properties: "x,y,startX,startY"; easing.type: Easing.InOutQuad; duration: 2000; }
onRunningChanged: !running ? pathView.state = 'triangle' : ''
}
]
delegate: Rectangle {
width: 20
height: 20
radius: 20
color: 'green'
}
}
Controls.Button {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
text: 'Start Animation'
onClicked: pathView.state = 'close'
}
The state names like "zigzag" and "triangle" does not resemble its real shape, just some random shape for test purposes.

Related

Sequential movement of an object by coordinates

I need to move an object by the clock and counterclockwise sequentially. But the for loop works differently, it only moves in the latter direction. When you click on the button, the object must first turn clockwise, and then counterclockwise. Maybe there is some kind of delay when performing the animation? How can I do it?
import QtQuick 2.15
import QtQuick.Window 2.14
import QtQuick3D 1.15
import QtQuick.Controls 2.14
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
visibility: "Maximized"
property int scl: 5
property int angle: 360
Node{
id: standAloneScene
DirectionalLight {
ambientColor: Qt.rgba(1.0, 1.0, 1.0, 1.0)
}
Node {
id: sphere
Model {
id: model_sphere
source: "#Cube"
x: 200
y: 100
z: 0
materials: [
DefaultMaterial {
diffuseColor: Qt.rgba(0.053, 0.130, 0.219, 0.75)
}
]
}
}
ParallelAnimation{
id: start
running: false
NumberAnimation {
target: sphere
property: "eulerRotation.y"
to: angle
duration: 2000
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: model_sphere
property: "eulerRotation.y"
to: 2*angle
duration: 2000
easing.type: Easing.InOutQuad
}
}
OrthographicCamera {
id: cameraOrthographicFront
eulerRotation.y: 45
eulerRotation.x: -45
x: 600
y: 800
z: 600
}
}
Rectangle {
id: view
anchors.top: parent.top
anchors.left: parent.left
width: parent.width
height: parent.height
color: "#848895"
border.color: "black"
View3D {
id: topLeftView
anchors.fill: parent
importScene: standAloneScene
camera: cameraOrthographicFront
}
Button {
id: posmoveZ
width: view.width/8
height: view.height/16
anchors.top: view.top
anchors.right: view.right
text: "start"
font.pixelSize: height
onClicked: {
for(var i=0; i<6; i++){
if(i % 2 == 0){
angle = 360
}
else{
angle = -360
}
start.restart();
}
}
}
}
}
The simplest answer to your problem is put your animations inside a SequentialAnimation, i.e.
SequentialAnimation {
loops: 3
ParallelAnimation {
id: clockwise
NumberAnimation { /* ... */ }
NumberAnimation { /* ... */ }
}
ParallelAnimation {
id: counterClockwise
NumberAnimation { /* ... */ }
NumberAnimation { /* ... */ }
}
}
Also, you should let QML use the default width and height of a Button. Here's the complete solution:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
import QtQuick3D 1.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
visibility: "Maximized"
property int scl: 5
property int angle: 360
Node{
id: standAloneScene
DirectionalLight {
ambientColor: Qt.rgba(1.0, 1.0, 1.0, 1.0)
}
Node {
id: sphere
Model {
id: model_sphere
source: "#Cube"
x: 200
y: 100
z: 0
materials: [
DefaultMaterial {
diffuseColor: Qt.rgba(0.053, 0.130, 0.219, 0.75)
}
]
}
}
SequentialAnimation {
id: sequentialAnimation
loops: 3
ParallelAnimation{
id: clockwise
running: false
property int count: 3
NumberAnimation {
target: sphere
property: "eulerRotation.y"
from: 360
to: 0
duration: 2000
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: model_sphere
property: "eulerRotation.y"
from: 720
to: 0
duration: 2000
easing.type: Easing.InOutQuad
}
}
ParallelAnimation{
id: counterClockwise
running: false
NumberAnimation {
target: sphere
property: "eulerRotation.y"
from: 0
to: 360
duration: 2000
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: model_sphere
property: "eulerRotation.y"
from: 0
to: 720
duration: 2000
easing.type: Easing.InOutQuad
}
}
}
OrthographicCamera {
id: cameraOrthographicFront
eulerRotation.y: 45
eulerRotation.x: -45
x: 600
y: 800
z: 600
}
}
Rectangle {
id: view
anchors.top: parent.top
anchors.left: parent.left
width: parent.width
height: parent.height
color: "#848895"
border.color: "black"
View3D {
id: topLeftView
anchors.fill: parent
importScene: standAloneScene
camera: cameraOrthographicFront
}
Button {
id: posmoveZ
anchors.top: view.top
anchors.right: view.right
anchors.margins: 10
text: "start"
font.pixelSize: height
enabled: !clockwise.running && !counterClockwise.running
onClicked: {
sequentialAnimation.restart();
}
}
}
}

Getting into PathView delegate

I am trying to animate a property within my PathView delegate once it comes into view.
Component {
id: pathViewDelegate
Item {
Text {
id: tempText
color: "red"
text: "test"
}
function intoView() {
myAnimation.start()
}
SequentialAnimation {
id: myAnimation
PropertyAnimation {duration: 1000; target: tempText; properties: "color"; to: "green"}
PropertyAnimation {duration: 1000; target: tempText; properties: "color"; to: "yellow"}
}
}
}
The only way I know how to display my PathView correctly (like a linear carousel) is to have the currentItem clipped off screen. In other words, the displayed item is always currentIndex+1 in my PathView.
PathView {
id: mainCarousel
width: parent.width
height: parent.height
interactive: false
highlightMoveDuration: 300
clip: true
model: pathViewModel
delegate: pathViewDelegate
path: Path {
startX: -mainCarousel.width*0.5
startY: mainCarousel.height/2
PathLine { x: mainCarousel.width*0.5; y: mainCarousel.height/2 }
PathLine { x: mainCarousel.width*(mainCarousel.count-0.5); y: mainCarousel.height/2 }
}
}
}
So, when I try to implement a function with "mainCarousel.currentItem.intoView()", it animates the item clipped off screen. I know there isn't a way to do "currentItem+1", but is there some other workaround?
EDIT: Full example code
ListModel {
id: pathViewModel
ListElement {header: "text 1"}
ListElement {header: "text 2"}
ListElement {header: "text 3"}
ListElement {header: "text 4"}
}
Component {
id: pathViewDelegate
Item {
width: mainCarousel.width
height: mainCarousel.height
Text {
color: "red"
text: header
}
function runAnimation() {
myAnimation.start()
}
SequentialAnimation {
id: myAnimation
PropertyAnimation {property: "color"; to: "green"; duration: 1000}
PropertyAnimation {property: "color"; to: "yellow"; duration: 1000}
PropertyAnimation {property: "color"; to: "purple"; duration: 1000}
}
}
}
PathView {
id: mainCarousel
width: 500
height: 500
interactive: false
highlightMoveDuration: 300
clip: true
model: pathViewModel
delegate: pathViewDelegate
path: Path {
startX: -mainCarousel.width*0.5
startY: mainCarousel.height/2
PathLine { x: mainCarousel.width*0.5; y: mainCarousel.height/2 }
PathLine { x: mainCarousel.width*(mainCarousel.count-0.5); y: mainCarousel.height/2 }
}
}
MouseArea {
id: telltaleMouseArea
anchors.fill: mainCarousel
onClicked: {
mainCarousel.incrementCurrentIndex()
mainCarousel.currentItem.runAnimation()
}
}
The initial item displayed is "text 2". The currentItem is "text 1", initially. In the code, when the MouseArea is clicked, "text 2" animates, but it is off screen. I am just trying to animate the item that is in view, when it becomes into view.
Change the animation to the following:
Text {
color: "red"
text: header
SequentialAnimation on color{
id: myAnimation
ColorAnimation {
to: "green"
duration: 1000
}
ColorAnimation {
to: "yellow"
duration: 1000
}
ColorAnimation {
to: "purple"
duration: 1000
}
}
}
and make sure your currentItem is in view with : startX: mainCarousel.width*0.5

Qml Coveflow angle animation

I want to implement a coverflow in QML but having a problem with the angle animation.
Scrolling to the left (from number 1→18→17→...) works great, but right scrolling is not behaving as expected. I'm not sure I got the angles right but they seem to animate from -40°→0° which looks odd (correct would be from 40°→0).
Is there a way to correct this behavior?
Here is a working example of my code:
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
visible: true
width: 1280
height: 800
MouseArea {
anchors.fill: parent
onWheel: {
if (wheel.angleDelta.y < 0)
{
view.incrementCurrentIndex()
}
else if (wheel.angleDelta.y > 0)
{
view.decrementCurrentIndex()
}
}
}
PathView {
id: view
property int itemAngle: 40.0
property int itemSize: width/3.5
anchors.fill: parent
pathItemCount: 10
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
interactive: true
model: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]
delegate: viewDelegate
path: Path {
startX: 0
startY: height / 2
PathPercent { value: 0.0 }
PathAttribute { name: "z"; value: 0 }
PathAttribute { name: "angle"; value: view.itemAngle }
PathAttribute { name: "origin"; value: 0 }
PathLine {x: view.width*0.4; y: view.height / 2}
PathPercent { value: 0.45 }
PathAttribute { name: "angle"; value: view.itemAngle }
PathAttribute { name: "origin"; value: 0 }
PathAttribute { name: "z"; value: 10 }
PathLine { relativeX: 0; relativeY: 0 }
PathAttribute { name: "angle"; value: 0.0 }
PathAttribute { name: "origin"; value: 0 }
PathAttribute { name: "z"; value: 10 }
PathLine {x: view.width*0.6; y: view.height / 2}
PathPercent { value: 0.55 }
PathAttribute { name: "angle"; value: 0.0 }
PathAttribute { name: "origin"; value: 0 }
PathAttribute { name: "z"; value: 10 }
PathLine { relativeX: 0; relativeY: 0 }
PathAttribute { name: "angle"; value: -view.itemAngle }
PathAttribute { name: "origin"; value: view.itemSize }
PathAttribute { name: "z"; value: 10 }
PathLine {x: view.width; y: view.height / 2}
PathPercent { value: 1 }
PathAttribute { name: "angle"; value: -view.itemAngle }
PathAttribute { name: "origin"; value: view.itemSize }
PathAttribute { name: "z"; value: 0 }
}
}
Component {
id: viewDelegate
Rectangle {
id: flipItem
width: view.itemSize
height: view.height
color: "white"
z: PathView.z
property var rotationAngle: PathView.angle
property var rotationOrigin: PathView.origin
transform:
Rotation {
id: rot
axis { x: 0; y: 1; z: 0 }
angle: rotationAngle
origin.x: rotationOrigin
origin.y: width
Behavior on angle { PropertyAnimation{} }
}
Rectangle {
border.color: "black"
border.width: 2
color: (index%2 === 0) ? "yellow" : "royalblue"
anchors.top: flipItem.top
anchors.topMargin: 100
anchors.left: flipItem.left
anchors.right: flipItem.right
width: flipItem.width
height: flipItem.height*0.55
smooth: true
antialiasing: true
Text {
text: model.modelData
color: "gray"
font.pixelSize: 30
font.bold: true
anchors.centerIn: parent
}
}
}
}
}

Changing state after a transition's animations have finished

I'd like to change state after a transition's animations have completed. I have the following code that achieves this, although it seems kind of hackish:
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
Rectangle {
id: root
width: 400
height: 400
Rectangle {
id: rect
color: "blue"
width: 50
height: 50
anchors.centerIn: parent
MouseArea {
anchors.fill: parent
onClicked: rect.state = "animating"
}
states: [
State {
name: "animating"
PropertyChanges {
target: rect
rotation: 360
}
},
State {
name: "shrinking"
PropertyChanges {
target: rect
scale: 0
}
}
]
transitions: [
Transition {
from: ""
to: "animating"
SequentialAnimation {
RotationAnimation {
duration: 500
}
ScriptAction {
script: rect.state = "shrinking"
}
}
},
Transition {
from: "animating"
to: "shrinking"
NumberAnimation {
property: "scale"
duration: 500
}
}
]
}
}
Is there a nicer way to do this without using ScriptAction? Note that I need the second state, and I don't want to just consolidate the scale animation into the SequentialAnimation of the animating transition.
The proper way is to change the state in the runningChanged handler of the transition, when running pass to false than the animation finished.
to do that you have two solutions:
Sol 1. use connections ( you will get a warning about a none notifiable property, ignore it)
Connections{
target:rect.transitions[0]
onRunningChanged:{
if( rect.transitions[0].running === false)
{
rect.state = "shrinking"
}
}
}
the code will be :
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
Rectangle {
id: root
width: 400
height: 400
Rectangle {
id: rect
color: "blue"
width: 50
height: 50
anchors.centerIn: parent
MouseArea {
anchors.fill: parent
onClicked: rect.state = "animating"
}
states: [
State {
name: "animating"
PropertyChanges {
target: rect
rotation: 360
}
},
State {
name: "shrinking"
PropertyChanges {
target: rect
scale: 0
}
}
]
Connections{
target:rect.transitions[0]
onRunningChanged:{
if( rect.transitions[0].running === false)
{
rect.state = "shrinking"
}
}
}
transitions: [
Transition {
from: ""
to: "animating"
RotationAnimation {
duration: 500
}
},
Transition {
from: "animating"
to: "shrinking"
NumberAnimation {
property: "scale"
duration: 500
}
}
]
}
}
solution 2:
change state in runningchanged handler in the transition directly:
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
Rectangle {
id: root
width: 400
height: 400
Rectangle {
id: rect
color: "blue"
width: 50
height: 50
anchors.centerIn: parent
MouseArea {
anchors.fill: parent
onClicked: rect.state = "animating"
}
states: [
State {
name: "animating"
PropertyChanges {
target: rect
rotation: 360
}
},
State {
name: "shrinking"
PropertyChanges {
target: rect
scale: 0
}
}
]
transitions: [
Transition {
from: ""
to: "animating"
RotationAnimation {
duration: 500
}
onRunningChanged:{
if( running === false)
{
rect.state = "shrinking"
}
}
},
Transition {
from: "animating"
to: "shrinking"
NumberAnimation {
property: "scale"
duration: 500
}
}
]
}
}
I prefer the first solution (Connections) cause it's more generic
A slightly different approach is to set the shrinking state in the animating state, and use a PropertyAction to force the state change to happen at the end of the transition:
State {
name: "animating"
PropertyChanges {
target: rect
rotation: 360
}
PropertyChanges {
target: rect
state: "shrinking"
}
and
Transition {
SequentialAnimation {
RotationAnimation {
duration: 500
}
PropertyAction {
target: rect
property: "state"
}
}
}
Note that I agree with jturcotte on his assessment of using these states here.

reducing the distance between Qml pathview members

I have been trying to implement a scroll kind of thing using the Pathview element of QML.Below image shows the progress
I want the reduce the distance between the Calendar and the Messaging element. Please help.
Below is my code of the scroll.
import QtQuick 2.0
Rectangle {
// property real rotationOrigin: PathView.origin
id: scroll
width: 800; height: 480
color: "white"
// property real rotationAngle
ListModel {
id: appModel
ListElement { name: "Music"; icon: "pics/AudioPlayer_48.png" }
ListElement { name: "Movies"; icon: "pics/VideoPlayer_48.png" }
ListElement { name: "Camera"; icon: "pics/Camera_48.png" }
ListElement { name: "Calendar"; icon: "pics/DateBook_48.png" }
ListElement { name: "Messaging"; icon: "pics/EMail_48.png" }
/*ListElement { name: "Todo List"; icon: "pics/TodoList_48.png" }
ListElement { name: "Contacts"; icon: "pics/AddressBook_48.png" }*/
}
Component {
id: appDelegate
Item {
id: item
width: 240; height: 80
scale: PathView.iconScale
property real rotationAngle: PathView.angle
transform: Rotation {
id: rotation
origin.x: item.width/2
origin.y: item.height/2
axis.x: 1; axis.y:0 ; axis.z: 0
angle: rotationAngle
}
Image {
id: myIcon
y: 20; anchors.verticalCenter: parent.verticalCenter
source: icon
smooth: true
}
Text {
anchors {
leftMargin: 10
left: myIcon.right; verticalCenter: parent.verticalCenter }
text: name
font.pointSize: 25
smooth: true
}
MouseArea {
anchors.fill: parent
onClicked: {
view.currentIndex = index
console.log("onClicked" + index);
}
}
}
}
Component {
id: appHighlight
Rectangle { width: 240; height: 80; color: "lightsteelblue" }
}
PathView {
Keys.onRightPressed: if (!moving) { incrementCurrentIndex(); console.log(moving) }
Keys.onLeftPressed: if (!moving) decrementCurrentIndex()
id: view
anchors.fill: parent
highlight: appHighlight
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
focus: true
model: appModel
delegate: appDelegate
path: Path {
startX: scroll.width/2
startY: 0
PathAttribute { name: "z"; value: 0 }
PathAttribute { name: "angle"; value: 75 }
PathAttribute { name: "iconScale"; value: 0.85 }
PathLine { x: scroll.width / 2; y: scroll.height / 4; }
PathAttribute { name: "z"; value: 50 }
PathAttribute { name: "angle"; value: 70 }
PathAttribute { name: "iconScale"; value: 0.85 }
PathLine { x: scroll.width /2; y: scroll.height/2; }
PathAttribute { name: "z"; value: 100 }
PathAttribute { name: "angle"; value: 0 }
PathAttribute { name: "iconScale"; value: 1.0 }
PathLine { x: scroll.width /2; y: 3*(scroll.height/4); }
PathAttribute { name: "z"; value: 50 }
PathAttribute { name: "angle"; value: -70 }
PathAttribute { name: "iconScale"; value: 0.85 }
PathLine { x: scroll.width /2; y: scroll.height; }
PathAttribute { name: "z"; value: 0 }
PathAttribute { name: "angle"; value: -75 }
PathAttribute { name: "iconScale"; value: 0.85 }
}
}
}
Did you try with PathPercent? Take a look here:
http://qmlbook.github.io/en/ch06/index.html#the-pathview

Resources