QML transitions with multiple (from, to) matches - qt

I have a simple QML script with a four states which you go through a cycle, say the states are one, two, three, four.
The transition from one to two and from three to four is the same.
Is there a simple way to define this without duplicating the code for the transition?
This does not work:
transitions: Transition {
from: "one"; to: "two";
from: "three"; to: "four";
ParallelAnimation {
NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.InOutQuad }
ColorAnimation { duration: 500 }
}
}
From the documentation, it seems there is no way of specifying this but
transitions: [ Transition {
from: "one"; to: "two";
ParallelAnimation {
NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.InOutQuad }
ColorAnimation { duration: 500 }
}
}, Transition {
from: "three"; to: "four";
ParallelAnimation {
NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.InOutQuad }
ColorAnimation { duration: 500 }
}
}]

I had a similar issue on a very simple app, it is not a pretty way to do it but the only way I found is like this:
transitions:
Transition {
from: "two"; to: "three";
//whatever animation you have for this
},
//and obviously any other transitions you may have
//any transition you don't specify will do this:
Transition {
from: "*"; to: "*";
ParallelAnimation {
NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.InOutQuad }
ColorAnimation { duration: 500 }
}
}

You can reuse an animation by giving it an id.
import QtQuick 2.0
import QtQuick.Controls 1.0
Rectangle {
id: root
width: 400; height: width
Column {
spacing: 20
Repeater {
model: ["one", "two", "three", "four"]
Button {
text: modelData
onClicked: rect.state = modelData
}
}
}
Rectangle {
id: rect
x: 200; y: 0
width: 60; height: width
color: "red"
state: "one"
states: [
State { name: "one" },
State { name: "two" },
State { name: "three" },
State { name: "four" }
]
NumberAnimation { id: na1; target: rect; property: "y"; to: 200 }
NumberAnimation { id: na2; target: rect; property: "y"; to: 0 }
transitions: [
Transition { from: "one"; to: "two"; animations: na1 },
Transition { from: "two"; to: "three"; animations: na2 },
Transition { from: "three"; to: "four"; animations: na1 },
Transition { from: "four"; to: "one"; animations: na2 }
]
}
}
I'm wondering if you know the concept of default property. In your code, ParallelAnimation is assigned to the default property animations of the Transition, and you can explicitly assign an animation(by it's id) to this default property.

Related

qt QML collapsible nested ListView with PropertyAnimation

I am new to QT QML, and I am planning to make a ListView with collapse with smooth animation. I saw this https://gist.github.com/elpuri/3753756 code. I tried adding PropertyAnimation during collapse and expand to the code. But failed, how should i make it work?
I was planning to add state and translation, but it is not working for two different components,
nestedModel.setProperty(index, "collapsed", !collapsed)
nestedModel.state = (collapsed.state === "COLLAPSED") ? "COLLAPSED" : "EXPANDED";
then for states and transitions,
delegate: Rectangle {
id: rect_change
color: "blue"
//height: 200
width: 300
border.color: "black"
border.width: 2
state: "COLLAPSED"
states: [
State {
name: "COLLAPSED"
PropertyChanges { target: rect_change; height: 0; }
},
State {
name: "EXPANDED"
PropertyChanges { target: rect_change; height: 200; }
}
]
transitions: [
Transition {
from: "EXPANDED"
to: "COLLAPSED"
PropertyAnimation { property: "height"; duration: 400; }
},
Transition {
from: "COLLAPSED"
to: "EXPANDED"
PropertyAnimation { property: "height"; duration: 400; }
}
]
}
For a simpler implementation, get rid of your states and transitions and just use a Behavior on height. You can change the Loader in the example that you linked to so it looks like this:
Loader {
id: subItemLoader
sourceComponent: subItemColumnDelegate
onStatusChanged: if (status == Loader.Ready) item.model = subItems
clip: true
height: collapsed ? 0 : subItems.count * 40
Behavior on height {
NumberAnimation {
duration: 400
}
}
}

Creating QML States dynamically

I want to make an icon component that changes it picture and color depending on it state:
StateIcon.qml:
import QtQuick 2.0
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
Item {
Layout.preferredWidth: appLayout.icon.prefWidth
Layout.preferredHeight: appLayout.icon.prefHeight
property variant stateImage: stateImageInstance
Image {
id: stateImageInstance
width: appLayout.icon.prefWidth
height: appLayout.icon.prefWidth
sourceSize.width: width
sourceSize.height: height
}
property variant imageOverlay: imageOverlayInstance
ColorOverlay {
id: imageOverlayInstance
anchors.fill: stateImage
source: stateImage
}
transitions: Transition {
SequentialAnimation {
NumberAnimation {
target: stateImage; property: "scale"
to: 0; duration: 100
}
PropertyAction {
target: stateImage; property: "source"
}
PropertyAction {
target: imageOverlay; property: "color"
}
NumberAnimation {
target: stateImage; property: "scale"
to: 1; duration: 100
}
}
}
}
The problem is that I have to define states in the component instance:
main.qml:
StateIcon {
id: stateIcon
states: [
State {
name: "state1";
PropertyChanges {
target: stateIcon.stateImage
source: "qrc:/resources/icons/icon1.svg"
}
PropertyChanges {
target: stateIcon.imageOverlay; color: "gray"
}
},
State {
name: "state2";
PropertyChanges {
target: stateIcon.stateImage
source: "qrc:/resources/icons/icon2.svg"
}
PropertyChanges {
target: stateIcon.imageOverlay; color: "green"
}
}
...
]
state: "state1"
}
And now I want to know is it possible to define only state names, color and source in some array:
main.qml:
StateIcon {
id: stateIcon
rawStates: [
{
name: "state1",
iconSource: "qrc:/resources/icons/state1.svg",
color: "green"
},
{
name: "state2",
iconSource: "qrc:/resources/icons/state2.svg",
color: "green"
},
...
]
state: "state1"
}
And in the StateIcon.qml define states property dynamically using rawStates property?
Maybe something like that:
StateIcon.qml:
import QtQuick 2.0
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
Item {
property variant rawStates
Layout.preferredWidth: appLayout.icon.prefWidth
Layout.preferredHeight: appLayout.icon.prefHeight
Image {
id: stateImage
width: appLayout.icon.prefWidth
height: appLayout.icon.prefWidth
sourceSize.width: width
sourceSize.height: height
}
ColorOverlay {
id: imageOverlay
anchors.fill: stateImage
source: stateImage
}
states: [
for(var i=0; i<rawStates.length; ++i) {
?
}
]
transitions: Transition {
SequentialAnimation {
NumberAnimation {
target: stateImage; property: "scale"
to: 0; duration: 100
}
PropertyAction {
target: stateImage; property: "source"
}
PropertyAction {
target: imageOverlay; property: "color"
}
NumberAnimation {
target: stateImage; property: "scale"
to: 1; duration: 100
}
}
}
}
Instead of using States I would use a plain javascript associative arrays.
You can't use transitions but you could use Behavior instead. Not anything can be done with behavior but it's enough most of the time.
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQml 2.2
ApplicationWindow {
id: mainWindow
visible: true
minimumWidth: 500
minimumHeight: 500
Row {
Rectangle {
id: rect
width: 100
height: 100
property var stateDescriptors: {
'state0': {color: 'green'},
'state1': {color: 'red'},
'state2': {color: 'blue'},
'state3': {color: 'purple'},
'state4': {color: 'orange'}
}
property string iconState: "state0"
Text {
anchors.fill: parent
text: parent.iconState
}
color: stateDescriptors[iconState].color
Behavior on iconState {
SequentialAnimation {
NumberAnimation {
target: rect; property: "scale"
to: 0; duration: 100
}
PropertyAction { } //actually change the iconState here, since the color is binded to it, it will also change between the 2 scale animations
NumberAnimation {
target: rect; property: "scale"
to: 1; duration: 100
}
}
}
}
Button {
text: 'change state'
property int count: 0
onClicked: {
count = (count + 1) % Object.keys(rect.stateDescriptors).length
rect.iconState = 'state' + count
}
}
}
}
Maybe this helps you:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQml 2.2
ApplicationWindow {
id: mainWindow
visible: true
minimumWidth: 500
minimumHeight: 500
Row {
Rectangle {
id: rect
width: 100
height: 100
Text {
anchors.fill: parent
text: parent.state
}
property var myStates: []
states: myStates
onStateChanged: console.log(Object.keys(rect.states))
}
Button {
text: 'add state'
onClicked: {
rect.myStates.push(statePrototype.createObject(rect,
{
name: 'state' + count,
color: Qt.rgba(Math.random(count),
Math.random(count),
Math.random(count),
Math.random(count))
}))
rect.myStatesChanged()
count++
}
}
Button {
text: 'change state'
onClicked: {
rect.state = 'state' + (count1 % count)
count1++
}
}
}
property int count: 0
property int count1: 0
Component {
id: statePrototype
State {
id: st
property color color
PropertyChanges {
target: rect
color: st.color
}
}
}
}
It seems to be not so easily possible to add States to states directly. With the extra mile going over a custom property var myStates it suddenly works. Don't forget to tell everyone, that myStatesChanged() after adding something!
EDIT Once more, with the list of JS Objects, and a Instantiator. The method is the same
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQml 2.2
ApplicationWindow {
id: mainWindow
visible: true
minimumWidth: 500
minimumHeight: 500
Row {
Rectangle {
id: rect
width: 100
height: 100
Text {
anchors.fill: parent
text: parent.state
}
property var myStates: []
states: myStates
onStateChanged: console.log(Object.keys(rect.states))
}
Button {
text: 'change state'
property int count: 0
onClicked: {
rect.state = 'state' + count % rect.myStates.length
count ++
}
}
Button {
text: 'add states'
onClicked: {
stateDescriptors.push( { name: 'state' + stateDescriptors.length, color: Qt.rgba(Math.random(1),
Math.random(2),
Math.random(3),
Math.random(4)) })
stateDescriptorsChanged()
}
}
}
Instantiator {
model: stateDescriptors
delegate: State {
name: modelData.name
PropertyChanges {
target: rect
color: modelData.color
}
Component.onCompleted: {
console.log('created', modelData.name)
rect.myStates.push(this)
rect.myStatesChanged()
}
Component.onDestruction: {
console.log('destroy', modelData.name)
rect.myStates.pop()
}
}
}
property var stateDescriptors: [
{
name: 'state0',
color: 'green'
},
{
name: 'state1',
color: 'red'
},
{
name: 'state2',
color: 'blue'
},
{
name: 'state3',
color: 'purple'
},
{
name: 'state4',
color: 'orange'
}
]
}

How to show an image for few seconds in QML?

I have an image that i need to show for few seconds. How to do it in QML ?
Does anyone has an an example ?
i have this one with state but its doest not appear . I need also to make ir repetitive
Item {
id: myimageanimation
x:0
y:0
z:2000
Image {
id: rect
x: 293
y: 242
source: "assets/ic_sound_popup_off.png"
}
states: [
State {
name: "actif"
PropertyChanges {
target: rect
visible:true
}
},
State {
name:"inactif"
PropertyChanges {
target:rect
visible : false
}
}
]
transitions: [
Transition {
NumberAnimation {
property: "visible"
duration:2000
easing.type: Easing.InOutQuad
}
}
]
You can use an PropertyAnimation on visible
Window {
id: root
visible: true
width: 1200
height: 1000
Image {
id: rect
anchors.fill: parent
source: 'qrc:/Pics/mypic.png'
}
PropertyAnimation {
running: true
target: rect
property: 'visible'
to: false
duration: 5000 // turns to false after 5000 ms
}
}
Or, as #ddriver mentioned, use the Timer-Object, e.g. like this:
Window {
id: root
visible: true
width: 1200
height: 1000
Image {
id: rect
anchors.fill: parent
source: 'qrc:/Pics/mypic.png'
}
Timer {
interval: 5000 // triggers every 5000 ms
onTriggered: rect.visible = false
running: true
}
}
You can also use the Animation to fade it away:
Window {
id: root
visible: true
width: 1200
height: 1000
Rectangle {
id: rect
anchors.fill: parent
color: 'red'
visible: opacity > 0
}
SequentialAnimation {
running: true
PauseAnimation {
duration: 4000 // Wait for 4000ms
}
NumberAnimation {
target: rect
property: 'opacity'
to: 0
duration: 1000 // Then fade away for 1000ms
}
}
}
With the Timer, you can use a Behavior on opacity to achive the same.

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.

QML components sequentially loading with fade

I need sequentially to load one by one components from array.
Before loading next component to Loader it should be fade-out , loaded and then fade-in.
The code above blinks not correctly and show this message:
"QML State: Binding loop detected for property "when"
What have I done wrong?
Thank you
import QtQuick 2.0
Rectangle {
id: screen
width: 400
height: 400
color: "black"
Rectangle {
id: view
anchors.fill: parent
Loader {
id: loader
onLoaded: { view.state="fadeIn"; }
}
states: [
State {
name: "fadeOut";
PropertyChanges { target: view; opacity: 0.1; }
},
State {
name: "fadeIn";
PropertyChanges { target: view; opacity: 1; }
},
State {
name: "load"; when: view.opacity == 0;
StateChangeScript { script: { loader.sourceComponent=com_array[index]; } }
}
]
transitions: [
Transition {
to: "fadeIn"
NumberAnimation { properties: "opacity"; from: 0.0; to: 0.99; duration: 2000 }
},
Transition {
to: "fadeOut"
NumberAnimation { properties: "opacity"; from: 0.99; to: 0; duration: 2000 }
}
]
}
Timer {
id: timer
interval: 3000; running: true; repeat: true
onTriggered: {
++index;
if( index >= com_array.length ) {
index = 0
}
view.state="fadeOut"
}
}
// -------------------------------------------------------------------
// Components
Item {
id: list
property Component red_rect_com : Component {
Rectangle {
width: screen.width; height: screen.height
Rectangle {
anchors.fill: parent; color: "red";
}
}
}
property Component blue_rect_com : Component {
Rectangle {
width: screen.width; height: screen.height
Rectangle {
anchors.fill: parent; color: "blue";
}
}
}
}
property int index: 0
property var com_array: [ list.red_rect_com, list.blue_rect_com ]
Component.onCompleted: { loader.sourceComponent = com_array[index]; }
}
UPD
Possible this could be useful for other, full example with correction ( thanks to answer author ):
import QtQuick 2.0
Rectangle {
id: screen
width: 400
height: 400
color: "black"
Rectangle {
id: view
anchors.fill: parent
property var sourceComponent
opacity: 0
function loadComponent( component, is_first_start ) {
sourceComponent = component;
if( is_first_start ) {
fadeOut.stop(); fadeIn.start();
}
else {
fadeIn.stop(); fadeOut.start();
}
}
Loader {
id: loader
onLoaded: {
fadeOut.stop();
fadeIn.start();
}
}
NumberAnimation on opacity {
id: fadeOut;
to: 0.0;
duration: 2000;
onStopped: { loader.sourceComponent=view.sourceComponent; }
}
NumberAnimation on opacity {
id: fadeIn;
to: 1.0;
duration: 2000
}
}
Timer {
id: timer
interval: 4000; running: true; repeat: true
onTriggered: {
++index;
if( index >= com_array.length ) {
index = 0
}
view.loadComponent( com_array[index], false );
}
}
// -------------------------------------------------------------------
// Components
Item {
id: list
property Component red_rect_com : Component {
Rectangle {
width: screen.width; height: screen.height
Rectangle {
anchors.fill: parent; color: "red";
}
}
}
property Component blue_rect_com : Component {
Rectangle {
width: screen.width; height: screen.height
Rectangle {
anchors.fill: parent; color: "blue";
}
}
}
}
property int index: 0
property var com_array: [ list.red_rect_com, list.blue_rect_com ]
Component.onCompleted: { view.loadComponent( com_array[index], true ); }
}
It gives you the error because QML make connections to communicate between changes:
onStateChange is connected by PropertyChanges that changes opacity to a view's opacity property.
The onOpacityChanged signal will be connected to State's when property.
The onWhenChanged signal will be connected to the state property, thus making binding Loop.
Another thing is that when you make NumberAnimation within Transition, you do not need to specify the from and to properties. The PropertyChanges sets the correct value.
Anyway, your states does not make sense, you using them wrong.
You should also wait for fadeOut, because it will load so fast that you will not see the effect.
Simply make two animations:
Rectangle {
id: view
anchors.fill: parent
property var sourceComponent
function loadComponent(component){
fadeIn.stop(); fadeOut.start();
sourceComponent = component;
}
Loader {
id: loader
onLoaded: { fadeOut.stop(); fadeIn.start(); }
}
NumberAnimation on opacity {
id: fadeOut
to: 0.0
onStopped: { loader.sourceComponent= sourceComponent; }
}
NumberAnimation on opacity {
id: fadeIn
to: 1.0
}
}
You should also change the loading to:
property int index: 0
onIndexChanged: { view.loadComponent(com_array[index]); }
But there is better solution: using ShaderEffectSource to take a frame of Loader before changing the source, and using it to fadeOut meanwhile loading the component.

Resources