
WPF 애니메이션 기본 개념
1. 애니메이션의 핵심
- WPF는 DependencyProperty(종속성 속성)에 대해서만 애니메이션을 지원.
- 모든 애니메이션은 시간의 흐름에 따라 속성 값을 점진적으로 변화시킴.
- GPU 가속이 지원되므로 부드러운 움직임 가능.
2. 타임라인(Timeline)
- 애니메이션의 시간 흐름을 정의.
- BeginTime, Duration, RepeatBehavior, AutoReverse 등의 속성이 있음.
| 속성 | 설명 | 예시 |
| BeginTime (TimeSpan?) | 애니메이션 시작 지연 시간. null이면 즉시 시작 | BeginTime="0:0:2" → 2초 후 시작 |
| Duration (Duration) | 애니메이션이 한 번 재생되는 데 걸리는 시간 | Duration="0:0:3" → 3초 동안 실행 |
| RepeatBehavior (RepeatBehavior) | 반복 횟수 또는 반복 시간 지정 | RepeatBehavior="3x" (3번 반복), RepeatBehavior="Forever" |
| AutoReverse (bool) | True면 애니메이션이 끝난 뒤 역방향으로 재생 | AutoReverse="True" |
| AccelerationRatio (double) | 처음 가속되는 비율 (0~1) | AccelerationRatio="0.3" → 앞 30% 가속 |
| DecelerationRatio (double) | 마지막 감속되는 비율 (0~1) | DecelerationRatio="0.3" → 뒤 30% 감속 |
| SpeedRatio (double) | 재생 속도 배율 | SpeedRatio="2.0" → 2배 빠르게 |
| FillBehavior (FillBehavior) | 애니메이션 끝난 후 속성 유지 여부 | HoldEnd(기본, 마지막 값 유지), Stop(원래 값 복원) |
- 예시) Timeline 속성 적용 : 사각형의 색을 3초 동안 바꾸고 2초 후 시작하며 끝난후 역방향으로 반복
- 2초 후 시작 (BeginTime="0:0:2")
- 3초 동안 색 변경
- 끝나면 역방향 (AutoReverse="True")
- 총 3번 반복 (RepeatBehavior="3x")
- 앞 30% 가속, 뒤 30% 감속
- 1.5배 속도로 재생
- 마지막 상태 유지 (FillBehavior="HoldEnd")
<Window x:Class="TimelineExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Timeline Example" Height="200" Width="300">
<Grid>
<Rectangle Name="myRect" Width="100" Height="100" Fill="Red" HorizontalAlignment="Center" VerticalAlignment="Center">
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
From="Red" To="Blue"
BeginTime="0:0:2"
Duration="0:0:3"
AutoReverse="True"
RepeatBehavior="3x"
AccelerationRatio="0.3"
DecelerationRatio="0.3"
SpeedRatio="1.5"
FillBehavior="HoldEnd"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</Grid>
</Window>
3. Storyboard
여러 애니메이션을 그룹으로 관리.
하나의 Storyboard 안에 여러 Target 속성과 애니메이션을 묶어 동작 가능.
1) 애니메이션 타입
- From/To/By 기반 애니메이션
예: DoubleAnimation, ColorAnimation, PointAnimation - 키프레임(KeyFrame) 애니메이션
예: DoubleAnimationUsingKeyFrames, ColorAnimationUsingKeyFrames - 경로(Path) 애니메이션
예: MatrixAnimationUsingPath (객체를 PathGeometry를 따라 이동)
2) 주요 class
| 클래스 | 설명 |
| AnimationTimeline | 모든 애니메이션의 기본 클래스 |
| DoubleAnimation | double 타입 속성에 애니메이션 적용 |
| ColorAnimation | 색상(Color) 변화 |
| PointAnimation | 좌표(Point) 변화 |
| KeyFrameAnimation | 시간 구간별로 다른 값 적용 |
| Storyboard | 여러 애니메이션을 묶어서 실행 |
| EasingFunctionBase | 가속/감속 곡선 적용 (예: Bounce, Elastic) |
3) 애니메이션 속성들
- From: 시작 값
- To: 종료 값
- By: 상대적 변화량
- Duration: 애니메이션 재생 시간
- AutoReverse: 왕복 애니메이션 여부
- RepeatBehavior: 반복 횟수 또는 시간 (Forever도 가능)
- 예시) 버튼 크기 변경 애니메이션
<Window x:Class="AnimationBasicDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Animation Basic" Height="200" Width="300">
<Grid>
<Button x:Name="MyButton" Width="100" Height="40" Content="Click Me">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<!-- 버튼 Width 애니메이션 -->
<DoubleAnimation
Storyboard.TargetProperty="Width"
From="100" To="200" Duration="0:0:1"
AutoReverse="True" RepeatBehavior="2x" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
</Window>
- 코드비하인드에서 Storyboard 실행
using System;
using System.Windows;
using System.Windows.Media.Animation;
namespace AnimationBasicDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void AnimateButton()
{
DoubleAnimation anim = new DoubleAnimation
{
From = 100,
To = 200,
Duration = TimeSpan.FromSeconds(1),
AutoReverse = true,
RepeatBehavior = RepeatBehavior.Forever
};
MyButton.BeginAnimation(WidthProperty, anim);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
AnimateButton();
}
}
}
4) 주요 이벤트 (Timeline에서 상속된 것과 Storyboard 고유 이벤트가 혼합되어 있다.)
| 이벤트 | 설명 |
| Completed | 애니메이션이 정상적으로 종료되었을 때 발생한다. (중간에 Stop()하면 발생하지 않음) |
| CurrentStateInvalidated | Storyboard의 실행 상태(Active, Filling, Stopped)가 변경될 때 발생한다. |
| CurrentTimeInvalidated | 현재 재생 시간이 변경될 때 발생. (프레임마다 호출될 수 있음) |
| RemoveRequested | Remove() 호출 시 발생. |
| SeekCompleted | Seek() 또는 SeekAlignedToLastTick() 호출 후 탐색이 완료되었을 때 발생. |
| CurrentGlobalSpeedInvalidated | 애니메이션의 재생 속도가 변경될 때 발생. |
| CurrentIteration | 애니메이션이 루프(반복) 중일 때, 새 반복이 시작될 때 발생. (WPF 기본 Timeline에는 직접 노출 안 되고, 하위 API로 가능) |
using System;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace WpfApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var rect = new Rectangle
{
Width = 50,
Height = 50,
Fill = System.Windows.Media.Brushes.Blue
};
this.Content = rect;
var animation = new DoubleAnimation
{
From = 50,
To = 300,
Duration = TimeSpan.FromSeconds(2),
AutoReverse = true
};
var storyboard = new Storyboard();
storyboard.Children.Add(animation);
Storyboard.SetTarget(animation, rect);
Storyboard.SetTargetProperty(animation, new PropertyPath(Rectangle.WidthProperty));
// 이벤트 연결
storyboard.Completed += (s, e) => MessageBox.Show("애니메이션 완료!");
storyboard.CurrentStateInvalidated += (s, e) =>
{
var clock = (Clock)s;
Console.WriteLine($"상태 변경: {clock.CurrentState}");
};
storyboard.CurrentTimeInvalidated += (s, e) =>
{
var clock = (Clock)s;
Console.WriteLine($"현재 시간: {clock.CurrentTime}");
};
Loaded += (s, e) => storyboard.Begin();
}
}
}
* Animation Erasing
진행 중인 애니메이션을 중단하거나 제거해서 기존 속성이 애니메이션에서 해방(erase)되어 원래의 값(또는 새로운 값)으로 돌아가도록 하는 과정
1) Erasing 방법 : 크게 아래의 2가지 방식
(1) BeginAnimation으로 null 지정
UIElement 또는 DependencyObject의 BeginAnimation 메서드에 null을 전달하면 해당 속성의 애니메이션이 제거됨.
myButton.BeginAnimation(Button.WidthProperty, null);
- Width 속성에 걸린 애니메이션이 중단되고, 애니메이션 직전의 값 또는 마지막 값을 유지.
- 속성이 애니메이션에 의해서만 변경되던 경우, 애니메이션이 제거되면 기본값이나 바인딩 값으로 돌아갈 수 있다
(2) Remove / RemoveStoryboard 사용
Storyboard 기반 애니메이션은 Remove() 또는 Storyboard.Remove()로 제거할 수 있다.
Storyboard sb = (Storyboard)this.Resources["MyStoryboard"];
sb.Remove(myButton); // myButton에 적용된 스토리보드 애니메이션 제거
예시) 버튼 Width 애니메이션 제거 : 버튼이 커졌다 작아졌다 반복하다 2초 후 멈추고 당시 크기에서 고정됨
DoubleAnimation anim = new DoubleAnimation
{
From = 100,
To = 300,
Duration = TimeSpan.FromSeconds(3),
RepeatBehavior = RepeatBehavior.Forever
};
// 애니메이션 시작
myButton.BeginAnimation(Button.WidthProperty, anim);
// 2초 후 애니메이션 제거
Task.Delay(2000).ContinueWith(_ =>
{
Dispatcher.Invoke(() =>
{
myButton.BeginAnimation(Button.WidthProperty, null); // 애니메이션 제거
});
});
(3) Erasing할 때 주의할 점
- 속성값 복원: 애니메이션이 중단되면, 속성값은 애니메이션 시작 전의 값 또는 현재 진행 값으로 남는다
- FillBehavior 영향:
- FillBehavior.HoldEnd → 애니메이션이 끝난 후 최종 값을 유지
- FillBehavior.Stop → 애니메이션 종료 후 원래 값으로 복귀
- Storyboard.Remove와 BeginAnimation(null)의 차이
- BeginAnimation(null)은 개별 속성의 애니메이션만 제거
- Storyboard.Remove()는 해당 스토리보드가 제어하는 모든 애니메이션을 제거
* Erasing function
EasingFunction 클래스는 애니메이션 속도를 시간에 따라 비선형적으로 변화시키기 위해 사용된다.
- 천천히 시작해서 빠르게 끝나거나,
- 빠르게 시작해서 느리게 끝나거나,
- 탄성, 바운스, 백(back) 효과 등
다양한 가속·감속 패턴을 적용할 수 있게 해준다.
(1) 모든 EasingFunction은 IEasingFunction 인터페이스를 구현하며, 대부분은 EasingFunctionBase를 상속한다.
public abstract class EasingFunctionBase : Freezable, IEasingFunction
{
public bool EasingMode { get; set; } // EaseIn, EaseOut, EaseInOut
protected abstract double EaseInCore(double normalizedTime);
}
- 주요 속성
| 속성 | 설명 |
| EasingMode | EaseIn, EaseOut, EaseInOut 중 하나 선택 |
| EaseInCore(double) | 0~1 범위의 진행 비율을 받아서 비선형 변환 값 반환 |
(2) 대표적인 Erasing function 클래스
| 클래스 | 설명 |
| CircleEase | 원형 곡선 기반의 완만한 가속/감속 |
| CubicEase | 세제곱 함수 기반의 부드러운 속도 변화 |
| SineEase | 사인파 기반 부드러운 가속/감속 |
| BounceEase | 공이 튀는 듯한 반동 효과 |
| ElasticEase | 고무줄처럼 늘어났다 줄어드는 탄성 효과 |
| BackEase | 목표 지점을 지나쳤다가 돌아오는 효과 |
| PowerEase | 거듭제곱 곡선 기반 가속/감속 |
에시) DoubleAnimation에 BounceEase를 적용해보기
//xaml
<Window x:Class="EasingDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Easing Demo" Height="200" Width="400">
<Canvas>
<Ellipse Name="ball" Width="50" Height="50" Fill="Red" Canvas.Left="50" Canvas.Top="50"/>
</Canvas>
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ball"
Storyboard.TargetProperty="(Canvas.Top)"
From="50" To="120" Duration="0:0:2"
AutoReverse="True" RepeatBehavior="Forever">
<DoubleAnimation.EasingFunction>
<BounceEase Bounces="3" Bounciness="2" EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
</Window>
//cas
var anim = new DoubleAnimation
{
From = 50,
To = 300,
Duration = TimeSpan.FromSeconds(2),
AutoReverse = true,
RepeatBehavior = RepeatBehavior.Forever,
EasingFunction = new ElasticEase
{
Oscillations = 3,
Springiness = 5,
EasingMode = EasingMode.EaseOut
}
};
ball.BeginAnimation(Canvas.TopProperty, anim);
- custome erasing function
모든 커스텀 erasing function은 EasingFunctionBase를 상속받아야 한다.
protected override double EaseInCore(double normalizedTime)
- normalizedTime은 0.0 ~ 1.0 범위의 값 (애니메이션 진행률)
- 반환값도 0.0 ~ 1.0 범위여야 하며, 애니메이션 값 보간 시 사용됨.
//cs
using System;
using System.Windows;
using System.Windows.Media.Animation;
public class OvershootEase : EasingFunctionBase
{
public double Overshoot { get; set; } = 1.5; // 튀어오르는 강도
protected override double EaseInCore(double normalizedTime)
{
// 역 시그모이드 수식: 튀어오른 후 감속
double t = normalizedTime;
return t * t * ((Overshoot + 1) * t - Overshoot);
}
// 필수: Freezable에서 Clone 등을 위해 override
protected override Freezable CreateInstanceCore()
{
return new OvershootEase();
}
}
//xam
<Window x:Class="CustomEaseDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomEaseDemo"
Title="Custom Easing" Width="400" Height="300">
<Grid>
<Button Name="MyButton" Width="100" Height="40" Content="Click Me">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)"
From="0" To="250" Duration="0:0:2">
<DoubleAnimation.EasingFunction>
<local:OvershootEase Overshoot="2"
EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
</Window>
- cs코드에서 사용예시
var animation = new DoubleAnimation
{
From = 0,
To = 250,
Duration = TimeSpan.FromSeconds(2),
EasingFunction = new OvershootEase
{
Overshoot = 2,
EasingMode = EasingMode.EaseOut
}
};
MyButton.BeginAnimation(Canvas.LeftProperty, animation);
- EaseInCore에서 다양한 수학 함수를 사용해 독특한 효과 가능
- Sine, Cosine, Log, Exp, Polynomial 등
- 진행률을 가속/감속/되돌리기/튀기기 등 자유롭게 변형
- Path 기반 이동과 결합해 복잡한 애니메이션 가능
* Points
- XAML 트리거 vs 코드 비하인드
간단한 UI 상호작용 → XAML EventTrigger
동적 애니메이션 제어 → C# 코드 - 성능
WPF 애니메이션은 Composition Layer(GPU)에서 처리되어 CPU 부담이 적음. - 이징 함수(Easing Functions)
움직임에 현실감을 더하기 위해 가속/감속 패턴 적용 가능.