Maui 實踐:自制輕量級通知組件 NoticeView
原創 夏群林 2025.8.4
顯示彈出消息,Microsoft.Maui.Controls 命名空間下的 Page 類,提供了 DisplayAlert / DisplayActionSheet / DisplayPromptAsync 三種方法,滿足一般的對話交互需要,但必須點擊類似 "OK" / "Cancel" 的按鈕關閉窗口才能結束對話。也可以自定義一個 ContenPage 頁,有點麻煩,除非特殊要求,否則沒必要。如果單純顯示消息,發送后不管,官方提供了 Snackbar / Toast,會在可配置的持續時間后被消除。
但在我的應用中,發現 Snackbar / Toast 不好用,經常顯示反應不及時。更麻煩的是,當有系列的提示密集發送,主體流程早已結束,消息還在慢吞吞的往外嘣,體驗很不好。
于是我自己做了一個 NoticeView, 作為 MAUI 應用中的輕量級通知組件,核心目標是實現多優先級消息的有序管理與靈活顯示,其解決的關鍵問題,即后續消息到來,前面不太重要的消息,要讓路,要清空。這樣,保證消息的及時顯示。
具體需求包括:
- 支持多級優先級(Low/Medium/High/Critical),高優先級消息需“插隊”優先顯示,低優先級消息無條件讓路;
- 每條消息需保證最小顯示時長(避免閃顯),無后續消息時按默認時長顯示;
- 自動清理低優先級消息,限制隊列最大長度,防止內存溢出;
- 支持多線程環境下的消息發送,確保隊列操作安全;
- 提供手動清空功能,資源釋放時自動清理,避免內存泄漏。
一、架構設計
1. 核心組件
NoticePriority:枚舉定義優先級,Critical 為最高級,權壓一切,比如,用Critical級確保清空所有消息。
public enum NoticePriority { Low, Medium, High, Critical }
public static void ClearNotice() => DisplayNotice(string.Empty, NoticePriority.Critical);
NoticeMessage:消息載體,包含內容(Value)和優先級(Priority);
public class NoticeMessage(string value, NoticePriority priority = NoticePriority.Medium)
{
public string Value { get; } = value;
public NoticePriority Priority { get; } = priority;
}
NoticeDisplayOptions:配置類,控制顯示時長(MinDisplayDuration/DisplayDuration)、隊列最大長度(MaxQueueLength)。提供了默認顯示樣式,包括字體、顏色、旋轉、陰影,我喜歡浮雕斜上如同驚鴻一瞥的效果,作為默認配置。各人可自行定義。
public class NoticeDisplayOptions
{
public TimeSpan DisplayDuration { get; set; } = TimeSpan.FromSeconds(3);
public TimeSpan MinDisplayDuration { get; set; } = TimeSpan.FromMilliseconds(500);
public int MaxQueueLength { get; set; } = 50;
public Color FontColor { get; set; } = Colors.DarkOrchid;
public float FontSize { get; set; } = 16F;
public float RotationAngle { get; set; } = -20;
public bool HasShadow { get; set; } = true;
}
QueuedNotice:內部隊列消息類,封裝優先級、入隊序號(Order)和內容,實現 IComparable 接口用于排序;
private class QueuedNotice(NoticePriority priority, int order, string message) : IComparable<QueuedNotice>
{
public NoticePriority Priority { get; } = priority;
public int Order { get; } = order;
public string Message { get; } = message;
public int CompareTo(QueuedNotice? other)
{
if (other is null) return 1;
// 優先級順序:Critical > High > Medium > Low
int priorityCompare = other.Priority.CompareTo(Priority);
return priorityCompare != 0 ? priorityCompare : Order.CompareTo(other.Order);
}
}
NoticeView:核心控件,繼承 GraphicsView,實現 IDrawable,用于繪制UI;和 IDisposable,便于資源釋放。NoticeView 負責消息接收、隊列管理、顯示控制。 也提供了發送消息和清除消息的靜態方法。
public partial class NoticeView : GraphicsView, IDrawable, IDisposable
{
private readonly NoticeDisplayOptions _options;
private readonly ConcurrentDictionary<int, QueuedNotice> _messageQueue = new();
private readonly Lock _queueSync = new();
private QueuedNotice? _currentMessage;
private DateTimeOffset _currentMessageStartTime;
private int _messageOrder = 0;
private bool _isTimerRunning;
private IDispatcherTimer? _timer;
public NoticeView(NoticeDisplayOptions? options = null)
{
_options = options ?? new NoticeDisplayOptions();
Drawable = this;
VerticalOptions = LayoutOptions.Fill;
HorizontalOptions = LayoutOptions.Fill;
InputTransparent = true;
WeakReferenceMessenger.Default.Register<NoticeMessage>(this, (_, m) => OnMessageArrive(m));
}
// ...
}
2. 整體架構圖
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 外部調用者 │──┬──>│ NoticeMessage │──┬──>│ NoticeView │
└─────────────────┘ │ └─────────────────┘ │ └────────┬────────┘
│ │ │
│ │ ▼
│ │ ┌─────────────────┐
│ └──>│ 消息隊列管理 │
│ └────────┬────────┘
│ │
└──────────────────────────────────────┘
發送消息
二、關鍵實現與核心代碼
1. 優先級與排序機制
通過自定義排序規則保證消息按“優先級降序+同優先級入隊順序升序”排列,確保高優先級消息先顯示,同優先級消息按發送順序顯示。
實現細節:
- 自定義
QueuedNotice類并實現IComparable<QueuedNotice>接口,排序規則:- 先比較優先級(Critical > High > Medium > Low);
- 優先級相同時,比較入隊序號(Order),序號越小(入隊越早)越優先。
- 入隊序號通過
Interlocked.Increment生成,保證多線程環境下的原子性和唯一性,避免序號沖突。
// 生成唯一入隊序號(多線程安全)
int enqueueOrder = Interlocked.Increment(ref _messageOrder);
設計考量:
- 用
IComparable封裝排序邏輯,避免分散在業務代碼中,提高可維護性; - 原子操作生成序號,解決多線程并發下的序號重復問題,確保同優先級消息的順序性。
2. 隊列管理策略
通過線程安全容器存儲消息,自動清理低優先級消息,控制隊列長度,確保高優先級消息“無障礙”顯示。
實現細節:
- 采用
ConcurrentDictionary<int, QueuedNotice>作為隊列容器,以入隊序號(Order)為鍵,支持并發讀寫,減少鎖競爭; - 新消息入隊時,根據優先級清理低優先級消息:
- 非 Critical 級:移除隊列中所有優先級低于新消息的消息;
- Critical 級:直接清空隊列+清除當前顯示的消息(權壓一切);
- 隊列長度超過
MaxQueueLength時,循環移除“最低優先級中最早入隊的消息”,避免隊列無限增長。
private void OnMessageArrive(NoticeMessage message)
{
int enqueueOrder = Interlocked.Increment(ref _messageOrder);
var enqueueNotice = new QueuedNotice(message.Priority, enqueueOrder, message.Value);
bool needImmediateSwitch = false;
lock (_queueSync) // 加鎖保證原子性
{
if (message.Priority == NoticePriority.Critical)
{
// Critical級:清空隊列+清除當前消息,直接搶占顯示
_messageQueue.Clear();
_currentMessage = null;
needImmediateSwitch = true;
}
else
{
// 非Critical級:移除隊列中所有低優先級消息
var lowerPriorityItems = _messageQueue.Values
.Where(m => m.Priority < message.Priority)
.ToList();
foreach (var item in lowerPriorityItems)
_ = _messageQueue.TryRemove(item.Order, out _);
// 關鍵:當前顯示的消息若優先級更低,立即清除并標記切換
if (_currentMessage != null && _currentMessage.Priority < message.Priority)
{
_messageQueue.TryRemove(_currentMessage.Order, out _);
_currentMessage = null;
needImmediateSwitch = true;
}
}
// 隊列長度控制:超過上限時,移除最低優先級中最舊的消息
while (_messageQueue.Count >= _options.MaxQueueLength)
{
var lowestPriority = _messageQueue.Values.Min(m => m.Priority);
var oldestLow = _messageQueue.Values
.Where(m => m.Priority == lowestPriority)
.OrderBy(m => m.Order)
.FirstOrDefault();
if (oldestLow != null)
_messageQueue.TryRemove(oldestLow.Order, out _);
else
break; // 極端情況防止死循環
}
_messageQueue.TryAdd(enqueueNotice.Order, enqueueNotice);
}
// 高優先級消息立即切換顯示,無需等待當前消息時長
if (message.Priority == NoticePriority.Critical || needImmediateSwitch)
MainThread.BeginInvokeOnMainThread(SwitchToNextMessage);
else
MainThread.BeginInvokeOnMainThread(UpdateDisplay);
}
設計考量:
- 用
ConcurrentDictionary減少簡單操作,如單條添加 / 移除的鎖競爭,提高并發性能; - 復合操作,如批量清理+添加,用
lock保證原子性,避免數據不一致; - Critical 級消息直接清空隊列和當前消息,確保“最高優先級”的絕對權威性;
- 隊列長度控制優先移除“最低優先級中最舊的消息”,平衡了優先級和時效性。
3. 顯示與切換邏輯
通過定時器監控消息顯示時長,根據“是否有后續消息”動態調整顯示時長,高優先級消息可強制打斷當前消息顯示。
實現細節:
- 定時器采用
DispatcherTimer,綁定UI線程,間隔100ms高頻檢查,確保時長判斷精準; - 顯示時長規則: 有后續消息時,當前消息至少顯示
MinDisplayDuration,避免閃顯; 無后續消息時,顯示時長為MinDisplayDuration與DisplayDuration的最大值,保證用戶能看清; - 切換邏輯:當滿足時長條件或收到更高優先級消息時,移除當前消息,顯示隊列中優先級最高的下一條消息。
private void Timer_Tick(object? sender, EventArgs e)
{
if (_currentMessage == null)
{
StopTimer();
return;
}
// 計算當前消息已顯示時長(從開始顯示時起算)
var elapsed = DateTimeOffset.Now - _currentMessageStartTime;
bool hasNextMessage;
lock (_queueSync)
{
// 檢查是否有除當前消息外的其他消息
hasNextMessage = _messageQueue.Count > (_currentMessage != null ? 1 : 0);
}
// 動態計算最大顯示時長
var maxDuration = hasNextMessage
? _options.MinDisplayDuration
: TimeSpan.FromMilliseconds(Math.Max(
_options.MinDisplayDuration.TotalMilliseconds,
_options.DisplayDuration.TotalMilliseconds));
// 滿足時長條件則切換消息
if (elapsed >= maxDuration)
MainThread.BeginInvokeOnMainThread(SwitchToNextMessage);
}
// 切換到下一條消息
private void SwitchToNextMessage()
{
lock (_queueSync)
{
// 移除當前顯示的消息
if (_currentMessage != null)
_messageQueue.TryRemove(_currentMessage.Order, out _);
// 顯示下一條消息(隊列中優先級最高的)
var nextNotice = !_messageQueue.IsEmpty
? _messageQueue.Values.OrderBy(m => m).FirstOrDefault()
: null;
if (nextNotice != null)
{
_currentMessage = nextNotice;
_currentMessageStartTime = DateTimeOffset.Now; // 從顯示時開始計時
Notice = nextNotice.Message;
Invalidate(); // 觸發UI重繪
}
else
{
ClearCurrentMessage(); // 隊列空則清除顯示
}
}
}
設計考量:
_currentMessageStartTime僅在消息開始顯示時賦值,確保計時起點與用戶可見時間一致;- 高頻定時器(100ms)減少時長判斷的延遲,提升用戶體驗;
- 動態調整顯示時長(區分有/無后續消息),既避免消息閃顯,又防止無后續消息時顯示過短。
4. 線程安全與資源管理
通過鎖機制和線程隔離保證多線程安全,通過 IDisposable 接口和手動清理方法確保資源釋放。
實現細節:
- 線程安全:
- 簡單操作(單條消息的添加/移除)依賴
ConcurrentDictionary的線程安全特性; - 復合操作(批量清理、隊列長度控制)用
lock (_queueSync)保證原子性; - 所有UI操作(如刷新顯示、切換消息)通過
MainThread.BeginInvokeOnMainThread限制在主線程,避免跨線程異常。
- 簡單操作(單條消息的添加/移除)依賴
- 資源管理:
- 實現
IDisposable接口,釋放時注銷消息訂閱、停止定時器、清空隊列; - 提供
ClearQueue手動清空方法,支持主動清理; ClearNotice方法通過發送 Critical 級空消息,利用其“清空一切”特性快速清理。
- 實現
public void Dispose()
{
// 注銷消息訂閱,避免內存泄漏
WeakReferenceMessenger.Default.UnregisterAll(this);
StopTimer(); // 停止定時器
ClearQueue(); // 清空隊列和當前消息
GC.SuppressFinalize(this);
}
// 手動清空隊列
public void ClearQueue()
{
lock (_queueSync)
{
_messageQueue.Clear();
ClearCurrentMessage();
}
}
// 清空通知(利用Critical級特性)
public static void ClearNotice() => DisplayNotice(string.Empty, NoticePriority.Critical);
設計考量:
- 最小化鎖范圍(僅復合操作加鎖),平衡線程安全和性能;
- 消息訂閱使用弱引用 WeakReferenceMessenger,配合
Dispose注銷,避免組件銷毀后仍接收消息導致的內存泄漏; ClearNotice復用 Critical 級消息的清理邏輯,減少代碼冗余,同時保證清理徹底性。
三、使用示例
使用很方便。在需要顯示的頁面,放一個 NoticeView 實例即可:
public partial class SearchPage : ContentPage
{
public SearchPage(SearchViewModel viewModel)
{
InitializeComponent();
this.contentPageLayout.Children.Add(new NoticeView() { ZIndex = 3, InputTransparent = true });
}
// ...
}
并且,可在多個顯示頁面放置 NoticeView 實例。
在任何需要的地方調用:
NoticeView.DisplayNotice($"Neither found nor suggested",NoticePriority.High);
四、得意之處
通過“優先級排序+隊列動態清理+即時切換+線程安全”的設計,NoticeView 實現了高優先級消息優先顯示、低優先級消息自動讓路的核心需求。其設計亮點在于:
- 用
IComparable和原子序號保證了多線程下的消息順序; - 區分簡單/復合操作的線程安全策略,平衡了性能和安全性;
- 動態時長控制和強制切換機制,兼顧了用戶體驗和優先級權威性;
- 完善的資源清理機制,避免了內存泄漏風險。
該組件可直接集成到 MAUI 應用中,支持自定義樣式和時長,適用于各類輕量級通知場景,如操作提示、狀態更新等。
本方案源代碼開源,按照 MIT 協議許可。地址 xiaql/Zhally.Toolkit: Practices on specific tool kit in MAUI。

浙公網安備 33010602011771號