WPF动画生命周期
动画会暂时覆盖属性的值,但不会自动修改属性的基础值。
① 动画开始前:属性有自己的基础值
<Button x:Name="btn" Width="100" Height="50" Content="按钮"/>此时:
| 项目 | 值 |
|---|---|
| Width 的基础值 | 100 |
| 界面实际使用的宽度 | 100 |
这里的“基础值”,可以先理解为:不考虑动画时,这个属性应该使用的值。本例中,就是 XAML 设置的 100。
② 动画运行中:动画值暂时覆盖基础值
启动一个让宽度从 100 变成 300 的动画:
DoubleAnimation animation = new DoubleAnimation { From = 100, To = 300, Duration = TimeSpan.FromSeconds(2) }; btn.BeginAnimation( FrameworkElement.WidthProperty, animation);假设动画执行到一半:
| 项目 | 值 |
|---|---|
| Width 的基础值 | 仍然是 100 |
| 当前动画值 | 约 200 |
| 界面实际使用的宽度 | 约 200 |
按钮变宽了,但原本设置的 100 并没有被动画改成 200。
你可以把它想成:基础值还在下面,动画临时提供了一个优先使用的值。
③ 动画播放完:默认继续保持终点值
2 秒后,按钮宽度变为 300。
你可能以为:
动画已经结束,那么它应该不再影响按钮了。
但是默认情况下,动画的:
FillBehavior = FillBehavior.HoldEnd;HoldEnd表示:播放完成后,继续保持动画的最终效果。
因此:
| 项目 | 值 |
|---|---|
| Width 的基础值 | 100 |
| 动画保持的值 | 300 |
| 界面实际使用的宽度 | 300 |
这里要区分两件事:
- 播放完成:属性不再从 100 逐渐变到 300。
- 停止影响属性:属性不再使用动画提供的值。
播放完成,不代表动画已经停止影响属性。
这种现象描述为动画结束后仍保持作用。更严格地说,默认情况下此时进入的是填充阶段(Filling),已经不是正在变化的活动阶段(Active)。
④ 为什么动画结束后,重新赋值“没有效果”?
假设动画播放结束后执行:
btn.Width = 150;按钮可能仍然显示为 300。
不是这句代码没执行,而是现在变成了:
| 项目 | 值 |
|---|---|
| Width 的基础值 | 已经改成 150 |
| 动画保持的值 | 仍然是 300 |
| 界面实际使用的宽度 | 300 |
基础值改了,但动画值仍然优先。
这就是这一节最容易遇到的问题。
⑤ 解决办法一:结束后恢复基础值
设置:
animation.FillBehavior = FillBehavior.Stop;完整写法:
DoubleAnimation animation = new DoubleAnimation { From = 100, To = 300, Duration = TimeSpan.FromSeconds(2), FillBehavior = FillBehavior.Stop }; btn.BeginAnimation( FrameworkElement.WidthProperty, animation);假设基础值一直是 100,效果就是:
| 阶段 | 实际宽度 |
|---|---|
| 开始前 | 100 |
| 播放中 | 从 100 逐渐变到 300 |
| 播放完成后 | 恢复为 100 |
注意:恢复的是当时的基础值。如果你在动画过程中把基础值改成了 150,那么动画结束后就会显示 150。
另外,FillBehavior.Stop是让动画结束后不再提供属性值,不等同于把动画对象删除或立刻回收内存。
⑥ 解决办法二:主动移除属性上的动画
通过下面的代码,移除宽度属性上的动画影响:
btn.BeginAnimation( FrameworkElement.WidthProperty, null);这里的null表示不再给这个属性应用动画。
例如:
// 移除动画,恢复使用基础值 btn.BeginAnimation( FrameworkElement.WidthProperty, null); // 正常设置宽度 btn.Width = 150;这次按钮就会显示为 150。
注意:它移除的是Width 属性上的动画,不是按钮上的所有动画。
⑦ 解决办法三:播放结束后保留最终宽度,同时移除动画
实际开发中,你可能希望:
按钮变宽到 300 后就保持 300,并且以后还能正常修改 Width。
可以在Completed事件中处理:
private void btn_Click(object sender, RoutedEventArgs e) { DoubleAnimation animation = new DoubleAnimation { From = 100, To = 300, Duration = TimeSpan.FromSeconds(2) }; animation.Completed += (s, args) => { // 1. 先记住动画作用下的当前宽度 double currentWidth = btn.Width; // 2. 移除动画 btn.BeginAnimation( FrameworkElement.WidthProperty, null); // 3. 把刚才的宽度设置为基础值 btn.Width = currentWidth; }; btn.BeginAnimation( FrameworkElement.WidthProperty, animation); }顺序很重要:
- 先记录当前宽度 300。
- 再移除动画。
- 再赋值,让基础值也变为 300。
这样,动画移除后,按钮依然保持 300。以后再执行:
btn.Width = 150;就可以正常变成 150。
这里的Completed表示动画完成了活动期,并不表示动画已经自动移除。上述例子使用默认的HoldEnd,所以能在事件中读取到保持的最终值。
⑧ 书里另外两种方式,应该怎样理解?
书中还提到“回到原值”的动画,以及:
animation.AutoReverse = true;AutoReverse的效果是:
从 100 变到 300,再从 300 变回 100。
但要注意:反向播放回到 100,不代表动画已被移除。如果仍使用默认的HoldEnd,它还可能保持反向播放结束时的值。
因此,当你的目的是“动画结束后,让普通赋值恢复正常”,最直接的办法是:
| 你的需求 | 做法 |
|---|---|
| 播放后恢复基础值 | FillBehavior.Stop |
| 立即取消动画影响 | BeginAnimation(属性, null) |
| 播放后保留最终值,并恢复正常赋值 | Completed中记录当前值、移除动画、再赋值 |
记住这一节的核心关系:基础值一直存在;动画可以覆盖它;动画播放完毕后,是否继续覆盖,由结束行为和移除操作决定。