2009-10-02 12 views
6

Uczę się animacji WPF i jestem zdezorientowany, jak sekwencyjnie stosować animacje. Jako prosty przykład, mam cztery prostokąty w jednolitej siatce i chciałbym zmienić kolor każdego z nich sekwencyjnie. Oto, co mam do tej pory:WPF - animacja sekwencyjna prosty przykład

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(); 

    } 

Jaki jest najprostszy sposób na osiągnięcie tego? Kod w komentarzach to moje pierwsze przypuszczenie, ale nie działa poprawnie.

Odpowiedz

8

Powinno nastąpić zdarzenie ani.Completed - obsłużyć to zdarzenie i rozpocząć następną fazę animacji, a następnie uruchomić pierwszą z uruchomioną, a każda faza uruchomi następną.

ColorAnimation ani = // whatever... 

ani.Completed += (s, e) => 
    { 
     ColorAnimation ani2 = // another one... 

     // and so on 
    }; 

newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani); 

UPDATE:

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); 
     }; 
    } 
} 

Spróbuj wklejając powyższe pod swoim programie. Robi to, czego potrzebujesz, ale w sposób, który może ci się przydać w innych kontekstach. Nadal jest napędzany zdarzeniami, ale wykorzystuje "metodę iteratora" (z zwrotem wydajności), aby stworzyć wrażenie, że jest to sekwencyjne kodowanie, które blokuje podczas animacji.

Przyjemnie jest, że można bawić się metodą AnimationSequence w bardzo intuicyjny sposób - można napisać oś czasu animacji w serii instrukcji lub użyć pętli lub cokolwiek innego.

+0

Co zrobić, jeśli animacje są dynamiczne? Chciałbym móc nazwać niebieski, zielony, czerwony, niebieski, zielony, czerwony; każda animacja może 2 sekundy po poprzedniej. Czy istnieje sposób, aby wywołać blok animateCell, dopóki nie zostanie uruchomiony ogień zadeklarowany? –

3

Rozwiązaniem, które próbowałem, jest użycie kolejki podobnej do tej. Umożliwi to dynamiczne dodawanie do łańcucha animacji. Nie jestem pewien, czy zamek jest konieczny, ale zostawiłem go, żeby był bezpieczny.

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); 
     } 
    } 
} 
+0

O wiele bardziej czytelny, a kolejka pasuje do wyobraźni sekwencji. Zaimplementowano kolejkę akcji, w której akcja rozpoczyna animację. –

3

Można to osiągnąć za pomocą klasy ze sprzecznych nazwy ParallelTimeline i starannie dostosowanie właściwości BeginTime. W poniższym przykładzie należy zwrócić uwagę, że właściwość BeginTime drugiego DoubleAnimation jest ustawiona na czas trwania pierwszego.

<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>