Animating on Text change - qt

Is there anything for animating text changes? there is already animations for property changes, for example this code do the animation for properties opacity, width and scale and whenever they changed by states, they will get animations.
NumberAnimation {
properties: "opacity, width, scale, visible"
easing.type: Easing.OutBack; duration:500
}
However i didn't find anything for text change, for example counting from N to N+1 became animating (eg. fading out old value and fading new one). how i can animating text changes?

For this usecase I use Behavior with a custom Animation :
//FadeAnimation.qml
import QtQuick 2.0
SequentialAnimation {
id: root
property QtObject target
property string fadeProperty: "scale"
property int fadeDuration: 150
property alias outValue: outAnimation.to
property alias inValue: inAnimation.to
property alias outEasingType: outAnimation.easing.type
property alias inEasingType: inAnimation.easing.type
property string easingType: "Quad"
NumberAnimation { // in the default case, fade scale to 0
id: outAnimation
target: root.target
property: root.fadeProperty
duration: root.fadeDuration
to: 0
easing.type: Easing["In"+root.easingType]
}
PropertyAction { } // actually change the property targeted by the Behavior between the 2 other animations
NumberAnimation { // in the default case, fade scale back to 1
id: inAnimation
target: root.target
property: root.fadeProperty
duration: root.fadeDuration
to: 1
easing.type: Easing["Out"+root.easingType]
}
}
Please note that it can be done without all the added properties, but I have them to enable easy customization.
An example usage could be :
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
anchors.centerIn: parent
color: "red"
width: 100
height: width
radius: width/2
Text {
id: textItem
anchors.centerIn: parent
font.pixelSize: 30
color: "white"
property int foo: 0
// ### Important part ###
text: foo
Behavior on foo {
FadeAnimation {
target: textItem
}
}
// ######################
}
MouseArea {
anchors.fill: parent
onClicked: textItem.foo++
}
}
}
Output : https://zippy.gfycat.com/SilentImpressiveChameleon.webm
The fadeProperty is scale by default but it also works great with opacity.
EDIT :
I've implemented this as ready-to-use components in https://github.com/okcerg/quickbehaviors

I would suggest something like the following AnimatedText.qml:
import QtQuick 2.5
Item{
property real progress: 0.0
property string text0
property string text1
Text{
text: text0
opacity: 1.0 - progress
}
Text{
text: text1
opacity: progress
}
}
Can be used as follows:
AnimatedText{
text0: "First text"
text1: "Second text"
NumberAnimation on progress {
from: 0.0
to: 1.0
duration: 5000
}
}

I created a custom Item for this purpose with fade animation. You can edit it for any animation:
AnimatedText.qml
import QtQuick 2.7
Item {
id: root
width: Math.max(txt1.width, txt2.width);
height: Math.max(txt1.height, txt2.height);
property string text: ""
property int currentActiveTxt: 1
property real pointSize: 20
Text {
id: txt1
font { pointSize: root.pointSize }
}
Text {
id: txt2
font { pointSize: root.pointSize }
}
onTextChanged: {
if(currentActiveTxt == 1) {
txt2.text = root.text;
currentActiveTxt = 2;
root.state = "txt2 is active";
} else {
txt1.text = root.text;
currentActiveTxt = 1;
root.state = "txt1 is active";
}
}
states: [
State {
name: "txt1 is active"
PropertyChanges {
target: txt1
opacity: 1.0
}
PropertyChanges {
target: txt2
opacity: 0.0
}
},
State {
name: "txt2 is active"
PropertyChanges {
target: txt1
opacity: 0.0
}
PropertyChanges {
target: txt2
opacity: 1.0
}
}
]
state: "txt1 is active"
transitions: [
Transition {
from: "txt1 is active"
to: "txt2 is active"
NumberAnimation {
property: "opacity"
duration: 200
}
},
Transition {
from: "txt2 is active"
to: "txt1 is active"
NumberAnimation {
property: "opacity"
duration: 200
}
}
]
}
Sample usage:
Window {
id:root
visible: true
width: 340
height: 480
title: qsTr("Hello World")
AnimatedText {
id: txt
pointSize: 30
anchors.left: parent.left
anchors.top: parent.top
anchors.margins: 10
text: ":)"
}
Timer {
interval: 1000
running: true
repeat: true
property int i: 0
onTriggered: txt.text = i++;
}
}

Related

Flipable overlapping other elements

In my QML application I'm trying to create a grid of items that can be flipped at the press of a button. The backside of such an item should then fill a major part of the screen until it is flipped back.
Let's say I start off with the following view of my application
When I press the question mark button of the item in the center then the item is flipped and moved slightly. What I would expect to see after this is the following
The blue box is the backside of my item and it covers most of the screen. Pressing the 'X'-Button on the top right would again flip the item back.
However what I actually see after flipping the first time is the following
You can see that parts of the items in my grid are covered by my flipped item and parts are not.
The code I'm using is as follows
import QtQuick 2.9
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
import QtQuick.Window 2.2
Window {
id: main
width: 640
height: 480
visible: true
title: qsTr("Hello World")
function absolutePos(item) {
var my_x = item.x
var my_y = item.y
if (item.parent !== null) {
var parent_pos = absolutePos(item.parent)
my_x += parent_pos.x
my_y += parent_pos.y
}
return {x: my_x, y: my_y}
}
GridLayout {
columns: 5; rows: 3
Repeater {
model: 15
delegate: Item {
width: main.width / 5 - 10
height: main.height / 3 - 10
Flipable {
id: flipable
anchors.fill: parent
property bool flipped: false
front: Rectangle {
anchors.fill: parent
border.color: "black"
border.width: 2
}
back: Rectangle {
id: backSide
width: 580; height: 400
property var absolute_pos: absolutePos(this)
border.color: "blue"
border.width: 2
Button {
anchors.top: parent.top
anchors.right: parent.right
text: "X"
width: 30; height: 30
onClicked: {
flipable.flipped = !flipable.flipped
}
}
}
transform: [
Rotation {
id: rotation
origin.x: flipable.width / 2
origin.y: flipable.height / 2
axis.x: 0; axis.y: 1; axis.z: 0
angle: 0
},
Translate {
id: translation
x: 0; y: 0
}
]
states: State {
name: "back"
PropertyChanges {
target: rotation
angle: 180
}
PropertyChanges {
target: translation
x: 490 - backSide.absolute_pos.x
}
PropertyChanges {
target: translation
y: 40 - backSide.absolute_pos.y
}
when: flipable.flipped
}
transitions: Transition {
ParallelAnimation {
NumberAnimation {
target: rotation
property: "angle"; duration: 300
}
NumberAnimation {
target: translation
property: "x"; duration: 300
}
NumberAnimation {
target: translation
property: "y"; duration: 300
}
}
}
}
Button {
anchors.top: parent.top
anchors.right: parent.right
text: "?"
width: 30; height: 30
onClicked: {
flipable.flipped = !flipable.flipped
}
}
}
}
}
}
I was already trying to achieve the effect by manually setting the parent of my Flipable to Window.contentItem so that it will always be above any other items. However this also doesn't fix the problem since the item will still cover the siblings following the current item.
Also I'm still hoping, there is a solution which doesn't require manipulating the z-order of my items in some arcane way.
I am not sure what you mean by "some arcane way", but changing the z property of your delegate is perfectly fine:
delegate: Item {
z: flipable.flipped ? 1 : 0
// ...
}
You will also probably want to hide the "?" button when flipped:
visible: !flipable.flipped

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: animation only when mouse enters image

I would like to make an animation when mouse comes over the image, but NOT when mouse leaves the image.
Item{
width: 800
height:800
Rectangle{
id: blueRec
width: 100; height: 100; color: "blue"
MouseArea{
anchors.fill: parent
onClicked: {
im1.visible = true
im1.source = "1.png"
}
}
}
Image {
id: im1
scale: im1MouseArea.containsMouse ? 0.8 : 1.0
Behavior on scale {
NumberAnimation{
id: anim
from: 0.95
to: 1
duration: 400
easing.type: Easing.OutBounce
}
}
MouseArea{
id: im1MouseArea
hoverEnabled: true
anchors.fill: parent
}
}
}
The code above makes also animation, when mouse is leaving image.
Can someone help?
Setting the scale and then triggering an animation that alters the scale seems like an odd approach. If I were you, I'd break this out into states and set the animation to trigger on the appropriate transition.
Here's an example of how this could be done:
Image {
id: im1
states: [ "mouseIn", "mouseOut" ]
state: "mouseOut"
transitions: [
Transition {
from: "*"
to: "mouseIn"
NumberAnimation {
target: im1
properties: "scale"
from: 0.95
to: 1
duration: 400
easing.type: Easing.OutBounce
}
}
]
MouseArea{
id: im1MouseArea
hoverEnabled: true
anchors.fill: parent
onContainsMouseChanged: {
im1.state = containsMouse ? "mouseIn" : "mouseOut"
}
}
}

Component View with transaction in QML

I tries to implement View, which rendering some Component and on change source Component plays fadeout/fadein animation.
My code:
import QtQuick 2.1
Item {
id: componentViewRoot
onOpacityChanged: console.log(opacity)
property Component source: null
PropertyAnimation {
id: fadeout
target: componentViewRoot;
alwaysRunToEnd: true
property: "opacity";
to: 0;
duration: 1000
}
PropertyAnimation {
id: fadein
target: componentViewRoot;
alwaysRunToEnd: true
property: "opacity";
to: 1;
duration: 1000
}
onSourceChanged: {
fadeout.start()
loader.sourceComponent = source;
}
Loader{
id:loader
anchors.fill: parent
onLoaded: fadein.start()
}
}
But it not working (don't plays animation) properly.
Please help me.
UDP:
I want use this View like StackView from QtQuick.Controls without depth.
Some code:
Rectangle {
id: mainObjectRoot
implicitHeight: 440
implicitWidth: 720
color: "#f8f8f8"
ComponentView{
id: compView
height: 265
width: 680
onXChanged: console.log("compView"+x)
onYChanged: console.log("compView"+y)
anchors.right: parent.right
anchors.rightMargin: 20
anchors.bottom: parent.bottom
anchors.bottomMargin: 45
anchors.top: parent.top
anchors.topMargin: 130
anchors.left: parent.left
anchors.leftMargin: 20
source: passwordView
}
Connections{
target: sessionManager
onRequiredPasswordChanged :{
if(sessionManager.requiredPassword){
compView.source = passwordView
}
else{
compView.source = mainview
}
}
}
Component {
id: passwordView
PasswordView{
}
}
Component {
id: helpview
HelpView{
}
}
Component{
id: mainview
MainView{
}
}
Component{
id: settingsview
SettingsView{
}
}
In my example code I want to play transition animation on compView.source changed
For now I was
#ComponentView.qml:
import QtQuick 2.1
Item {
id: componentViewRoot
//#source for setting
property Component source: null
//#item for acces rendering item
property alias item: loader.item
function __changeItem(component){
anim._component = component
anim.start()
}
SequentialAnimation {
id:anim
property Component _component: null
NumberAnimation {
id: fadeout
target: componentViewRoot
alwaysRunToEnd: true
property: "opacity"
from : 1
to: 0
}
PropertyAction{
target: loader
property: "sourceComponent"
value: anim._component
}
NumberAnimation {
id: fadein
target: componentViewRoot
alwaysRunToEnd: true
property: "opacity"
from : 0
to: 1
}
}
onSourceChanged: {
__changeItem(source)
}
Loader{
id:loader
anchors.fill: parent
}
}

QML: How to make nice transition effect between image sources (one fades into the other)?

I wonder how to make smooth transitions betwen image sources in QML, I try
import QtQuick 1.1
Image {
id: rect
source: "quit.png"
smooth: true
MouseArea {
id: mouseArea
anchors.fill: parent
anchors.margins: -10
hoverEnabled: true //this line will enable mouseArea.containsMouse
onClicked: Qt.quit()
}
states: State {
name: "mouse-over"; when: mouseArea.containsMouse
PropertyChanges { target: rect; scale: 0.8; source :"quit2.png" }
}
transitions: Transition {
NumberAnimation { properties: "scale, source"; easing.type: Easing.InOutQuad; duration: 1000 }
}
}
But It does not work on source as a transition just as final state change.. so I wonder how to make one image source fade into andothe and back?
You want the first image to fade out into the other? How about if you place two Image objects on top of each other, then animate the opacity property?
EDIT: This worked for me (I'm using QtQuick 1.0 because my Qt Creator installation is a bit outdated):
import QtQuick 1.0
Rectangle {
Image {
id: rect
source: "quit.png"
smooth: true
opacity: 1
MouseArea {
id: mouseArea
anchors.fill: parent
anchors.margins: -10
hoverEnabled: true //this line will enable mouseArea.containsMouse
onClicked: Qt.quit()
}
states: State {
name: "mouse-over"; when: mouseArea.containsMouse
PropertyChanges { target: rect; scale: 0.8; opacity: 0}
PropertyChanges { target: rect2; scale: 0.8; opacity: 1}
}
transitions: Transition {
NumberAnimation { properties: "scale, opacity"; easing.type: Easing.InOutQuad; duration: 1000 }
}
}
Image {
id: rect2
source: "quit2.png"
smooth: true
opacity: 0
anchors.fill: rect
}
}
To the question in your comment: you can place the image exactly on top of the other by copying the anchors thru anchors.fill: rect
Here is also a simple scroll transition between images:
import QtQuick 2.6
import QtQuick.Controls 1.4
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
id: imageRect
anchors.centerIn: parent
width: 240
height: 320
clip: true
property int currentIndex: 0
property var imageSources: [ "imageLeft.jpg", "imageCenter.jpg" ]
Repeater {
model: imageRect.imageSources
Image {
id: image
width: parent.width
height: parent.height
x: index * parent.width - imageRect.currentIndex * parent.width
fillMode: Image.PreserveAspectFit
source: imageRect.imageSources[index]
Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } }
}
}
}
Button {
id: leftButton
anchors.bottom: parent.bottom
text: "left"
onClicked: if(imageRect.currentIndex > 0) imageRect.currentIndex--
}
Button {
id: rightButton
anchors.bottom: parent.bottom
anchors.left: leftButton.right
text: "right"
onClicked: if(imageRect.currentIndex < imageRect.imageSources.length - 1) imageRect.currentIndex++
}
Button {
id: addButton
anchors.bottom: parent.bottom
anchors.left: rightButton.right
text: "+"
onClicked: imageRect.imageSources = [ "imageLeft.jpg", "imageCenter.jpg" , "imageRight.jpg" ]
}
}

Resources