WPF- 순차적 애니메이션 간단한 예
-
19-09-2019 - |
문제
WPF 애니메이션에 대해 배우고 있으며 애니메이션을 순차적으로 적용하는 방법에 대해 혼란 스럽습니다. 간단한 예로, 균일 한 그리드에 4 개의 사각형이 있으며 각각의 색상을 순차적으로 변경하고 싶습니다. 지금까지 내가 가지고있는 것은 다음과 같습니다.
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();
}
이것을 달성하는 가장 쉬운 방법은 무엇입니까? 주석의 코드는 내 첫 추측이지만 올바르게 작동하지 않습니다.
해결책
이벤트가 있어야합니다 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);
};
}
}
위의 프로그램을 프로그램에 붙여 넣으십시오. 그것은 당신이 필요한 것을 수행하지만 다른 맥락에서 당신에게 유용 할 수있는 방식으로. 여전히 이벤트 중심이지만 "반복자 방법"(수율 리턴 포함)을 사용하여 애니메이션이 진행되는 동안 차단하는 순차적 코딩이라는 인상을 만듭니다.
이것에 대한 좋은 점은 매우 직관적 인 방식으로 애니메이션 시급 방법을 가지고 놀 수 있다는 것입니다. 일련의 진술로 애니메이션의 타임 라인을 작성하거나 루프를 사용하거나 원하는대로 사용할 수 있습니다.
다른 팁
내가 시도한 솔루션은 SO와 같은 줄을 사용하는 것입니다. 이렇게하면 애니메이션 체인을 동적으로 추가 할 수 있습니다. 자물쇠가 필요한지 확실하지 않지만 안전하기 위해 그 자물쇠를 남겼습니다.
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);
}
}
}
이것은 모순 된 이름의 클래스를 사용하여 달성 할 수 있습니다. ParallelTimeline
그리고 조심스럽게 조정합니다 BeginTime
재산. 아래의 예에서 BeginTime
두 번째 속성 DoubleAnimation
첫 번째 지속 시간으로 설정됩니다.
<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>