Dynamically reparenting objects in QML fails on initial load - qt

I am trying to dynamically reparent QML objects generated in a repeater according to data they inherit from their model.
This works like a charm - with one catch. When the object is generated for the first time, it is automatically reparented to the Repeater's parent after the state's ParentChange object makes its changes. Run the following QML file in a QML viewer, paying attention to the order of the console messages to see what I'm describing.
After you've clicked on each of the objects, they behave as expected.
import QtQuick 1.1
Rectangle {
id: container
height: 300
width: 300
signal completed
ListModel {
id: fooModel
ListElement { data: "red" }
ListElement { data: "red" }
ListElement { data: "blue" }
}
Component.onCompleted: {
console.log("Rect Completed!")
container.completed()
}
// The object I want to dynamically move
Component {
id: delg
Rectangle {
id: moveable
height: 40; width: 100
border.width: 1; border.color: "black"
state: model.data
color: state
// The following code makes it work, but feels very hackish
/*Connections {
target: container
onCompleted: {
moveable.parent = moveable.state == "red" ? red_col : blue_col
}
}*/
onStateChanged: { console.log("New state: " + state) }
onParentChanged: { console.log("New parent: " + parent) }
Component.onCompleted: { console.log("Delegate Completed!") }
MouseArea {
anchors.fill: parent
onClicked: {
// I know this is bad to do, but in my REAL application,
// the change is triggered through the model, not the qml
// object
moveable.state = (moveable.state == "red" ? "blue" : "red")
}
}
states: [
State {
name: 'red'
ParentChange { target: moveable; parent: red_col; x: 0 }
},
State {
name: 'blue'
ParentChange { target: moveable; parent: blue_col; x: 0 }
}
]
transitions: [ Transition {
ParentAnimation {
NumberAnimation { properties: 'x,y,height,width' }
}
}]
}
}
// Generates the Objects
Repeater {
id: repeat
model: fooModel
delegate: delg
}
// Display
Row {
spacing: 100
Column {
id: red_col
spacing: 10
width: 100; height: 300
move: Transition { NumberAnimation { properties: "y" } }
add: Transition { NumberAnimation { properties: "y" } }
}
Column {
id: blue_col
spacing: 10
width: 100; height: 300
move: Transition { NumberAnimation { properties: "y" } }
add: Transition { NumberAnimation { properties: "y" } }
}
}
}
I figured out a way to fix the behavior, but it's not pretty. (See the commented out "Connections" code above for that fix).
Is there a cleaner/less-hacky way to accomplish the same thing I'm trying here?

Easy way of doing it is placing extra Item under your delegate. This will cause Repeater to reparent Item and your own code would set new parent of its child, your Rectangle element. Like this:
import QtQuick 1.1
Rectangle {
id: container
height: 300
width: 300
signal completed
ListModel {
id: fooModel
ListElement { data: "red" }
ListElement { data: "red" }
ListElement { data: "blue" }
}
Component.onCompleted: {
console.log("Rect Completed!")
container.completed()
}
// The object I want to dynamically move
Component {
id: delg
Item {
Rectangle {
id: moveable
height: 40; width: 100
border.width: 1; border.color: "black"
state: model.data
color: state
// The following code makes it work, but feels very hackish
/*Connections {
target: container
onCompleted: {
moveable.parent = moveable.state == "red" ? red_col : blue_col
}
}*/
onStateChanged: { console.log("New state: " + state) }
onParentChanged: { console.log("New parent: " + parent) }
Component.onCompleted: { console.log("Delegate Completed!") }
MouseArea {
anchors.fill: parent
onClicked: {
// I know this is bad to do, but in my REAL application,
// the change is triggered through the model, not the qml
// object
moveable.state = (moveable.state == "red" ? "blue" : "red")
}
}
states: [
State {
name: 'red'
ParentChange { target: moveable; parent: red_col; x: 0 }
},
State {
name: 'blue'
ParentChange { target: moveable; parent: blue_col; x: 0 }
}
]
transitions: [ Transition {
ParentAnimation {
NumberAnimation { properties: 'x,y,height,width' }
}
}]
}
}
}
// Generates the Objects
Repeater {
id: repeat
model: fooModel
delegate: delg
}
// Display
Row {
spacing: 100
Column {
id: red_col
spacing: 10
width: 100; height: 300
move: Transition { NumberAnimation { properties: "y" } }
add: Transition { NumberAnimation { properties: "y" } }
}
Column {
id: blue_col
spacing: 10
width: 100; height: 300
move: Transition { NumberAnimation { properties: "y" } }
add: Transition { NumberAnimation { properties: "y" } }
}
}
}

Related

How to trigger An Action Once After NumberAnimation Is Completed In QML

I am trying to call a reset function once after the animation is completed. But in the below given code , the animation starts after resetting the values
On Long press of the button I need to start an animation which basically acts as a progress bar, and once the animation is completed I need to call the reset() function.
I have tried the below given code , But here the animation starts once after the resetting of values are done.
Button {
id: button1
onPressAndHold: {
rectangle.visible = true
timer.restart()
}
}
Item {
id: rectangle
Behavior on width {
NumberAnimation {
duration: 1000
easing.type: Easing.InOutCubic
}
}
Image {
id: img
source: "somesource"
fillMode: Image.PreserveAspectCrop
}
}
Timer {
id: timer
repeat: true
interval: 50
onTriggered: {
rectangle.width = img.sourceSize.width * img.progress
if (rectangle.width <= img.sourceSize.width) {
timer.stop()
reset(values)
}
}
}
can you please let me know on how modify it such that the animation completes first and then the reset is done. Thank you in advance!
Ok, that really works for standalone Animation only that sounds a bit strange for me since the common usecase is using animations inside Behavior or State.
So you can use NumberAnimation.onRunningChanged or ScriptAction as #iam_peter said or use Transition.onRunningChanged as well:
Window {
height: 200
width: 600
visible: true
title: qsTr("Animation test")
RowLayout {
width: parent.width
height: 100
anchors.centerIn: parent
Button {
text: "start"
onClicked: {
testRect.state = "state2"
}
}
Rectangle {
id: testContainer
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle {
id: testRect
height: 100
width: 0
color: "orange"
states: [
State {
name: "state1"
PropertyChanges {
target: testRect
width: 0
}
},
State {
name: "state2"
PropertyChanges {
target: testRect
width: testContainer.width
}
}
]
transitions: Transition {
NumberAnimation {
property: "width"
duration: 2000
easing.type: Easing.InQuad
}
onRunningChanged: {
if(running == false)
{
finishRect.color = "red";
}
}
}
}
}
Rectangle {
id: finishRect
Layout.preferredWidth: 100
color: "yellow"
width: 100
height: width
radius: width / 2
}
}
}
According to this post you have two options.
You can us the onRunningChanged handler and check if the animation is still running. If not call anything you want at that point.
Rectangle {
id: rect
width: 100; height: 100
color: "red"
Behavior on width {
NumberAnimation {
duration: 1000
onRunningChanged: {
if (!running)
console.log("Animation finished")
}
}
}
MouseArea {
anchors.fill: parent
onClicked: rect.width = 50
}
}
The other option would be to create a SequentialAnimation and add a SrcriptAction that runs after the NumberAnimation is completed.
Rectangle {
id: rect
width: 100; height: 100
color: "red"
Behavior on width {
SequentialAnimation {
NumberAnimation { duration: 1000 }
ScriptAction {
script: console.log("Animation finished")
}
}
}
MouseArea {
anchors.fill: parent
onClicked: rect.width = 50
}
}

QML: ListView items expansion

I have a ListView and its delagates. My intention is to make expandable delegates (eg. I have three parent delegates, if the parent delegate is clicked, it will expand its children). I made this demonstration:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
id: root
width: 1406; height: 536
ListModel {
id: animalsModel
ListElement { name: "Cats"; isExpanded: false; type: "parent"}
ListElement { name: "Puss in Boots 1"; isExpanded: false; type: "Cats"}
ListElement { name: "Puss in Boots 2"; isExpanded: false; type: "Cats"}
ListElement { name: "Puss in Boots 3"; isExpanded: false; type: "Cats"}
ListElement { name: "Dogs"; isExpanded: false; type: "parent"}
ListElement { name: "Pug 1"; isExpanded: false; type: "Dogs" }
ListElement { name: "Birds"; isExpanded: false; type: "parent"}
ListElement { name: "Parrot 1"; isExpanded: false; type: "Birds" }
ListElement { name: "Parrot 2"; isExpanded: false; type: "Birds" }
ListElement { name: "Parrot 3"; isExpanded: false; type: "Birds" }
}
ListView {
id: listing
width: 181
height: parent.height
model: animalsModel
delegate: listdelegate
}
Component {
id: listdelegate
Rectangle {
id: menuItem
width: 181
height: type === "parent" ? 55 : 0
color: type === "parent" ? "lightblue" : "white"
border.width: 1
states: State {
name: "expanded"
when: isExpanded
PropertyChanges {
target: menuItem
height: 55
}
}
transitions:[
Transition {
from: ""
to: "expanded"
reversible: true
SequentialAnimation {
PropertyAnimation { property: "height"; duration: 1000 }
}
}
]
Text {
id: text
text: name
clip: true
anchors.fill: parent
}
MouseArea {
function expand(isExpanded) {
for(var i = 0; i < animalsModel.count; ++i) {
var animal = animalsModel.get(i)
if(animal.type === name) {
animal.isExpanded = isExpanded
}
}
}
anchors.fill: parent
onClicked: {
if (type == "parent") {
if (isExpanded == false) {
// expand
expand(true)
}
else {
// collapse
expand(false)
}
isExpanded = !isExpanded
}
}
}
}
}
}
It works perfectly except one small detail: the height animation is very expensive in my case (I have more complex delagate). How can I make the expanding animation height independent. My idea is to not resize the delagetes, only hide them behind the parent. Like a train going into the tunnel. But I cannot manipulate the x property of delagates in case of vertical ListView.
Any idea how to achive this? In the best scenario how to make the animation I described?

QML List View Drag and Drop

I would like to create two qml list views that can perform two functions:
Drag and drop items within one list to change order of items
Drag and drop items cross lists. Items will remove from one list and add to another
Based on the Drag and Drop example in Qt Documentation, I decided to create two list views that accessing the same list model. Each item in the list model has a key as a bool value. Using DelegateModel with a filter, I can display items with key equals to true in one list and with false in the second list. When dragging an item from first list to second list, the key value will be flipped.
Now, I am facing two problems:
onDropped is not executed
DelegateModel is not refreshed after key value changed
Can anybody help me solve these problems or maybe have a better solution?
import QtQuick 2.5
import QtQuick.Window 2.0
import QtQml.Models 2.2
import QtQuick.Controls 2.2
Window {
visible: true
width: 300
height: 400
title: qsTr("List View Test")
property bool dropAreaOneEntered: false
property bool dropAreaTwoEntered: false
property int listNumer: 0
Rectangle {
id: root
width: parent.width
height: parent.height
ListView {
id: displayListOne
width: 100; height: parent.height
model: displayListOneModel
property int dragItemIndex: -1
moveDisplaced: Transition {
NumberAnimation{
properties: "x,y"
duration: 200
}
}
}
ListView {
id: displayListTwo
width: 100; height: parent.height
anchors.right: parent.right
model: displayListTwoModel
property int dragItemIndex: -1
moveDisplaced: Transition {
NumberAnimation{
properties: "x,y"
duration: 200
}
}
}
// ..........................................................
ListModel {
id: itemsListModel
// #disable-check M16
ListElement { colore: "blue"; key: true }
// #disable-check M16
ListElement { colore: "orange"; key: false }
// #disable-check M16
ListElement { colore: "blue"; key: true }
// #disable-check M16
ListElement { colore: "orange"; key: false }
// #disable-check M16
ListElement { colore: "blue"; key: true }
// #disable-check M16
ListElement { colore: "orange"; key: false }
// #disable-check M16
ListElement { colore: "blue"; key: true }
// #disable-check M16
ListElement { colore: "orange"; key: false }
}
// ..........................................................
DelegateModel {
id: displayListOneModel
model: itemsListModel
delegate: MouseArea {
id: mouseOneArea
width: 100; height: 100
property bool held: false
drag.target: held ? itemOneBlock : undefined
drag.axis: Drag.XAndYAxis
property var draggedItem: null
onPressAndHold: {
held = true
listNumer = 1
displayListOne.dragItemIndex = index
}
onReleased: {
listNumer = 0
held = false
}
Rectangle {
id: itemOneBlock
anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
width: 100
height: 100
color: colore
opacity: mouseOneArea.held ? 0.8 : 1.0
Drag.active: mouseOneArea.held
Drag.source: mouseOneArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
states: State{
when: mouseOneArea.held
ParentChange { target: itemOneBlock; parent: root }
AnchorChanges {
target: itemOneBlock
anchors { horizontalCenter: undefined; verticalCenter: undefined }
}
}
}
DropArea {
id: listOneDropArea
anchors.fill: parent
property bool flag: false
onExited: dropAreaOneEntered = false
onEntered: {
dropAreaOneEntered = true
if (dropAreaOneEntered & ! dropAreaTwoEntered){
if (listNumer == 1){
displayListOneModel.items.move(drag.source.DelegateModel.itemsIndex,
mouseOneArea.DelegateModel.itemsIndex)
}
}
}
onDropped: {
console.log("onDropped has been executed!")
}
}
}
groups: [
DelegateModelGroup {
includeByDefault: true
name: "listOne"
}
]
filterOnGroup: "listOne"
Component.onCompleted: {
var rowCount = itemsListModel.count;
for( var i = 0; i < items.count; i++ ) {
var entry = items.get(i).model;
if((entry.key != true)) {
items.removeGroups(i,1,"listOne")
}
}
}
}
DelegateModel {
id: displayListTwoModel
model: itemsListModel
delegate: MouseArea {
id: mouseTwoArea
width: 100; height: 100
property bool held: false
drag.target: held ? itemTwoBlock : undefined
drag.axis: Drag.XAndYAxis
onPressAndHold: {
held = true
listNumer = 2
displayListTwo.dragItemIndex = index
console.log("current index is "+index)
}
onReleased: {
listNumer = 0
held = false
}
Rectangle {
id: itemTwoBlock
anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
width: 100
height: 100
color: colore
opacity: mouseTwoArea.held ? 0.8 : 1.0
Drag.active: mouseTwoArea.held
Drag.source: mouseTwoArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
states: State{
when: mouseTwoArea.held
ParentChange { target: itemTwoBlock; parent: root }
AnchorChanges {
target: itemTwoBlock
anchors { horizontalCenter: undefined; verticalCenter: undefined }
}
}
}
DropArea {
id: listTwoDropArea
anchors.fill: parent
onExited: dropAreaTwoEntered = false
onEntered: {
dropAreaTwoEntered = true
if (dropAreaTwoEntered & ! dropAreaOneEntered){
if (listNumer == 2){
displayListTwoModel.items.move(drag.source.DelegateModel.itemsIndex,
mouseTwoArea.DelegateModel.itemsIndex)
}
}
}
onDropped: {
console.log("Flip the key value here!")
}
}
}
groups: [
DelegateModelGroup {
includeByDefault: true
name: "listTwo"
}
]
filterOnGroup: "listTwo"
Component.onCompleted: {
var rowCount = itemsListModel.count;
for( var i = 0; i < items.count; i++ ) {
var entry = items.get(i).model;
if((entry.key != false)) {
items.removeGroups(i,1,"listTwo")
}
}
}
}
}
}

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'
}
]
}

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