From e4242c9ecb79c34f9f892703c5935bbce9f708a3 Mon Sep 17 00:00:00 2001 From: zhangkun Date: Mon, 10 Nov 2025 17:29:39 +0800 Subject: [PATCH] feat: enhance notification animations and visual effects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Removed custom state-based removal animations from NormalNotify and OverlapNotify components 2. Added sophisticated ListView transitions for add/remove operations with smooth animations 3. Implemented indexInGroup property to track notification position within groups 4. Added panelShown property to control animation activation only when panel is visible 5. Enhanced OverlapIndicator with staggered fade-in animations for better visual experience 6. Fixed text width calculation in NotifyItemContent to prevent layout issues during animations 7. Added proper z-index management for notification items to ensure correct layering Log: Improved notification panel animations with smoother transitions and better visual effects Influence: 1. Test notification panel opening and closing animations 2. Verify smooth add/remove transitions for individual notifications 3. Check group notification animations and overlap indicator effects 4. Test notification content display with various text lengths and icons 5. Verify animation performance with multiple notifications 6. Test notification interactions (remove, dismiss, action invoke) during animations feat: 增强通知动画和视觉效果 1. 从 NormalNotify 和 OverlapNotify 组件中移除基于自定义状态的删除动画 2. 为添加/删除操作添加复杂的 ListView 过渡动画,实现平滑的动画效果 3. 实现 indexInGroup 属性来跟踪通知在组内的位置 4. 添加 panelShown 属性控制动画仅在面板可见时激活 5. 增强 OverlapIndicator,添加错开的淡入动画以获得更好的视觉体验 6. 修复 NotifyItemContent 中的文本宽度计算,防止动画期间的布局问题 7. 添加适当的 z-index 管理确保通知项的正确层级 Log: 改进了通知面板动画,提供更平滑的过渡和更好的视觉效果 Influence: 1. 测试通知面板打开和关闭动画 2. 验证单个通知的平滑添加/删除过渡效果 3. 检查组通知动画和重叠指示器效果 4. 测试不同文本长度和图标的通知内容显示 5. 验证多通知情况下的动画性能 6. 测试动画期间的通知交互(删除、关闭、操作调用) pms: BUG-338883 --- panels/notification/center/NormalNotify.qml | 34 +---- panels/notification/center/NotifyCenter.qml | 1 + panels/notification/center/NotifyView.qml | 129 +++++++++++++++--- .../center/NotifyViewDelegate.qml | 4 + panels/notification/center/OverlapNotify.qml | 12 +- panels/notification/center/notifyitem.h | 4 + panels/notification/center/notifymodel.cpp | 6 +- panels/notification/center/notifymodel.h | 3 +- panels/notification/center/package/main.qml | 8 ++ panels/notification/plugin/NotifyItem.qml | 1 + .../notification/plugin/NotifyItemContent.qml | 11 +- .../notification/plugin/OverlapIndicator.qml | 29 ++++ 12 files changed, 180 insertions(+), 62 deletions(-) diff --git a/panels/notification/center/NormalNotify.qml b/panels/notification/center/NormalNotify.qml index e05a4c568..d2cfe8c1a 100644 --- a/panels/notification/center/NormalNotify.qml +++ b/panels/notification/center/NormalNotify.qml @@ -14,29 +14,6 @@ NotifyItem { implicitWidth: impl.implicitWidth implicitHeight: impl.implicitHeight - property var removedCallback - - states: [ - State { - name: "removing" - PropertyChanges { target: root; x: root.width; opacity: 0} - } - ] - - transitions: Transition { - to: "removing" - ParallelAnimation { - NumberAnimation { properties: "x"; duration: 300; easing.type: Easing.Linear } - NumberAnimation { properties: "opacity"; duration: 300; easing.type: Easing.Linear } - } - onRunningChanged: { - if (!running && root.removedCallback) { - root.removedCallback() - root.removedCallback = undefined - } - } - } - Control { id: impl anchors.fill: parent @@ -54,18 +31,13 @@ NotifyItem { strongInteractive: root.strongInteractive contentIcon: root.contentIcon contentRowCount: root.contentRowCount + indexInGroup: root.indexInGroup onRemove: function () { - root.removedCallback = function () { - root.remove() - } - root.state = "removing" + root.remove() } onDismiss: function () { - root.removedCallback = function () { - root.dismiss() - } - root.state = "removing" + root.dismiss() } onActionInvoked: function (actionId) { root.actionInvoked(actionId) diff --git a/panels/notification/center/NotifyCenter.qml b/panels/notification/center/NotifyCenter.qml index ee5f9221b..fb161d5dd 100644 --- a/panels/notification/center/NotifyCenter.qml +++ b/panels/notification/center/NotifyCenter.qml @@ -15,6 +15,7 @@ FocusScope { palette: DTK.palette property alias model: notifyModel + property alias viewPanelShown: view.viewPanelShown property int maxViewHeight: 400 property int stagingViewCount: 0 diff --git a/panels/notification/center/NotifyView.qml b/panels/notification/center/NotifyView.qml index 613740e57..3ec145556 100644 --- a/panels/notification/center/NotifyView.qml +++ b/panels/notification/center/NotifyView.qml @@ -6,6 +6,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import org.deepin.dtk 1.0 +import org.deepin.ds import org.deepin.ds.notificationcenter Control { @@ -13,6 +14,7 @@ Control { focus: true required property NotifyModel notifyModel + property alias viewPanelShown: view.panelShown readonly property real viewHeight: view.contentHeight readonly property int viewCount: view.count @@ -28,6 +30,7 @@ Control { // activeFocusOnTab: true ScrollBar.vertical: ScrollBar { } property int nextIndex: -1 + property bool panelShown: false onNextIndexChanged: { if (nextIndex >= 0 && count > 0) { @@ -52,26 +55,112 @@ Control { notifySetting.toggle(); } } - // remove: Transition { - // ParallelAnimation { - // NumberAnimation { properties: "y"; duration: 300 } - // } - // } - // add: Transition { - // ParallelAnimation { - // NumberAnimation { properties: "y"; duration: 300 } - // } - // } - // addDisplaced: Transition { - // ParallelAnimation { - // NumberAnimation { properties: "y"; duration: 300 } - // } - // } - // moveDisplaced: Transition { - // ParallelAnimation { - // NumberAnimation { properties: "y"; duration: 300 } - // } - // } + add: Transition { + id: addTrans + enabled: view.panelShown + + NumberAnimation { + properties: "y" + from: { + if (addTrans.ViewTransition.item.objectName.startsWith("overlap-")) { + // 24: group notify overlap height + return addTrans.ViewTransition.destination.y + view.spacing + 24 + } else if (addTrans.ViewTransition.item.indexInGroup === 0) { + return addTrans.ViewTransition.destination.y - view.spacing - 24 + } else { + return addTrans.ViewTransition.destination.y + } + } + duration: 400 + easing.type: Easing.OutQuart + } + + NumberAnimation { + property: "opacity"; + from: { + if (addTrans.ViewTransition.item.objectName.startsWith("overlap-")) { + return 0 + } else if (addTrans.ViewTransition.item.indexInGroup === 0) { + return 1 + } else { + return 0 + } + } + to: 1 + duration: { + if (addTrans.ViewTransition.item.objectName.startsWith("overlap-")) { + return 0 + } else if (addTrans.ViewTransition.item.indexInGroup === 0) { + return 100 + } else { + return 600 + } + } + easing.type: Easing.OutExpo + } + } + + remove: Transition { + id: removeTrans + NumberAnimation { + properties: "y" + to: { + if (removeTrans.ViewTransition.item.objectName.startsWith("overlap-")) { + return removeTrans.ViewTransition.destination.y + view.spacing + 24 + } else if (removeTrans.ViewTransition.item.indexInGroup === 1) { + return removeTrans.ViewTransition.destination.y - view.spacing - 24 + } else if (removeTrans.ViewTransition.item.indexInGroup === 2) { + return removeTrans.ViewTransition.destination.y - view.spacing - 24 + } else { + return removeTrans.ViewTransition.destination.y + } + } + duration: 400 + easing.type: Easing.OutQuart + } + + NumberAnimation { + property: "scale"; + from: 1 + to: { + if (removeTrans.ViewTransition.item.indexInGroup === 1) { + return 0.9 + } else if (removeTrans.ViewTransition.item.indexInGroup === 2) { + return 0.9 + } else { + return 1 + } + } + duration: 600 + easing.type: Easing.OutExpo + } + + NumberAnimation { + property: "opacity"; + to: 0 + duration: { + if (removeTrans.ViewTransition.item.indexInGroup === 0) { + return 100 + } else if (removeTrans.ViewTransition.item.objectName.startsWith("overlap-")) { + return 100 + } else { + return 600 + } + } + easing.type: Easing.OutExpo + } + } + + addDisplaced: Transition { + id: addDisplacedTrans + NumberAnimation { property: "y"; duration: 400; easing.type: Easing.OutQuart} + NumberAnimation { property: "opacity"; to: 1.0; duration: 600; easing.type: Easing.OutExpo} + } + + removeDisplaced: Transition { + id: removeDisplacedTrans + NumberAnimation { properties: "y"; duration: 400; easing.type: Easing.OutQuart} + } } background: BoundingRectangle {} diff --git a/panels/notification/center/NotifyViewDelegate.qml b/panels/notification/center/NotifyViewDelegate.qml index 182b41027..fe69b42d0 100644 --- a/panels/notification/center/NotifyViewDelegate.qml +++ b/panels/notification/center/NotifyViewDelegate.qml @@ -26,6 +26,7 @@ DelegateChooser { width: NotifyStyle.contentItem.width appName: model.appName activeFocusOnTab: true + z: index Loader { anchors.fill: parent @@ -63,6 +64,7 @@ DelegateChooser { objectName: "normal-" + model.appName width: NotifyStyle.contentItem.width activeFocusOnTab: true + z: index appName: model.appName iconName: model.iconName @@ -74,6 +76,7 @@ DelegateChooser { contentIcon: model.contentIcon contentRowCount: model.contentRowCount defaultAction: model.defaultAction + indexInGroup: model.indexInGroup Loader { anchors.fill: parent @@ -126,6 +129,7 @@ DelegateChooser { objectName: "overlap-" + model.appName width: NotifyStyle.contentItem.width activeFocusOnTab: true + z: index count: model.overlapCount appName: model.appName diff --git a/panels/notification/center/OverlapNotify.qml b/panels/notification/center/OverlapNotify.qml index d57693af5..de0806f7d 100644 --- a/panels/notification/center/OverlapNotify.qml +++ b/panels/notification/center/OverlapNotify.qml @@ -65,17 +65,12 @@ NotifyItem { contentIcon: root.contentIcon contentRowCount: root.contentRowCount enableDismissed: root.enableDismissed + indexInGroup: root.indexInGroup onRemove: function () { - root.removedCallback = function () { - root.remove() - } - root.state = "removing" + root.remove() } onDismiss: function () { - root.removedCallback = function () { - root.dismiss() - } - root.state = "removing" + root.dismiss() } onActionInvoked: function (actionId) { root.actionInvoked(actionId) @@ -84,6 +79,7 @@ NotifyItem { OverlapIndicator { id: indicator + enableAnimation: root.ListView.view.panelShown anchors { bottom: parent.bottom left: parent.left diff --git a/panels/notification/center/notifyitem.h b/panels/notification/center/notifyitem.h index c97c27d0d..32c866a0e 100644 --- a/panels/notification/center/notifyitem.h +++ b/panels/notification/center/notifyitem.h @@ -41,6 +41,9 @@ class AppNotifyItem : public QObject void updateActions(); void updateStrongInteractive(); + int indexInGroup() const { return m_indexInGroup; } + void setIndexInGroup(int index) { m_indexInGroup = index; } + void refresh(); bool pinned() const; @@ -54,6 +57,7 @@ class AppNotifyItem : public QObject NotifyEntity m_entity; bool m_pinned = false; bool m_strongInteractive = false; + int m_indexInGroup = -1; }; class BubbleNotifyItem : public AppNotifyItem diff --git a/panels/notification/center/notifymodel.cpp b/panels/notification/center/notifymodel.cpp index f721e4e50..5ea4ea34a 100644 --- a/panels/notification/center/notifymodel.cpp +++ b/panels/notification/center/notifymodel.cpp @@ -77,6 +77,7 @@ void NotifyModel::expandApp(int row) for (int i = 0; i < notifies.size(); i++) { auto item = notifies[i]; m_appNotifies.insert(start + i, item); + item->setIndexInGroup(i); } endInsertRows(); } @@ -724,6 +725,8 @@ QVariant NotifyModel::data(const QModelIndex &index, int role) const } } else if (role == NotifyRole::NotifyContentRowCount) { return NotifySetting::instance()->contentRowCount(); + } else if (role == NotifyRole::NotifyIndexInGroup) { + return notify->indexInGroup(); } return QVariant::fromValue(notify); } @@ -778,7 +781,8 @@ QHash NotifyModel::roleNames() const {NotifyStrongInteractive, "strongInteractive"}, {NotifyContentIcon, "contentIcon"}, {NotifyOverlapCount, "overlapCount"}, - {NotifyContentRowCount, "contentRowCount"}}; + {NotifyContentRowCount, "contentRowCount"}, + {NotifyIndexInGroup, "indexInGroup"}}; return roles; } diff --git a/panels/notification/center/notifymodel.h b/panels/notification/center/notifymodel.h index 0127a9484..6e2960cce 100644 --- a/panels/notification/center/notifymodel.h +++ b/panels/notification/center/notifymodel.h @@ -37,7 +37,8 @@ class NotifyModel : public QAbstractListModel NotifyStrongInteractive, NotifyContentIcon, NotifyOverlapCount, - NotifyContentRowCount + NotifyContentRowCount, + NotifyIndexInGroup }; NotifyModel(QObject *parent = nullptr); diff --git a/panels/notification/center/package/main.qml b/panels/notification/center/package/main.qml index adb63f6a6..c63f4790c 100644 --- a/panels/notification/center/package/main.qml +++ b/panels/notification/center/package/main.qml @@ -106,8 +106,12 @@ Window { function onVisibleChanged() { if (Panel.visible) { notifyStaging.model.open() + DS.singleShot(100, function() { + notifyCenter.viewPanelShown = true + }) } else { notifyStaging.model.close() + notifyCenter.viewPanelShown = false } } } @@ -127,8 +131,12 @@ Window { function onVisibleChanged() { if (Panel.visible) { notifyCenter.model.open() + DS.singleShot(100, function() { + notifyCenter.viewPanelShown = true + }) } else { notifyCenter.model.close() + notifyCenter.viewPanelShown = false } } } diff --git a/panels/notification/plugin/NotifyItem.qml b/panels/notification/plugin/NotifyItem.qml index 7bbc5ca4e..189e2650b 100644 --- a/panels/notification/plugin/NotifyItem.qml +++ b/panels/notification/plugin/NotifyItem.qml @@ -32,6 +32,7 @@ FocusScope { property bool strongInteractive: false property string contentIcon: "deepin-editor" property int contentRowCount: 6 + property int indexInGroup: -1 signal remove() signal dismiss() diff --git a/panels/notification/plugin/NotifyItemContent.qml b/panels/notification/plugin/NotifyItemContent.qml index 1435af1a1..e9f7fb4aa 100644 --- a/panels/notification/plugin/NotifyItemContent.qml +++ b/panels/notification/plugin/NotifyItemContent.qml @@ -133,6 +133,7 @@ NotifyItem { } DciIcon { + id: appIcon name: root.iconName !== "" ? root.iconName : "application-x-desktop" sourceSize: Qt.size(24, 24) Layout.alignment: Qt.AlignLeft | Qt.AlignTop @@ -143,6 +144,7 @@ NotifyItem { } ColumnLayout { + id: contentLayout spacing: 0 Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.rightMargin: 10 @@ -200,12 +202,18 @@ NotifyItem { } RowLayout { + id: bodyRow Layout.fillWidth: true Layout.alignment: Qt.AlignLeft | Qt.AlignTop Text { id: bodyText Layout.alignment: Qt.AlignLeft | Qt.AlignTop - Layout.fillWidth: true + // text 宽度若让Layout通过implicitWidth计算会导致ListView的add动画出现位置错误,故这里手动计算Text的宽度 + Layout.preferredWidth: NotifyStyle.contentItem.width - appIcon.width + - appIcon.Layout.leftMargin - appIcon.Layout.rightMargin + - contentLayout.Layout.rightMargin - contentLayout.Layout.leftMargin + - (contentIconLoader.active ? (contentIconLoader.width + 1) : 0) + - bodyRow.spacing * bodyRow.children.length - 1 visible: text !== "" text: root.content maximumLineCount: root.contentRowCount @@ -228,6 +236,7 @@ NotifyItem { } Loader { + id: contentIconLoader Layout.maximumWidth: 106 Layout.maximumHeight: 106 Layout.minimumWidth: 16 diff --git a/panels/notification/plugin/OverlapIndicator.qml b/panels/notification/plugin/OverlapIndicator.qml index 988b13536..fb96afafd 100644 --- a/panels/notification/plugin/OverlapIndicator.qml +++ b/panels/notification/plugin/OverlapIndicator.qml @@ -14,6 +14,7 @@ Item { property int radius: 12 property int overlapHeight: 8 property bool revert: false + property bool enableAnimation: false implicitHeight: layout.height implicitWidth: 360 @@ -40,6 +41,34 @@ Item { topMargin: revert ? undefined : -(height - overlapHeight) bottomMargin: revert ? -(height - overlapHeight) : undefined } + opacity: 0 + + Component.onCompleted: { + if (root.enableAnimation) { + fadeInAnimation.start() + } else { + opacity = 1 + } + } + + SequentialAnimation { + id: fadeInAnimation + + PauseAnimation { + duration: realIndex * 100 + } + + ParallelAnimation { + NumberAnimation { + target: background + property: "opacity" + from: 0 + to: 1 + duration: 300 + easing.type: Easing.OutQuad + } + } + } } } }