WPF 巧用动画反转
在 WPF 程序中,假设有这么一个需求:界面上有个矩形,点击某个按钮后,矩形沿某条 复杂的路径
移动,并停在路径的终点处;此时点击另一个按钮,矩形沿刚才的路径反向移动,最终停在路径的起始位置。
假设路径非常复杂,要单独构建如上的两个动画,需要很大的工作量。那么,能否仅构建一个动画,使其能暂停到一半的地方,并且可以反向回到初始状态呢?其实现方案为,设置 Storyboard
的 AutoReverse
属性值为 true
,并将动画 Pause()
在一半时长的位置,然后通过 Resume()
方法来实现反向动画。此处还需用到 Storyboard
的 CurrentTimeInvalidated
事件,该事件在动画的每一帧都会触发。
private Storyboard _storyboard;
private void InitializeStorybaord()
{
_storyboard = new Storyboard {AutoReverse = true};
_storyboard.Completed += Storyboard_Completed;
_storyboard.CurrentTimeInvalidated += Storyboard_CurrentTimeInvalidated;
......
}
/// <summary>
/// 每一帧都会触发此事件,通过当前时间与总时间的关系来判断进度
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Storyboard_CurrentTimeInvalidated(object sender, EventArgs e)
{
var currentTime = _storyboard.GetCurrentTime(this);
var totalTime = GetTotalTime(_storyboard); // 单向总时长
if (currentTime.HasValue)
{
// 判断是否到达一半时长(单向总时长)
var elapse = totalTime - currentTime.Value.TotalMilliseconds;
if (elapse < totalTime / 50) // 这是个经验阈值
{
// 调用 Storyboard.Pasue() 方法所致
if (_storyboard.GetIsPaused(this))
{
// 已经停到了一半时长
}
else // 时间到了一半时所致
{
StopForward();
}
}
}
}
/// <summary>
/// 动画结束事件处理方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Storyboard_Completed(object sender, EventArgs e)
{
StopBackward();
}
/// <summary>
/// 开始正向播放
/// </summary>
private void PlayForward()
{
_storyboard.Begin(this, true);
}
/// <summary>
/// 开始反向播放
/// </summary>
private void PlayBackward()
{
_storyboard.Resume(this);
}
/// <summary>
/// 停止正向播放,停在一半时长的位置
/// </summary>
private void StopForward()
{
_storyboard.Seek(this, TimeSpan.FromMilliseconds(GetTotalTime(_storyboard)), TimeSeekOrigin.BeginTime);
_storyboard.Pause(this);
}
/// <summary>
/// 停止反向播放,回到原始状态
/// </summary>
private void StopBackward()
{
_storyboard.SkipToFill(this);
_storyboard.Stop(this);
}
private static double GetTotalTime(TimelineGroup group)
{
var maxTime = 0.0;
foreach (var child in group.Children)
{
// 将组内的最大时间作为本组的时长
var currentTime = 0.0;
if (child.BeginTime.HasValue)
{
currentTime += child.BeginTime.Value.TotalMilliseconds;
}
if (child is TimelineGroup childGroup)
{
currentTime += GetTotalTime(childGroup);
}
else
{
if (child.Duration.HasTimeSpan)
{
currentTime += child.Duration.TimeSpan.TotalMilliseconds;
}
}
if (currentTime > maxTime)
{
maxTime = currentTime;
}
}
return maxTime;
}
如上代码的主要目的是使 AutoReverse
动画能够停留在一半时长的位置,其使用了一些 WPF 动画的高级方法。本方案主要用于解决动画难以构建的问题,如果正向动画和反转动画的构建都非常简单,那么完全可以编写两个动画来实现需求。