How to implement an opening and closing animation on a frame?
-
In my project, I have several user messages to show, depending on what happens in the application. To achieve that, I created a frame where my messages may appear, which acts as a view for my messages.
The message component itself is just another frame, containing a glyph, a text and a close button. Each message is created dynamically and added to the message view described above when needed to be shown, and is destroyed when closed.
Here is how my message view looks actually:
All works as I expected, however I want now to add an opening and a closing animation. I want to animate the height in a such manner the message gives the effect to scroll down when opening, and scroll up when closing.
However I don't know how to achieve this animation. I tried by myself, but my code just doesn't work, and I don't know how to implement a such animation in the above described dynamic context.
Here is my actual code:
- Message view, where message items are created and deleted
import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.15 import QtQuick.Shapes 1.15 import QtGraphicalEffects 1.15 import QtQuick.Templates 2.15 as T /** * Message view *@author JMR */ T.Frame { // advanced properties property int m_MaxMsgOnView: 3 property int m_MsgOffset: 4 // aliases property var model: null // common properties id: frMessageView visible: true /** * Frame background */ background: Rectangle { color: "transparent" anchors.fill: parent } /** * Bind several signals from the c++ model to the message view */ Connections { // common properties target: model /** * Called when a message was added */ function onMessageAdded() { if (g_Debug) console.log("Notificaion message added signal"); // recreate the view clearView(); populateView(); // show it (as message was added, view should at least contain one) frMessageView.visible = true; } /** * Called when a message was removed */ function onMessageRemoved() { if (g_Debug) console.log("Notificaion message removed signal"); // clear the current view content clearView(); // is view empty? if (!model.rowCount()) { frMessageView.visible = false; return; } // populate the view with remaining content populateView(); } } /** * Clear message view */ function clearView() { // get frame children count var childrenCount = frMessageView.children.length; // iterate through frame children components for (var i = childrenCount - 1; i >= 0; --i) // is component a message item? if (frMessageView.children[i].m_ItemType === "WQTMessageItem") // delete it (be careful, component will be moved in garbage collector and not deleted immediately) frMessageView.children[i].destroy(); } /** * Populate message view */ function populateView() { // load the message item component var component = Qt.createComponent('WQTMessageItem.qml'); // succeeded? if (component.status !== Component.Ready) { console.error("Message view - populateView - an error occurred while message item template was created - " + component.errorString()); return; } // get message count to show (no more than 3) and first visible message var rowCount = model.rowCount(); var messageCount = rowCount >= m_MaxMsgOnView ? m_MaxMsgOnView : rowCount; var startIndex = rowCount - messageCount; var xOffset = 0; var yOffset = m_MaxMsgOnView * m_MsgOffset; // iterate through messages to create for (var i = 0; i < messageCount; ++i) { // calculate message index to show var curIndex = startIndex + i; // out of bounds? if (curIndex >= rowCount) break; // add message item component to view component.createObject(frMessageView, {"id": "miMessageItem" + i, "m_Target": frMessageView, "x": xOffset, "y": yOffset, "width": frMessageView.width - 10, "height": 48, "m_MaxHeight": 48, "state": "EXTENDED", "m_Index": curIndex}); // calculate next offset xOffset += m_MsgOffset; yOffset -= m_MsgOffset; } } }
- Message item
import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.15 import QtQuick.Shapes 1.15 import QtGraphicalEffects 1.15 import QtQuick.Templates 2.15 as T /** * Message item *@author JMR */ T.Frame { // advanced properties property var m_Target: null property string m_ItemType: "WQTMessageItem" property string m_NotificationIcon: "qrc:/Resources/Images/main/icon_notification.svg" property string m_WarningIcon: "qrc:/Resources/Images/main/icon_warning.svg" property string m_ErrorIcon: "qrc:/Resources/Images/main/icon_error.svg" property string m_CloseBtnGlyph: "qrc:/Resources/Images/main/icon_small_close.svg" property int m_Index: 0 property int m_MaxHeight: 48 // state dependent properties property int m_Type property string m_BgColor property string m_OutlineColor property string m_TextColor property string m_Glyph /** * Equivalent to WQTMessageViewModel::IEMessageType */ enum IEMessageType { Notification, Warning, Error } // common properties id: frFrame height: 0 state: "COLLAPSED" /** * Item background */ background: Rectangle { // common properties anchors.fill: parent color: m_BgColor radius: 5 border.width: 1 border.color: m_OutlineColor } /** * Message icon */ Image { // common properties id: imIcon anchors.left: parent.left anchors.leftMargin: 12 anchors.top: parent.top anchors.topMargin: 10 width: 16 height: 16 source: m_Glyph sourceSize: Qt.size(width, height) fillMode: Image.PreserveAspectFit smooth: true } /** * Close button */ Button { // common properties id: btClose display: AbstractButton.IconOnly anchors.right: parent.right anchors.rightMargin: 6 anchors.top: parent.top anchors.topMargin: 7 width: 8 height: 8 background: null /** * Close button glyph */ Image { // common properties anchors.fill: parent source: m_CloseBtnGlyph sourceSize: Qt.size(width, height) fillMode: Image.PreserveAspectFit smooth: false } /** * Called when close button is clicked */ onClicked: { frFrame.state = "COLLAPSED"; if (m_Target.model) m_Target.model.removeMessageAt(m_Target.model.rowCount() - 1); } } /** * Message title */ Text { // common properties id: laTitle anchors.top: parent.top anchors.topMargin: 8 anchors.left: imIcon.right anchors.leftMargin: 7 anchors.right: btClose.left anchors.rightMargin: 7 font.bold: true font.pixelSize: 12 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter renderType: Text.NativeRendering text: m_Target.model ? m_Target.model.getMessageTitle(m_Index) : "" color: m_TextColor } /** * Message caption */ Text { // common properties id: laDescription anchors.top: laTitle.bottom anchors.topMargin: 0 anchors.left: imIcon.right anchors.leftMargin: 7 anchors.right: btClose.left anchors.rightMargin: 7 anchors.bottom: parent.bottom anchors.bottomMargin: 5 font.pixelSize: 14 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter renderType: Text.NativeRendering text: m_Target.model ? m_Target.model.getMessageCaption(m_Index) : "" wrapMode: Text.WordWrap elide: Text.ElideRight color: m_TextColor } /** * Component state array */ states: [ State { name: "EXTENDED" PropertyChanges {target: frFrame; height: m_MaxHeight} }, State { name: "COLLAPSED" PropertyChanges {target: frFrame; height: 0} } ] /** * Transitions between states */ transitions: [ Transition { from: "*"; to: "EXTENDED" NumberAnimation {property: "height"; easing.type: Easing.Linear; duration: 5000} }, Transition { from: "*"; to: "COLLAPSED" NumberAnimation {property: "height"; easing.type: Easing.Linear; duration: 5000} } ] }
The code is just a try to animate the message, but it not working. Can someone explain me how to correct it to reach my objective? I would be very greatful.
NOTE I shown my code as is, forgive me, it depends on many other classes in my project, which I cannot show here. I hope this will be enough to understand the situation and help me to solve it.
-
In my project, I have several user messages to show, depending on what happens in the application. To achieve that, I created a frame where my messages may appear, which acts as a view for my messages.
The message component itself is just another frame, containing a glyph, a text and a close button. Each message is created dynamically and added to the message view described above when needed to be shown, and is destroyed when closed.
Here is how my message view looks actually:
All works as I expected, however I want now to add an opening and a closing animation. I want to animate the height in a such manner the message gives the effect to scroll down when opening, and scroll up when closing.
However I don't know how to achieve this animation. I tried by myself, but my code just doesn't work, and I don't know how to implement a such animation in the above described dynamic context.
Here is my actual code:
- Message view, where message items are created and deleted
import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.15 import QtQuick.Shapes 1.15 import QtGraphicalEffects 1.15 import QtQuick.Templates 2.15 as T /** * Message view *@author JMR */ T.Frame { // advanced properties property int m_MaxMsgOnView: 3 property int m_MsgOffset: 4 // aliases property var model: null // common properties id: frMessageView visible: true /** * Frame background */ background: Rectangle { color: "transparent" anchors.fill: parent } /** * Bind several signals from the c++ model to the message view */ Connections { // common properties target: model /** * Called when a message was added */ function onMessageAdded() { if (g_Debug) console.log("Notificaion message added signal"); // recreate the view clearView(); populateView(); // show it (as message was added, view should at least contain one) frMessageView.visible = true; } /** * Called when a message was removed */ function onMessageRemoved() { if (g_Debug) console.log("Notificaion message removed signal"); // clear the current view content clearView(); // is view empty? if (!model.rowCount()) { frMessageView.visible = false; return; } // populate the view with remaining content populateView(); } } /** * Clear message view */ function clearView() { // get frame children count var childrenCount = frMessageView.children.length; // iterate through frame children components for (var i = childrenCount - 1; i >= 0; --i) // is component a message item? if (frMessageView.children[i].m_ItemType === "WQTMessageItem") // delete it (be careful, component will be moved in garbage collector and not deleted immediately) frMessageView.children[i].destroy(); } /** * Populate message view */ function populateView() { // load the message item component var component = Qt.createComponent('WQTMessageItem.qml'); // succeeded? if (component.status !== Component.Ready) { console.error("Message view - populateView - an error occurred while message item template was created - " + component.errorString()); return; } // get message count to show (no more than 3) and first visible message var rowCount = model.rowCount(); var messageCount = rowCount >= m_MaxMsgOnView ? m_MaxMsgOnView : rowCount; var startIndex = rowCount - messageCount; var xOffset = 0; var yOffset = m_MaxMsgOnView * m_MsgOffset; // iterate through messages to create for (var i = 0; i < messageCount; ++i) { // calculate message index to show var curIndex = startIndex + i; // out of bounds? if (curIndex >= rowCount) break; // add message item component to view component.createObject(frMessageView, {"id": "miMessageItem" + i, "m_Target": frMessageView, "x": xOffset, "y": yOffset, "width": frMessageView.width - 10, "height": 48, "m_MaxHeight": 48, "state": "EXTENDED", "m_Index": curIndex}); // calculate next offset xOffset += m_MsgOffset; yOffset -= m_MsgOffset; } } }
- Message item
import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.15 import QtQuick.Shapes 1.15 import QtGraphicalEffects 1.15 import QtQuick.Templates 2.15 as T /** * Message item *@author JMR */ T.Frame { // advanced properties property var m_Target: null property string m_ItemType: "WQTMessageItem" property string m_NotificationIcon: "qrc:/Resources/Images/main/icon_notification.svg" property string m_WarningIcon: "qrc:/Resources/Images/main/icon_warning.svg" property string m_ErrorIcon: "qrc:/Resources/Images/main/icon_error.svg" property string m_CloseBtnGlyph: "qrc:/Resources/Images/main/icon_small_close.svg" property int m_Index: 0 property int m_MaxHeight: 48 // state dependent properties property int m_Type property string m_BgColor property string m_OutlineColor property string m_TextColor property string m_Glyph /** * Equivalent to WQTMessageViewModel::IEMessageType */ enum IEMessageType { Notification, Warning, Error } // common properties id: frFrame height: 0 state: "COLLAPSED" /** * Item background */ background: Rectangle { // common properties anchors.fill: parent color: m_BgColor radius: 5 border.width: 1 border.color: m_OutlineColor } /** * Message icon */ Image { // common properties id: imIcon anchors.left: parent.left anchors.leftMargin: 12 anchors.top: parent.top anchors.topMargin: 10 width: 16 height: 16 source: m_Glyph sourceSize: Qt.size(width, height) fillMode: Image.PreserveAspectFit smooth: true } /** * Close button */ Button { // common properties id: btClose display: AbstractButton.IconOnly anchors.right: parent.right anchors.rightMargin: 6 anchors.top: parent.top anchors.topMargin: 7 width: 8 height: 8 background: null /** * Close button glyph */ Image { // common properties anchors.fill: parent source: m_CloseBtnGlyph sourceSize: Qt.size(width, height) fillMode: Image.PreserveAspectFit smooth: false } /** * Called when close button is clicked */ onClicked: { frFrame.state = "COLLAPSED"; if (m_Target.model) m_Target.model.removeMessageAt(m_Target.model.rowCount() - 1); } } /** * Message title */ Text { // common properties id: laTitle anchors.top: parent.top anchors.topMargin: 8 anchors.left: imIcon.right anchors.leftMargin: 7 anchors.right: btClose.left anchors.rightMargin: 7 font.bold: true font.pixelSize: 12 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter renderType: Text.NativeRendering text: m_Target.model ? m_Target.model.getMessageTitle(m_Index) : "" color: m_TextColor } /** * Message caption */ Text { // common properties id: laDescription anchors.top: laTitle.bottom anchors.topMargin: 0 anchors.left: imIcon.right anchors.leftMargin: 7 anchors.right: btClose.left anchors.rightMargin: 7 anchors.bottom: parent.bottom anchors.bottomMargin: 5 font.pixelSize: 14 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter renderType: Text.NativeRendering text: m_Target.model ? m_Target.model.getMessageCaption(m_Index) : "" wrapMode: Text.WordWrap elide: Text.ElideRight color: m_TextColor } /** * Component state array */ states: [ State { name: "EXTENDED" PropertyChanges {target: frFrame; height: m_MaxHeight} }, State { name: "COLLAPSED" PropertyChanges {target: frFrame; height: 0} } ] /** * Transitions between states */ transitions: [ Transition { from: "*"; to: "EXTENDED" NumberAnimation {property: "height"; easing.type: Easing.Linear; duration: 5000} }, Transition { from: "*"; to: "COLLAPSED" NumberAnimation {property: "height"; easing.type: Easing.Linear; duration: 5000} } ] }
The code is just a try to animate the message, but it not working. Can someone explain me how to correct it to reach my objective? I would be very greatful.
NOTE I shown my code as is, forgive me, it depends on many other classes in my project, which I cannot show here. I hope this will be enough to understand the situation and help me to solve it.
hi @jeanmilost there are many ways to do this in QML the easiest way currently, because you set your height explicitly to 0 and the target value is probably via a Property animation or a Behavior animation
something simple like
Behavior on height { NumberAnimation{duration: 200} }
could already be enough
-
hi @jeanmilost there are many ways to do this in QML the easiest way currently, because you set your height explicitly to 0 and the target value is probably via a Property animation or a Behavior animation
something simple like
Behavior on height { NumberAnimation{duration: 200} }
could already be enough
@J-Hilk said in How to implement an opening and closing animation on a frame?:
something simple like
Behavior on height { NumberAnimation{duration: 200} }Good suggestion, I modified my code, this works now for the opening animation.
However there is a remaining issue, which is how to delete my message item AFTER the closing animation ends. My first idea is to add a timer with the same interval as the animation duration, and which will send the close command after the elapsed time, but I think that solution isn't elegant.
Is there a better way to do that, e.g is there an emitted signal which I can listen on animation ends?
-
Maybe use the finished signal from NumberAnimation?
-
hi
@jeanmilost said in How to implement an opening and closing animation on a frame?:property var model: null
You can use a ListModel instead.
And ListView to show the messages