WPF巧用动画反转

WPF巧用动画反转

在 WPF 程序中,假设有这么一个需求:界面上有个矩形,点击某个按钮后,矩形沿某条 复杂的路径 移动,并停在路径的终点处;此时点击另一个按钮,矩形沿刚才的路径反向移动,最终停在路径的起始位置。
假设路径非常复杂,要单独构建如上的两个动画,需要很大的工作量。那么,能否仅构建一个动画,使其能暂停到一半的地方,并且可以反向回到初始状态呢?其实现方案为,设置 StoryboardAutoReverse 属性值为 true,并将动画 Pause() 在一半时长的位置,然后通过 Resume() 方法来实现反向动画。此处还需用到 StoryboardCurrentTimeInvalidated 事件,该事件在动画的每一帧都会触发。

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 动画的高级方法。本方案主要用于解决动画难以构建的问题,如果正向动画和反转动画的构建都非常简单,那么完全可以编写两个动画来实现需求。

猜你喜欢

转载自blog.csdn.net/Iron_Ye/article/details/82833925
今日推荐