【问题标题】:WPF - sequential animation simple exampleWPF - 顺序动画简单示例
【发布时间】:2010-12-03 11:15:45
【问题描述】:

我正在学习 WPF 动画,但对如何按顺序应用动画感到困惑。作为一个简单的例子,我在一个统一的网格中有四个矩形,并且想依次更改每个矩形的颜色。这是我目前所拥有的:

public partial class Window1 : Window
{
    Rectangle blueRect;
    Rectangle redRect;
    Rectangle greenRect;
    Rectangle yellowRect;

    public Window1()
    {
        InitializeComponent();
        blueRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Blue, Name="Blue"};
        redRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Red, Name="Yellow"};
        greenRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Green, Name="Green" };
        yellowRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Yellow, Name="Yellow" };

        UniformGrid1.Children.Add(blueRect);
        UniformGrid1.Children.Add(redRect);
        UniformGrid1.Children.Add(greenRect);
        UniformGrid1.Children.Add(yellowRect);

    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        animateCell(blueRect, Colors.Blue);
        animateCell(redRect, Colors.Red);
    }

    private void animateCell(Rectangle rectangle, Color fromColor)
    {
        Color toColor = Colors.White;
        ColorAnimation ani = new ColorAnimation(toColor, new Duration(TimeSpan.FromMilliseconds(300)));
        ani.AutoReverse = true;

        SolidColorBrush newBrush = new SolidColorBrush(fromColor);
        ani.BeginTime = TimeSpan.FromSeconds(2);
        rectangle.Fill = newBrush;
        newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani);
        //NameScope.GetNameScope(this).RegisterName(rectangle.Name, rectangle);
        //Storyboard board = new Storyboard();
        //board.Children.Add(ani);
        //Storyboard.SetTargetName(rectangle, rectangle.Name);
        //Storyboard.SetTargetProperty(ani, new PropertyPath(SolidColorBrush.ColorProperty));
        //board.Begin();

    }

实现此目的最简单的方法是什么? cmets 中的代码是我的第一个猜测,但它无法正常工作。

【问题讨论】:

    标签: wpf animation storyboard


    【解决方案1】:

    应该有一个事件ani.Completed - 处理该事件并开始动画的下一个阶段,然后开始运行第一个阶段,每个阶段都会触发下一个阶段。

    ColorAnimation ani = // whatever...
    
    ani.Completed += (s, e) => 
       {
           ColorAnimation ani2 = // another one...
    
           // and so on
       };
    
    newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani);
    

    更新:

    public partial class Window1 : Window
    {
        Rectangle blueRect;
        Rectangle redRect;
        Rectangle greenRect;
        Rectangle yellowRect;
    
        public Window1()
        {
            InitializeComponent();
            blueRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Blue, Name = "Blue" };
            redRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Red, Name = "Yellow" };
            greenRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Green, Name = "Green" };
            yellowRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Yellow, Name = "Yellow" };
    
            UniformGrid1.Children.Add(blueRect);
            UniformGrid1.Children.Add(redRect);
            UniformGrid1.Children.Add(greenRect);
            UniformGrid1.Children.Add(yellowRect);
        }
    
        IEnumerable<Action<Action>> AnimationSequence()
        {
            for (; ; )
            {
                yield return AnimateCell(blueRect, Colors.Blue);
                yield return AnimateCell(redRect, Colors.Red);
                yield return AnimateCell(greenRect, Colors.Green);
                yield return AnimateCell(yellowRect, Colors.Yellow);
            }
        }
    
        private IEnumerator<Action<Action>> _actions;
    
        private void RunNextAction()
        {
            if (_actions.MoveNext())
                _actions.Current(RunNextAction);
        }
    
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            _actions = AnimationSequence().GetEnumerator();
            RunNextAction();
        }
    
        private Action<Action> AnimateCell(Rectangle rectangle, Color fromColor)
        {
            return completed =>
            {
                Color toColor = Colors.White;
                ColorAnimation ani = new ColorAnimation(toColor, 
                                        new Duration(TimeSpan.FromMilliseconds(300)));
                ani.AutoReverse = true;
                ani.Completed += (s, e) => completed();
    
                SolidColorBrush newBrush = new SolidColorBrush(fromColor);
                ani.BeginTime = TimeSpan.FromSeconds(2);
                rectangle.Fill = newBrush;
                newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani);
            };
        }
    }
    

    尝试将以上内容粘贴到您的程序中。它可以满足您的需求,但在其他情况下可能对您有用。它仍然是事件驱动的,但它使用“迭代器方法”(带有 yield return)来营造一种印象,即它是在动画进行时阻塞的顺序编码。

    这样做的好处是您可以以非常直观的方式使用 AnimationSequence 方法 - 您可以在一系列语句中写出动画的时间线,或使用循环,或任何您想要的。

    【讨论】:

    • 如果动画是动态的呢?我希望能够打电话说蓝色,绿色,红色,蓝色,绿色,红色;每个动画可能在前一个动画之后 2 秒。有没有办法让 animateCell 的调用者阻塞直到 ani.Completed 被触发?
    【解决方案2】:

    我尝试过的解决方案是使用这样的队列。这将允许您动态添加到动画链中。我不确定是否需要锁,但为了安全起见,我把它留在了里面。

    Queue<Object[]> animationQueue = new Queue<Object[]>();
    
    void sequentialAnimation(DoubleAnimation da, Animatable a, DependencyProperty dp)
    {
        da.Completed += new EventHandler(da_Completed);
    
        lock (animationQueue)
        {
            if (animationQueue.Count == 0) // no animation pending
            {
                animationQueue.Enqueue(new Object[] { da, a, dp });
                a.BeginAnimation(dp, da);
            }
            else
            {
                animationQueue.Enqueue(new Object[] { da, a, dp });
            }
        }
    }
    
    void da_Completed(object sender, EventArgs e)
    {
        lock (animationQueue)
        {
            Object[] completed = animationQueue.Dequeue();
            if (animationQueue.Count > 0)
            {
                Object[] next = animationQueue.Peek();
                DoubleAnimation da = (DoubleAnimation)next[0];
                Animatable a = (Animatable)next[1];
                DependencyProperty dp = (DependencyProperty)next[2];
    
                a.BeginAnimation(dp, da);
            }
        }
    }
    

    【讨论】:

    • 可读性更强,队列与序列的想象相匹配。实现了一个动作队列,其中一个动作只是开始一个动画。
    【解决方案3】:

    这可以通过使用具有矛盾名称ParallelTimeline 的类并仔细调整BeginTime 属性来完成。请注意,在下面的示例中,第二个 DoubleAnimationBeginTime 属性如何设置为第一个的持续时间。

    <ParallelTimeline>
          <DoubleAnimation
               Storyboard.TargetName="FlashRectangle" 
               Storyboard.TargetProperty="Opacity"
               From="0.0" To="1.0" Duration="0:0:1"/>
          <DoubleAnimation BeginTime="0:0:0.05"
               Storyboard.TargetName="FlashRectangle" 
               Storyboard.TargetProperty="Opacity"
               From="1.0" To="0.0" Duration="0:0:2"/>
     </ParallelTimeline>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-12-16
      • 2011-03-18
      • 1970-01-01
      • 1970-01-01
      • 2020-06-10
      • 1970-01-01
      • 2020-09-20
      相关资源
      最近更新 更多