Skip to content

Conversation

@qxp930712
Copy link

@qxp930712 qxp930712 commented Jan 31, 2026

  1. Introduce a pending queue mechanism in BubbleModel to serialize bubble addition
  2. Add timer to process next bubble insertion after animation completes
  3. Replace XAnimator with NumberAnimation in QML for better control
  4. Add addDisplaced transition to smooth out existing bubble movement
  5. Prevent visual glitches when receiving rapid notifications

Log: Fixed overlapping animations when multiple notifications arrive quickly

Influence:

  1. Send multiple notifications rapidly to test queue mechanism
  2. Verify bubble animations play sequentially without overlapping
  3. Ensure existing bubbles move smoothly when new ones are added
  4. Verify behavior when bubble count exceeds maximum limit

pms: BUG-320719

Summary by Sourcery

Serialize notification bubble insertions to avoid overlapping animations and improve visual behavior when multiple notifications arrive quickly.

Bug Fixes:

  • Prevent overlapping and conflicting bubble animations when multiple notifications are added in rapid succession.

Enhancements:

  • Introduce a pending queue and timed processing for bubble insertions in BubbleModel to control animation sequencing.
  • Switch bubble add transitions to NumberAnimation and add displaced-item animations for smoother bubble movement in the notification list.

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 31, 2026

Reviewer's Guide

Implements a queued bubble insertion mechanism in BubbleModel synchronized with QML animation timing, and updates the QML ListView transitions to use NumberAnimation and addDisplaced for smoother, non-overlapping notification bubble animations.

Sequence diagram for queued bubble insertion and animation

sequenceDiagram
    actor User
    participant App as AppNotificationSource
    participant BubbleModel
    participant QML as NotificationListView

    User ->> App: Perform action that triggers notifications
    App ->> BubbleModel: push(bubble1)
    activate BubbleModel
    BubbleModel ->> BubbleModel: append bubble1 to m_pendingBubbles
    alt not m_isAddingBubble
        BubbleModel ->> BubbleModel: processPendingBubbles()
    end

    BubbleModel ->> BubbleModel: takeFirst() -> bubble1
    BubbleModel ->> QML: insert bubble1 into model
    QML ->> QML: run add Transition NumberAnimation(x)
    QML ->> QML: run addDisplaced Transition on existing items
    BubbleModel ->> BubbleModel: start m_addBubbleTimer(BubbleAnimationDuration)
    deactivate BubbleModel

    App ->> BubbleModel: push(bubble2)
    activate BubbleModel
    BubbleModel ->> BubbleModel: append bubble2 to m_pendingBubbles
    note over BubbleModel: m_isAddingBubble is true, do not process immediately
    deactivate BubbleModel

    BubbleModel ->> BubbleModel: m_addBubbleTimer.timeout()
    activate BubbleModel
    BubbleModel ->> BubbleModel: processPendingBubbles()
    BubbleModel ->> BubbleModel: takeFirst() -> bubble2
    BubbleModel ->> QML: insert bubble2 into model
    QML ->> QML: run add Transition for bubble2
    QML ->> QML: run addDisplaced Transition on existing items
    alt m_pendingBubbles not empty
        BubbleModel ->> BubbleModel: start m_addBubbleTimer(BubbleAnimationDuration)
    else m_pendingBubbles empty
        BubbleModel ->> BubbleModel: m_isAddingBubble = false
    end
    deactivate BubbleModel
Loading

Updated class diagram for BubbleModel notification queue

classDiagram
    class QAbstractListModel

    class BubbleItem

    class NotifySetting {
        +static NotifySetting* instance()
        +signals contentRowCountChanged(int rowCount)
        +signals bubbleCountChanged(int count)
    }

    class QTimer {
        +setInterval(int msec) void
        +setSingleShot(bool singleShot) void
        +start() void
        +start(int msec) void
        +timeout() void
    }

    class BubbleModel {
        +BubbleModel(QObject *parent)
        +~BubbleModel()
        +push(BubbleItem *bubble) void
        +isReplaceBubble(const BubbleItem *bubble) const bool
        +updateLevel() void
        +updateBubbleTimeTip() void
        +updateContentRowCount(int rowCount) void
        +processPendingBubbles() void
        -QTimer *m_updateTimeTipTimer
        -QTimer *m_addBubbleTimer
        -QList<BubbleItem *> m_pendingBubbles
        -QList<qint64> m_delayBubbles
        -qint64 m_delayRemovedBubble
        -bool m_isAddingBubble
        -const int DelayRemovBubbleTime
        -const int BubbleAnimationDuration
    }

    BubbleModel --|> QAbstractListModel
    BubbleModel --> QTimer : uses m_updateTimeTipTimer
    BubbleModel --> QTimer : uses m_addBubbleTimer
    BubbleModel --> BubbleItem : manages
    BubbleModel --> NotifySetting : observes signals
Loading

File-Level Changes

Change Details Files
Serialize bubble insertions via a pending queue and timer in BubbleModel to align model updates with animation duration.
  • Instantiate and configure m_addBubbleTimer as a single-shot QTimer bound to processPendingBubbles().
  • Modify push() to enqueue new BubbleItem instances in m_pendingBubbles and trigger processing only when not already adding a bubble.
  • Introduce processPendingBubbles() to dequeue items, perform the existing insert/remove logic, and schedule the next insertion based on BubbleAnimationDuration.
  • Track insertion state with m_isAddingBubble and clear it when the queue is empty.
  • Add BubbleModel members for the pending queue, insertion timer, and BubbleAnimationDuration constant, ensuring consistency with the QML animation length.
panels/notification/bubble/bubblemodel.cpp
panels/notification/bubble/bubblemodel.h
Refine QML ListView animations to avoid overlapping and ensure smooth movement of existing bubbles.
  • Set cacheBuffer to 0 on the ListView to avoid layout caching during animations.
  • Replace the add transition's XAnimator with a NumberAnimation targeting the x property, explicitly animating from the item width to 0 over 600ms with easing.
  • Introduce an addDisplaced transition that animates x and y properties of displaced items with a 600ms easing animation to smooth repositioning when bubbles are added.
panels/notification/bubble/package/main.qml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The queueing logic in BubbleModel relies on a hard-coded BubbleAnimationDuration that must stay in sync with the QML duration; consider centralizing this duration (e.g., a shared constant or configuration) to avoid subtle desynchronization when one side changes.
  • Storing raw BubbleItem* in m_pendingBubbles can make ownership and lifetime unclear, especially if items are destroyed or replaced before dequeuing; consider using smart pointers or clearer ownership guarantees to avoid dangling pointers.
  • Using m_addBubbleTimer to approximate animation completion may be brittle if animation timings or easing change dynamically; if possible, consider driving processPendingBubbles from an animation-completed signal or a QML-to-C++ callback instead of a fixed timer.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The queueing logic in `BubbleModel` relies on a hard-coded `BubbleAnimationDuration` that must stay in sync with the QML duration; consider centralizing this duration (e.g., a shared constant or configuration) to avoid subtle desynchronization when one side changes.
- Storing raw `BubbleItem*` in `m_pendingBubbles` can make ownership and lifetime unclear, especially if items are destroyed or replaced before dequeuing; consider using smart pointers or clearer ownership guarantees to avoid dangling pointers.
- Using `m_addBubbleTimer` to approximate animation completion may be brittle if animation timings or easing change dynamically; if possible, consider driving `processPendingBubbles` from an animation-completed signal or a QML-to-C++ callback instead of a fixed timer.

## Individual Comments

### Comment 1
<location> `panels/notification/bubble/bubblemodel.cpp:54` </location>
<code_context>
         m_updateTimeTipTimer->start();
     }

+    // Add bubble to pending queue to prevent animation conflicts
+    m_pendingBubbles.append(bubble);
+    
</code_context>

<issue_to_address>
**issue (complexity):** Consider removing the separate m_isAddingBubble flag and deriving the insertion state solely from the pending queue and timer activity to simplify the bubble insertion logic.

You can drop `m_isAddingBubble` entirely and let the queue + timer state be the sole source of truth. That removes the implicit state machine and duplicated flag management while keeping the serialized insertion behavior.

Concretely:

- Drive everything off `m_pendingBubbles` and `m_addBubbleTimer->isActive()`
- Let `processPendingBubbles()` always handle “when to start the timer”
- Keep `push()` simple: just enqueue and, if idle, process once

Example refactor:

```cpp
void BubbleModel::push(BubbleItem *bubble)
{
    if (!m_updateTimeTipTimer->isActive()) {
        m_updateTimeTipTimer->start();
    }

    // Enqueue bubble
    m_pendingBubbles.append(bubble);

    // If no timer is running, we're idle: insert first bubble immediately
    if (!m_addBubbleTimer->isActive()) {
        processPendingBubbles();
    }
}

void BubbleModel::processPendingBubbles()
{
    if (m_pendingBubbles.isEmpty()) {
        // Nothing to do; ensure timer is stopped
        m_addBubbleTimer->stop();
        return;
    }

    BubbleItem *bubble = m_pendingBubbles.takeFirst();

    bool more = displayRowCount() >= BubbleMaxCount;
    if (more) {
        beginRemoveRows(QModelIndex(), BubbleMaxCount - 1, BubbleMaxCount - 1);
        endRemoveRows();
    }

    beginInsertRows(QModelIndex(), 0, 0);
    m_bubbles.prepend(bubble);
    endInsertRows();

    updateLevel();

    // If there are more pending bubbles, schedule the next one
    if (!m_pendingBubbles.isEmpty()) {
        m_addBubbleTimer->start(BubbleAnimationDuration);
    } else {
        m_addBubbleTimer->stop();
    }
}
```

This:

- Removes `m_isAddingBubble` and its duplicated set/clear logic
- Makes the protocol explicit: `push()` only enqueues and kicks off processing if idle; `processPendingBubbles()` owns the timing and queue advancement
- Keeps the existing behavior of spacing inserts by `BubbleAnimationDuration` without changing functionality.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

m_updateTimeTipTimer->start();
}

// Add bubble to pending queue to prevent animation conflicts
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider removing the separate m_isAddingBubble flag and deriving the insertion state solely from the pending queue and timer activity to simplify the bubble insertion logic.

You can drop m_isAddingBubble entirely and let the queue + timer state be the sole source of truth. That removes the implicit state machine and duplicated flag management while keeping the serialized insertion behavior.

Concretely:

  • Drive everything off m_pendingBubbles and m_addBubbleTimer->isActive()
  • Let processPendingBubbles() always handle “when to start the timer”
  • Keep push() simple: just enqueue and, if idle, process once

Example refactor:

void BubbleModel::push(BubbleItem *bubble)
{
    if (!m_updateTimeTipTimer->isActive()) {
        m_updateTimeTipTimer->start();
    }

    // Enqueue bubble
    m_pendingBubbles.append(bubble);

    // If no timer is running, we're idle: insert first bubble immediately
    if (!m_addBubbleTimer->isActive()) {
        processPendingBubbles();
    }
}

void BubbleModel::processPendingBubbles()
{
    if (m_pendingBubbles.isEmpty()) {
        // Nothing to do; ensure timer is stopped
        m_addBubbleTimer->stop();
        return;
    }

    BubbleItem *bubble = m_pendingBubbles.takeFirst();

    bool more = displayRowCount() >= BubbleMaxCount;
    if (more) {
        beginRemoveRows(QModelIndex(), BubbleMaxCount - 1, BubbleMaxCount - 1);
        endRemoveRows();
    }

    beginInsertRows(QModelIndex(), 0, 0);
    m_bubbles.prepend(bubble);
    endInsertRows();

    updateLevel();

    // If there are more pending bubbles, schedule the next one
    if (!m_pendingBubbles.isEmpty()) {
        m_addBubbleTimer->start(BubbleAnimationDuration);
    } else {
        m_addBubbleTimer->stop();
    }
}

This:

  • Removes m_isAddingBubble and its duplicated set/clear logic
  • Makes the protocol explicit: push() only enqueues and kicks off processing if idle; processPendingBubbles() owns the timing and queue advancement
  • Keeps the existing behavior of spacing inserts by BubbleAnimationDuration without changing functionality.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request introduces a queueing mechanism to prevent overlapping bubble insertion animations when multiple notifications arrive in rapid succession. The changes serialize bubble additions and coordinate C++ model updates with QML animations.

Changes:

  • Add pending queue in BubbleModel to serialize bubble insertions with timer-based coordination
  • Replace XAnimator with NumberAnimation for explicit animation control
  • Add displaced item transitions for smoother bubble movement

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
panels/notification/bubble/bubblemodel.h Add pending bubble queue, timer, and state flag for animation coordination
panels/notification/bubble/bubblemodel.cpp Implement queue mechanism with processPendingBubbles() to serialize additions
panels/notification/bubble/package/main.qml Switch to NumberAnimation, add displaced transitions, set cacheBuffer to 0
Comments suppressed due to low confidence (2)

panels/notification/bubble/bubblemodel.cpp:77

  • Memory leak: When the bubble count exceeds BubbleMaxCount, the code removes a bubble from the model without deleting the BubbleItem object. The removed bubble at index BubbleMaxCount - 1 needs to be retrieved from m_bubbles and deleted before calling beginRemoveRows.

The pattern used elsewhere in the codebase (see remove() at line 128-142) shows that you should call takeAt() to get the bubble pointer and then call deleteLater() on it. Without this, the BubbleItem objects that are removed when at max capacity will leak memory.

    bool more = displayRowCount() >= BubbleMaxCount;
    if (more) {
        beginRemoveRows(QModelIndex(), BubbleMaxCount - 1, BubbleMaxCount - 1);
        endRemoveRows();
    }

panels/notification/bubble/bubblemodel.cpp:46

  • Memory leak on cleanup: The destructor and clear() methods don't clean up pending bubbles in m_pendingBubbles. If the BubbleModel is destroyed or cleared while there are bubbles waiting in the pending queue, those BubbleItem objects will leak memory since they're not in m_bubbles yet.

You should add qDeleteAll(m_pendingBubbles) and m_pendingBubbles.clear() to both the destructor (after line 44) and the clear() method (after line 117).

BubbleModel::~BubbleModel()
{
    qDeleteAll(m_bubbles);
    m_bubbles.clear();
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

interactive: false
verticalLayoutDirection: ListView.BottomToTop

// Disable automatic layout updates during animations
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misleading comment: The comment states "Disable automatic layout updates during animations" but cacheBuffer controls the size of the buffer zone outside the visible area where delegates are instantiated, not layout updates during animations. Setting cacheBuffer to 0 means delegates are only created for visible items, which can cause performance issues during scrolling.

This setting doesn't prevent layout updates during animations. If you're experiencing layout issues during animations, consider using ListView.delayRemove or adjusting the displacement property instead.

Suggested change
// Disable automatic layout updates during animations
// Do not cache offscreen delegates (cacheBuffer controls offscreen instantiation, not layout updates/animations)

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

id: addTrans
XAnimator { target: addTrans.ViewTransition.item; from: addTrans.ViewTransition.item.width; duration: 600; easing.type: Easing.OutExpo }
// Use NumberAnimation with explicit to value
NumberAnimation {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里用XAnimator是为了规避动画卡顿的问题的,尤其是在录屏数据较大时的通知容易复现,XAnimator在子线程中执行,而NumberAnimation在主线程中执行,
看要不要保持XAnimator,


// Setup timer for processing pending bubbles
m_addBubbleTimer->setSingleShot(true);
connect(m_addBubbleTimer, &QTimer::timeout, this, &BubbleModel::processPendingBubbles);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

通知的超时是在这里实现的,

void NotificationManager::onHandingPendingEntities()

如果这里再加动画显示,可能出现另一个问题,这个通知的数据超时已经删掉了,但通知横幅还未显示,导致显示的通知横幅操作失败,

18202781743
18202781743 previously approved these changes Jan 31, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 73 to 93
// 在动画开始前,强制完成所有现有弹框的动画
ScriptAction {
script: {
// 遍历所有弹框,将它们的x强制设置为0
for (let i = 0; i < bubbleView.count; i++) {
let item = bubbleView.itemAtIndex(i)
if (item) {
if (item !== addTrans.ViewTransition.item) {
item.x = 0
}
}
}
}
}
XAnimator {
target: addTrans.ViewTransition.item
from: addTrans.ViewTransition.item.width
to: 0
duration: 600
easing.type: Easing.OutExpo
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description claims "Replace XAnimator with NumberAnimation in QML for better control", but this change still uses XAnimator. If the goal is to use NumberAnimation for better control as stated, the XAnimator should be replaced. Additionally, the PR description mentions adding an "addDisplaced transition" which is not present in this diff - a displaced transition would handle the movement of existing items when new items are added.

Copilot uses AI. Check for mistakes.
Comment on lines 73 to 93
// 在动画开始前,强制完成所有现有弹框的动画
ScriptAction {
script: {
// 遍历所有弹框,将它们的x强制设置为0
for (let i = 0; i < bubbleView.count; i++) {
let item = bubbleView.itemAtIndex(i)
if (item) {
if (item !== addTrans.ViewTransition.item) {
item.x = 0
}
}
}
}
}
XAnimator {
target: addTrans.ViewTransition.item
from: addTrans.ViewTransition.item.width
to: 0
duration: 600
easing.type: Easing.OutExpo
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description states "Introduce a pending queue mechanism in BubbleModel to serialize bubble addition" and "Add timer to process next bubble insertion after animation completes", but there are no changes to BubbleModel in this PR. The BubbleModel class still uses the same immediate insertion logic in the push() method without any queue or timer mechanism. This means rapid notifications will still trigger multiple simultaneous insertions, which contradicts the stated goal of serializing bubble additions.

Copilot uses AI. Check for mistakes.
Comment on lines 74 to 85
ScriptAction {
script: {
// 遍历所有弹框,将它们的x强制设置为0
for (let i = 0; i < bubbleView.count; i++) {
let item = bubbleView.itemAtIndex(i)
if (item) {
if (item !== addTrans.ViewTransition.item) {
item.x = 0
}
}
}
}
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ScriptAction iterates through all bubbles on every insertion to force their x position to 0. This approach has performance implications as it creates O(n) operations for each insertion. More importantly, this forceful snapping can create visual discontinuities if animations are mid-flight. A better approach would be to either complete the animations gracefully or use a displaced transition to handle the movement of existing items, as mentioned in the PR description.

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +92
XAnimator {
target: addTrans.ViewTransition.item
from: addTrans.ViewTransition.item.width
to: 0
duration: 600
easing.type: Easing.OutExpo
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explicit to: 0 parameter was added to the XAnimator, which is good for clarity. However, according to the PR description, XAnimator should be replaced with NumberAnimation (as seen in panels/notification/center/NotifyView.qml:127). NumberAnimation provides better control for synchronization and allows the animation to be properly interrupted or completed, which is essential for the queue mechanism mentioned in the PR description.

Copilot uses AI. Check for mistakes.
@18202781743
Copy link
Contributor

代码逻辑改了,提交信息重新生成下,

1. Modified the bubble addition transition logic in panels/notification/bubble/package/main.qml.
2. Updated the logic to only handle the previous bubble at index count - 2.
3. Improved pre-animation processing performance, reducing complexity from O(N) to O(1).
4. Ensured that when a new bubble appears, only the position of the immediately preceding bubble needs to be fixed to meet layout requirements.

Log: Fixed overlapping animations when multiple notifications arrive rapidly.

Influence:
1. Send multiple notifications in quick succession to test the queuing mechanism.
2. Verify that bubble animations play sequentially without overlapping.
3. Ensure existing bubbles move smoothly when new ones are added.
4. Verify correct behavior when the number of bubbles exceeds the maximum limit.

pms: BUG-320719
@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743, qxp930712

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@deepin-ci-robot
Copy link

deepin pr auto review

这段代码修改主要是针对通知气泡列表的动画效果进行了优化,目的是在新的通知气泡出现时,强制完成上一个气泡的入场动画,以避免动画重叠导致的视觉混乱。

以下是对这段代码的详细审查和改进建议:

1. 语法逻辑审查

  • 逻辑正确性:代码逻辑基本正确。它利用 ScriptActionadd 过渡动画开始前执行一段脚本。脚本判断 bubbleView.count > 1,确保列表中存在前一个气泡,然后获取该气泡并强制将其 x 属性设为 0,从而结束其滑入动画。
  • ViewTransition 的使用addTrans.ViewTransition.item 的用法是标准的 QML 语法,用于引用当前正在执行过渡动画的目标项。
  • XAnimator 显式设置 to 属性:原代码隐式地将 to 值设为当前属性值(即 0),修改后的代码显式添加了 to: 0。这是一个好的实践,提高了代码的可读性和明确性。

2. 代码质量审查

  • 可读性:代码注释清晰,解释了为什么要这样做("Before starting...")以及具体做了什么("Only handle...")。
  • 维护性:逻辑封装在 ScriptAction 中,与动画定义紧密结合,便于维护。

3. 代码性能审查

  • 潜在的性能风险
    • ScriptAction 中调用 bubbleView.itemAtIndex(index) 可能会有性能开销。虽然这里只查询了一个特定的索引,但如果 ListView 非常大且包含复杂的委托项,频繁的索引查询(尤其是在动画帧中)可能会引起卡顿。不过,鉴于这是通知气泡列表,数量通常很少,这个风险在当前场景下较低。
    • 建议:目前的实现是高效的,因为只查找了前一个元素,没有遍历整个列表。

4. 代码安全审查

  • 空值检查:代码中包含了 if (prevItem) 的检查,这是非常好的做法,防止了在异步加载或某些边缘情况下 itemAtIndex 返回 null 导致程序崩溃。
  • 边界检查if (bubbleView.count > 1) 确保了不会访问无效的索引 -10(取决于逻辑,这里逻辑是正确的,访问的是 count - 2)。

5. 改进意见

虽然代码本身已经很不错,但为了追求极致的健壮性和动画体验,可以考虑以下改进:

建议 1:使用 target 绑定优化(可选)

目前的写法是通用的,但如果 Bubble 组件内部有一个专门的属性或方法来控制动画状态(例如 forceCompleteAnimation()),在 QML 层直接修改属性 x 可能会破坏组件的封装性。

  • 改进方案:如果可能,建议在 Bubble 组件中暴露一个函数或信号,例如 cancelAnimation(),然后调用 prevItem.cancelAnimation(),而不是直接操作 prevItem.x。这样逻辑更清晰,也更符合组件化开发思想。

建议 2:动画状态的平滑性

直接设置 prevItem.x = 0 会导致前一个气泡瞬间跳到最终位置。虽然这解决了重叠问题,但在视觉上可能会略显突兀。

  • 改进方案:如果希望视觉更平滑,可以考虑缩短前一个气泡的动画时长,而不是直接设为 0。但这会增加复杂度,需要获取前一个气泡的动画对象引用。在通知气泡这种需要快速响应的场景下,直接归位通常是可接受的。

建议 3:关于 itemAtIndex 的说明

ListView.itemAtIndex() 在某些 Qt 版本中,如果对应的项不在缓存区域内,可能会返回 null。虽然通知气泡通常都在屏幕上,但为了代码的绝对安全,目前的 if (prevItem) 检查已经足够。

总结

这段代码修改是安全且有效的。它成功解决了多个通知连续弹出时动画重叠的问题,且没有引入明显的性能或安全漏洞。代码注释详尽,逻辑清晰。

最终建议采纳的代码(保持原样即可,仅补充注释说明):

add: Transition {
    id: addTrans
    // 在新动画开始前,强制完成上一个通知气泡的动画
    ScriptAction {
        script: {
            // 仅处理前一个通知气泡 (索引为 count - 2),无需遍历所有气泡
            // count > 1 确保至少存在一个前驱项
            if (bubbleView.count > 1) {
                let prevItem = bubbleView.itemAtIndex(bubbleView.count - 2)
                // 空值检查,防止因异步加载或布局未就绪导致的崩溃
                if (prevItem) {
                    // 直接设置 x 为 0 以强制完成动画
                    // 注意:这会打断前一个气泡的缓动效果,使其瞬间归位
                    prevItem.x = 0
                }
            }
        }
    }
    XAnimator { 
        target: addTrans.ViewTransition.item
        from: addTrans.ViewTransition.item.width
        to: 0 // 显式指定目标位置,提高可读性
        duration: 600
        easing.type: Easing.OutExpo
    }
}

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +86 to +92
XAnimator {
target: addTrans.ViewTransition.item
from: addTrans.ViewTransition.item.width
to: 0
duration: 600
easing.type: Easing.OutExpo
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description states "Replace XAnimator with NumberAnimation in QML for better control" but the code still uses XAnimator. This is a discrepancy between the PR description and the implementation. If NumberAnimation is needed for better control, the XAnimator should be replaced as described.

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +86
// Only handle the previous notification bubble (at index count - 1); no need to iterate through all of them
if (bubbleView.count > 1) {
let prevItem = bubbleView.itemAtIndex(bubbleView.count - 2)
if (prevItem) {
// Directly set x to 0 to forcibly complete the animation
prevItem.x = 0
}
}
}
}
XAnimator {
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forcibly setting prevItem.x to 0 while an XAnimator is running may not properly stop the animator. XAnimator runs on the render thread and directly manipulating the property it's animating can lead to race conditions and unpredictable behavior. The animator may continue running or may snap back to the animated value. To properly stop an animation, you should either use the Animation.stop() method or ensure the animator completes naturally. Consider using NumberAnimation instead of XAnimator if you need to synchronously control the animated property, or store a reference to the running animation so it can be properly stopped.

Suggested change
// Only handle the previous notification bubble (at index count - 1); no need to iterate through all of them
if (bubbleView.count > 1) {
let prevItem = bubbleView.itemAtIndex(bubbleView.count - 2)
if (prevItem) {
// Directly set x to 0 to forcibly complete the animation
prevItem.x = 0
}
}
}
}
XAnimator {
// If a previous add animation is still running, complete it cleanly
if (addXAnimator.running) {
addXAnimator.complete();
}
}
}
XAnimator {
id: addXAnimator

Copilot uses AI. Check for mistakes.
// Before starting the new animation, forcibly complete the previous notification bubble's animation
ScriptAction {
script: {
// Only handle the previous notification bubble (at index count - 1); no need to iterate through all of them
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states "at index count - 1" but the code actually accesses "bubbleView.count - 2". The comment is misleading because when a new item is being added in the add transition, bubbleView.count has already been incremented to include the new item. So count - 2 is indeed the previous bubble (the one that was last before this addition), not count - 1. The comment should be corrected to say "at index count - 2" or better yet, explain that since count includes the newly added item, count - 2 gives us the previously added bubble.

Suggested change
// Only handle the previous notification bubble (at index count - 1); no need to iterate through all of them
// Only handle the previous notification bubble; since count already includes the newly added bubble, the previous one is at index count - 2, so no need to iterate through all of them

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +82
let prevItem = bubbleView.itemAtIndex(bubbleView.count - 2)
if (prevItem) {
// Directly set x to 0 to forcibly complete the animation
prevItem.x = 0
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessing bubbleView.itemAtIndex during an add transition may be unreliable. During the add transition, the ListView is in the process of laying out items, and itemAtIndex may return null or an incorrect item reference, especially when multiple items are being animated simultaneously. This could result in the ScriptAction failing silently when prevItem is null, which defeats the purpose of this code. Consider adding additional null checks or using a more robust method to track and stop previous animations, such as maintaining a reference to the currently running animation.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants