Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. How to implement an opening and closing animation on a frame?
Forum Updated to NodeBB v4.3 + New Features

How to implement an opening and closing animation on a frame?

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
5 Posts 4 Posters 605 Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • jeanmilostJ Offline
    jeanmilostJ Offline
    jeanmilost
    wrote on last edited by
    #1

    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:
    f90c39aa-b88c-4012-ae06-cb1557ebe9e1-image.png

    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:

    1. 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;
            }
        }
    }
    
    1. 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.

    J.HilkJ 1 Reply Last reply
    0
    • jeanmilostJ jeanmilost

      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:
      f90c39aa-b88c-4012-ae06-cb1557ebe9e1-image.png

      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:

      1. 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;
              }
          }
      }
      
      1. 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.

      J.HilkJ Online
      J.HilkJ Online
      J.Hilk
      Moderators
      wrote on last edited by
      #2

      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


      Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


      Q: What's that?
      A: It's blue light.
      Q: What does it do?
      A: It turns blue.

      jeanmilostJ 1 Reply Last reply
      4
      • J.HilkJ J.Hilk

        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

        jeanmilostJ Offline
        jeanmilostJ Offline
        jeanmilost
        wrote on last edited by
        #3

        @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?

        1 Reply Last reply
        0
        • fcarneyF Offline
          fcarneyF Offline
          fcarney
          wrote on last edited by
          #4

          Maybe use the finished signal from NumberAnimation?

          C++ is a perfectly valid school of magic.

          1 Reply Last reply
          2
          • ODБOïO Offline
            ODБOïO Offline
            ODБOï
            wrote on last edited by
            #5

            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

            1 Reply Last reply
            0

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved