본문 바로가기
카테고리 없음

[WPF] Animation Basic

by 성장의 용 2025. 8. 10.
728x90
반응형

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초 후 시작하며 끝난후 역방향으로 반복
  1. 2초 후 시작 (BeginTime="0:0:2")
  2. 3초 동안 색 변경
  3. 끝나면 역방향 (AutoReverse="True")
  4. 총 3번 반복 (RepeatBehavior="3x")
  5. 앞 30% 가속, 뒤 30% 감속
  6. 1.5배 속도로 재생
  7. 마지막 상태 유지 (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)
    움직임에 현실감을 더하기 위해 가속/감속 패턴 적용 가능.