使用WPF故事板平滑地从顺时针模式转换为逆时针模式

3

编辑:该项目可以从这里下载。

我正在使用WPF Storyboard来实现地球的旋转动画,当用户点击按钮时,我希望能够将旋转方向从顺时针变为逆时针(反之亦然),尽管在现实中这永远不会发生。

enter image description here

在我的XAML文件中,我声明了21个BitmapImages(第一个和最后一个相同)和2个Storyboard,一个用于顺时针旋转,另一个用于逆时针旋转。

<Window x:Class="StackOverflowWPF.StoryboardSeekWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="StoryboardSeekWindow" Height="400" Width="400">
<Window.Resources>
    <BitmapImage x:Key="RotationImage0" UriSource="Images\rotation\0.png"/>
    <BitmapImage x:Key="RotationImage1" UriSource="Images\rotation\1.png"/>
    <BitmapImage x:Key="RotationImage2" UriSource="Images\rotation\2.png"/>
    <BitmapImage x:Key="RotationImage3" UriSource="Images\rotation\3.png"/>
    <BitmapImage x:Key="RotationImage4" UriSource="Images\rotation\4.png"/>
    <BitmapImage x:Key="RotationImage5" UriSource="Images\rotation\5.png"/>
    <BitmapImage x:Key="RotationImage6" UriSource="Images\rotation\6.png"/>
    <BitmapImage x:Key="RotationImage7" UriSource="Images\rotation\7.png"/>
    <BitmapImage x:Key="RotationImage8" UriSource="Images\rotation\8.png"/>
    <BitmapImage x:Key="RotationImage9" UriSource="Images\rotation\9.png"/>
    <BitmapImage x:Key="RotationImage10" UriSource="Images\rotation\10.png"/>
    <BitmapImage x:Key="RotationImage11" UriSource="Images\rotation\11.png"/>
    <BitmapImage x:Key="RotationImage12" UriSource="Images\rotation\12.png"/>
    <BitmapImage x:Key="RotationImage13" UriSource="Images\rotation\13.png"/>
    <BitmapImage x:Key="RotationImage14" UriSource="Images\rotation\14.png"/>
    <BitmapImage x:Key="RotationImage15" UriSource="Images\rotation\15.png"/>
    <BitmapImage x:Key="RotationImage16" UriSource="Images\rotation\16.png"/>
    <BitmapImage x:Key="RotationImage17" UriSource="Images\rotation\17.png"/>
    <BitmapImage x:Key="RotationImage18" UriSource="Images\rotation\18.png"/>
    <BitmapImage x:Key="RotationImage19" UriSource="Images\rotation\19.png"/>
    <BitmapImage x:Key="RotationImage20" UriSource="Images\rotation\20.png"/>
    <Storyboard x:Key="clockwiseStoryboard" RepeatBehavior="Forever">
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="imgRotatingEarth" Storyboard.TargetProperty="Source">
            <DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{Binding Source={StaticResource RotationImage0}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:0.2" Value="{Binding Source={StaticResource RotationImage1}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:0.4" Value="{Binding Source={StaticResource RotationImage2}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:0.6" Value="{Binding Source={StaticResource RotationImage3}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:0.8" Value="{Binding Source={StaticResource RotationImage4}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:1.0" Value="{Binding Source={StaticResource RotationImage5}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:1.2" Value="{Binding Source={StaticResource RotationImage6}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:1.4" Value="{Binding Source={StaticResource RotationImage7}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:1.6" Value="{Binding Source={StaticResource RotationImage8}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:1.8" Value="{Binding Source={StaticResource RotationImage9}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:2.0" Value="{Binding Source={StaticResource RotationImage10}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:2.2" Value="{Binding Source={StaticResource RotationImage11}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:2.4" Value="{Binding Source={StaticResource RotationImage12}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:2.6" Value="{Binding Source={StaticResource RotationImage13}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:2.8" Value="{Binding Source={StaticResource RotationImage14}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:3.0" Value="{Binding Source={StaticResource RotationImage15}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:3.2" Value="{Binding Source={StaticResource RotationImage16}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:3.4" Value="{Binding Source={StaticResource RotationImage17}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:3.6" Value="{Binding Source={StaticResource RotationImage18}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:3.8" Value="{Binding Source={StaticResource RotationImage19}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:4.0" Value="{Binding Source={StaticResource RotationImage20}}"/>
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>

    <Storyboard x:Key="counterClockwiseStoryboard" RepeatBehavior="Forever">
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="imgRotatingEarth" Storyboard.TargetProperty="Source">
            <DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{Binding Source={StaticResource RotationImage20}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:0.2" Value="{Binding Source={StaticResource RotationImage19}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:0.4" Value="{Binding Source={StaticResource RotationImage18}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:0.6" Value="{Binding Source={StaticResource RotationImage17}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:0.8" Value="{Binding Source={StaticResource RotationImage16}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:1.0" Value="{Binding Source={StaticResource RotationImage15}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:1.2" Value="{Binding Source={StaticResource RotationImage14}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:1.4" Value="{Binding Source={StaticResource RotationImage13}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:1.6" Value="{Binding Source={StaticResource RotationImage12}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:1.8" Value="{Binding Source={StaticResource RotationImage11}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:2.0" Value="{Binding Source={StaticResource RotationImage10}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:2.2" Value="{Binding Source={StaticResource RotationImage9}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:2.4" Value="{Binding Source={StaticResource RotationImage8}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:2.6" Value="{Binding Source={StaticResource RotationImage7}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:2.8" Value="{Binding Source={StaticResource RotationImage6}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:3.0" Value="{Binding Source={StaticResource RotationImage5}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:3.2" Value="{Binding Source={StaticResource RotationImage4}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:3.4" Value="{Binding Source={StaticResource RotationImage3}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:3.6" Value="{Binding Source={StaticResource RotationImage2}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:3.8" Value="{Binding Source={StaticResource RotationImage1}}"/>
            <DiscreteObjectKeyFrame KeyTime="0:0:4.0" Value="{Binding Source={StaticResource RotationImage0}}"/>
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</Window.Resources>
<Grid>
    <Image x:Name="imgRotatingEarth" HorizontalAlignment="Center" Margin="50" Stretch="Uniform" />

    <Button VerticalAlignment="Bottom" Content="Reverse" Height="30" Width="100" Click="reverseButton_Click" />
</Grid>

我的代码后置文件

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation;

namespace StackOverflowWPF
{
    /// <summary>
    /// Interaction logic for StoryboardSeekWindow.xaml
    /// </summary>
    public partial class StoryboardSeekWindow : Window
    {
        Storyboard clockwiseStoryboard;
        Storyboard counterClockwiseStoryboard;
        bool bRotateClockwisely;
        TimeSpan duration = new TimeSpan(0, 0, 4);
        double dProgress = 0; //the value of dProgress is from 0.0 (begin) to 1.0 (end)

        public StoryboardSeekWindow()
        {
            InitializeComponent();
            clockwiseStoryboard = this.FindResource("clockwiseStoryboard") as Storyboard;
            counterClockwiseStoryboard = this.FindResource("counterClockwiseStoryboard") as Storyboard;
            StartRotation();
        }

        private void StartRotation()
        {
            counterClockwiseStoryboard.Begin();
            counterClockwiseStoryboard.Pause();

            clockwiseStoryboard.Begin();

            bRotateClockwisely = true;
        }

        private void reverseButton_Click(object sender, RoutedEventArgs e)
        {
            Storyboard sbActive = bRotateClockwisely ? clockwiseStoryboard : counterClockwiseStoryboard;
            Storyboard sbPaused = bRotateClockwisely ? counterClockwiseStoryboard : clockwiseStoryboard;

            sbActive.Pause();

            //I want the other storyboard can seek to where the animation is paused.
            dProgress = sbActive.GetCurrentProgress();
            dProgress = 1.0 - dProgress;
            sbPaused.Seek(new TimeSpan((long)(duration.Ticks * dProgress)));

            sbPaused.Resume();

            bRotateClockwisely = !bRotateClockwisely;            
        }
    }
}

为了实现顺时针到逆时针模式的平滑过渡,我暂停活动动画并计算当前帧数,当恢复相反的动画时,我首先寻找到该帧。换句话说,我想要实现这种效果。
0.png->1.png->2.png->3.png->4.png->5.png->用户点击按钮->5.png->4.png->3.png->2.png->1.png->0.png->...
但我的代码不起作用。Storyboard.Seek() 似乎工作正常,但它没有开始反向旋转。我认为我可能在运行两个 Storyboard 以操作同一个对象方面出了问题。
请问能否帮我修复错误,并可能提供更好的思路来实现相同的效果?
2个回答

1

你可能在这里用所有这些图像'打错了牌'...为什么不尝试类似这样的东西呢?:

<BitmapImage x:Key="RotationImage0" UriSource="Images\rotation\0.png">
    <BitmapImage.RenderTransform>
        <RotateTransform x:Name="RotateTransform" CenterX="50" CenterY="50" Angle="0" />
    </BitmapImage.RenderTransform>
</BitmapImage>
<BitmapImage.Style>
    <Style>
        <Style.Triggers>
            <DataTrigger Binding="{Binding IsLoading, Mode=OneWay}" Value="True">
                <Setter Property="Control.Visibility" Value="Visible" />
                <DataTrigger.EnterActions>
                    <StopStoryboard BeginStoryboardName="ReverseRotationStoryboard" />
                    <BeginStoryboard Name="RotationStoryboard">
                        <Storyboard RepeatBehavior="Forever">
                            <DoubleAnimation Storyboard.TargetProperty="RenderTransform.
(RotateTransform.Angle)" From="0" To="360" Duration="0:0:2.0" />
                        </Storyboard>
                    </BeginStoryboard>
                </DataTrigger.EnterActions>
                <DataTrigger.ExitActions>
                    <StopStoryboard BeginStoryboardName="RotationStoryboard" />
                    <BeginStoryboard Name="ReverseRotationStoryboard">
                        <Storyboard RepeatBehavior="Forever">
                            <DoubleAnimation Storyboard.TargetProperty="RenderTransform.
(RotateTransform.Angle)" From="360" To="0" Duration="0:0:2.0" />
                        </Storyboard>
                    </BeginStoryboard>
                </DataTrigger.ExitActions>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</BitmapImage.Style>

如果您需要在动画中明确的“步骤”,您可以使用以下方式替换上面的DoubleAnimation:
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="RenderTransform.
(RotateTransform.Angle)" Duration="0:0:0.9">
    <DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0" />
    <DiscreteDoubleKeyFrame Value="30" KeyTime="0:0:0.075" />
    <DiscreteDoubleKeyFrame Value="60" KeyTime="0:0:0.150" />
    ...
    <DiscreteDoubleKeyFrame Value="330" KeyTime="0:0:0.825" />
</DoubleAnimationUsingKeyFrames>

这基本上是通过绑定一个名为IsLoadingBoolean属性(从我的程序中改编而来)来实现的。你可以随意命名你的属性,但是想法是将它设置为false以反转Animation的方向。我相信你能够根据用户的Click来设置此属性。
请注意,在DataTrigger.EnterActions部分中的Storyboard...当属性“变为”true时会发生这种情况。现在看一下DataTrigger.ExitActions部分中的Storyboard...当属性不再是true或“变为”false时会发生这种情况。

这不是从北极上方看到的地球,因此无法通过在单个图像上使用RotateTransform来完成动画。而且您的代码与我面临的问题相同,当属性在“true”和“false”之间切换时,角度将在反转旋转方向之前设置为360或0(即旋转开始时的角度),对吗? - kennyzx

1
我已经解决了自己的问题:
如果当前的 Storyboard 没有停止,将不会触发反转动画。所以,我应该停止当前的 Storyboard 并启动反转的 Storyboard,而不是暂停当前的 Storyboard 并恢复反转的 Storyboard。
并且在开始之后应该调用Storyboard.Seek()方法。如果一个 Storyboard 没有开始,Seek() 方法没有任何效果。
现在这段代码看起来是这样的。
private void reverseButton_Click(object sender, RoutedEventArgs e)
{
    Storyboard sbActive = bRotateClockwisely ? clockwiseStoryboard : counterClockwiseStoryboard;
    Storyboard sbPaused = bRotateClockwisely ? counterClockwiseStoryboard : clockwiseStoryboard;


    //I want the other storyboard can seek to where the animation is paused.
    dProgress = sbActive.GetCurrentProgress();
    dProgress = 1.0 - dProgress;

    sbActive.Stop();           

    sbPaused.Begin();
    sbPaused.Seek(new TimeSpan((long)(duration.Ticks * dProgress)), TimeSeekOrigin.BeginTime);

    bRotateClockwisely = !bRotateClockwisely;            
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接