WPF中如何移除元素的动画及其Completed事件监听?
WPF元素循环淡入淡出动画的停止方案
问题背景
我有一组UI元素,通过两个DoubleAnimation针对Opacity属性实现循环淡入淡出动画,利用动画的Completed事件触发下一个动画,代码如下:
foreach(UIElement myElement in ListOfElements) { DoubleAnimation fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromSeconds(fadeInSeconds)); DoubleAnimation fadeOut = new DoubleAnimation(1, 0, TimeSpan.FromSeconds(fadeOutSeconds)); fadeIn.Completed += (s, e) => myElement.BeginAnimation(UIElement.OpacityProperty, fadeOut); fadeOut.Completed += (s, e) => myElement.BeginAnimation(UIElement.OpacityProperty, fadeIn); }
现在需要停止特定元素的动画循环:使用myElement.BeginAnimation(UIElement.OpacityProperty, null)可以停止当前动画,但无法阻止已触发的Completed事件在原定时间执行,导致动画可能意外重启。因此需要先找到该元素的活跃动画,移除其Completed事件监听器,再停止动画。
问题解答
1. 查找指定元素的活跃动画
WPF没有直接提供获取元素活跃动画的API,最可靠的方案是提前保存动画引用:
- 创建一个字典(例如
Dictionary<UIElement, (DoubleAnimation FadeIn, DoubleAnimation FadeOut)>),在初始化动画时,将每个元素对应的淡入、淡出动画存入字典。 - 当需要停止特定元素动画时,直接从字典中取出对应的动画实例即可。
如果无法提前保存引用,也可以通过AnimationClock间接获取当前运行的动画,但这种方式有局限性:
// 获取元素Opacity属性的动画时钟 var animationClock = myElement.GetAnimationClock(UIElement.OpacityProperty); if (animationClock != null && animationClock.Timeline is DoubleAnimation activeAnimation) { // activeAnimation为当前正在运行的动画,但无法区分是淡入还是淡出动画 }
该方法只能拿到当前正在执行的动画,无法获取等待触发的另一个动画,因此优先推荐提前保存引用的方案。
2. 移除动画的Completed事件监听器
由于最初使用的是匿名委托,匿名委托无法直接通过-=移除,因此需要调整代码,使用具名委托实例:
- 先定义可复用的委托实例,在初始化动画时绑定:
// 定义存储元素与动画、委托的字典 var animationDict = new Dictionary<UIElement, (DoubleAnimation FadeIn, DoubleAnimation FadeOut, EventHandler FadeInCompleted, EventHandler FadeOutCompleted)>(); foreach(UIElement myElement in ListOfElements) { DoubleAnimation fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromSeconds(fadeInSeconds)); DoubleAnimation fadeOut = new DoubleAnimation(1, 0, TimeSpan.FromSeconds(fadeOutSeconds)); // 定义具名委托 EventHandler fadeInCompleted = (s, e) => myElement.BeginAnimation(UIElement.OpacityProperty, fadeOut); EventHandler fadeOutCompleted = (s, e) => myElement.BeginAnimation(UIElement.OpacityProperty, fadeIn); // 绑定事件 fadeIn.Completed += fadeInCompleted; fadeOut.Completed += fadeOutCompleted; // 存入字典 animationDict.Add(myElement, (fadeIn, fadeOut, fadeInCompleted, fadeOutCompleted)); }
- 停止动画时,从字典中取出对应委托和动画,移除事件监听并停止动画:
if (animationDict.TryGetValue(targetElement, out var animationData)) { // 移除Completed事件监听 animationData.FadeIn.Completed -= animationData.FadeInCompleted; animationData.FadeOut.Completed -= animationData.FadeOutCompleted; // 停止元素的Opacity动画 targetElement.BeginAnimation(UIElement.OpacityProperty, null); // 从字典移除,避免内存泄漏 animationDict.Remove(targetElement); }
如果坚持使用匿名委托,无法完成监听移除——因为每次创建的匿名委托都是独立实例,-=无法匹配到对应的绑定项。
内容的提问来源于stack exchange,提问作者Tim Smith
相关产品推荐
相关产品推荐

