JavaFX 애니메이션 입문
JavaFX 애니메이션 입문
JavaFX 애플리케이션에서 애니메이션을 만들고 싶다면 javafx.animation 패키지가 가장 친숙한 출발점이에요. 이 패키지는 애니메이션과 전이(transition)를 만들기 위한 간단한 프레임워크를 제공해요. 그 중심에는 JavaFX 전반에서 널리 쓰이는 WritableValue<T>라는 원리가 자리 잡고 있어요. WritableValue<T>는 읽고 설정할 수 있는 값을 감싸는 인터페이스로, JavaFX UI 요소의 프로퍼티를 저장할 때 흔히 사용돼요. 예를 들어 Rectangle 셰이프의 width나 height 같은 값이 여기에 해당해요. 이 패키지는 흔한 효과를 위한 다양한 내장 전이, 병렬·순차 전이 지원, 그리고 애니메이션이 끝났을 때 이벤트를 처리하는 기능까지 함께 제공해요.
본문
이 글에서는 모든 종류의 애니메이션을 살펴볼 거예요. 먼저 Animation과 그 하위 클래스인 Transition, Timeline을 다루고, 마지막으로 더 저수준의 애니메이션인 AnimationTimer를 알아볼게요. Transition은 더 간단하고 사용자 친화적인 방식으로 애니메이션을 만들 수 있게 해 주는 반면, Timeline은 더 큰 유연성을 제공해서 더 복잡한 애니메이션에 적합해요. 반대로 AnimationTimer는 프레임별(frame-by-frame) 업데이트를 위해 설계되었고 WritableValue<T>를 사용하지 않아요.
Animation
추상 클래스 Animation은 Transition과 Timeline 애니메이션의 핵심 기능을 제공하며, 직접 상속해서 확장할 수는 없어요.
Animation은 여러 프로퍼티로 구성돼요:
targetFramerate는 이Animation이 실행될 최대 프레임레이트(초당 프레임 수)예요.currentTime은 이Animation에서 현재 시점을Duration으로 나타낸 값이에요.rate는Animation이 재생될 방향과 속도를 정의해요. 양수와 음수를 모두 지원해요.cycleCount는 이Animation의 사이클 수를 정의해요. 실행 중에는 변경할 수 없고 반드시 양수여야 해요.cycleDuration은 이Animation의 한 사이클에 걸리는Duration이에요. 기본 rate인 1.0으로 시작부터 끝까지 재생하는 데 걸리는 시간을 뜻해요.totalDuration은 반복을 포함한 이Animation의 전체 지속 시간을 나타내요.cycleDuration * cycleCount의 결과이거나, 경우에 따라Duration.INDEFINITE일 수 있어요.delay는Animation이 시작될 때 지연시키는Duration이에요.autoReverse프로퍼티는Animation이 번갈아 가는 사이클에서 역방향으로 재생될지 여부를 지정해요.onFinished이벤트 핸들러는Animation이 끝났을 때 추가 동작을 정의하는 데 사용해요.status는Animation의 현재 상태를 나타내며, 가능한 상태는PAUSED,RUNNING,STOPPED예요.
또한 play(), playFrom(String cuePoint), pause(), stop() 등 애니메이션 흐름을 제어하는 유용한 메서드도 여러 개 제공해요. Animation의 문서를 잠깐 훑어보면 기능이 어떻게 구성되어 있는지 좋은 개요를 얻을 수 있어요.
Transition
추상 클래스 Transition은 모든 전이의 기반이 되는 클래스예요. 흔히 쓰이는 Animation의 한 형태라고 볼 수 있어요. JavaFX는 일반적인 Node와 Shape 프로퍼티를 위한 다양한 내장 전이를 제공해요.
Fade Transition
FadeTransition은 페이드 효과를 만들어요. Node의 opacity 프로퍼티를 주기적으로 업데이트하는 방식으로 동작해요.
Circle circle = new Circle(150, 150, 20, Color.GREEN);
FadeTransition transition = new FadeTransition(Duration.seconds(5), circle);
transition.setFromValue(1.0);
transition.setToValue(0);
transition.setInterpolator(Interpolator.LINEAR);
transition.play();
(JavaFX 애플리케이션을 구성하는 완전한 가이드는 JavaFX Application Basic Structure By Example 문서를 참고해 주세요.)
Fill Transition
FillTransition은 셰이프의 채움색이 변하는 애니메이션을 만들어요. Shape의 fill 프로퍼티를 주기적으로 업데이트하는 방식으로 동작해요.
Circle circle = new Circle(150, 150, 20, Color.GREEN);
FillTransition transition = new FillTransition(Duration.seconds(5), circle);
transition.setFromValue(Color.GREEN);
transition.setToValue(Color.BLACK);
transition.setInterpolator(Interpolator.LINEAR);
transition.play();
Translate Transition
TranslateTransition은 한 위치에서 다른 위치로 직선 이동(translate)하는 애니메이션을 만들어요. Node의 translateX, translateY, translateZ 프로퍼티를 주기적으로 업데이트하는 방식으로 동작해요.
Circle circle = new Circle(50, 50, 10, Color.GREEN);
TranslateTransition transition = new TranslateTransition(Duration.seconds(5), circle);
transition.setToX(200);
transition.setToY(200);
transition.setInterpolator(Interpolator.LINEAR);
transition.play();
Path Transition
PathTransition은 일련의 셰이프로 지정된 복잡한 사전 정의 경로를 따라 이동하는 애니메이션을 만들어요. 경로를 따라 이동하는 것은 Node의 translateX, translateY 프로퍼티를 주기적으로 업데이트해서 이루어지고, orientation이 OrientationType.ORTHOGONAL_TO_TANGENT로 설정되면 rotate 변수도 함께 업데이트돼요.
Circle circle = new Circle(50, 50, 10, Color.GREEN);
Path path = new Path();
path.getElements().add(new MoveTo(50, 50)); // starting point
path.getElements().add(new LineTo(250, 250));
PathTransition transition = new PathTransition(Duration.seconds(5), path, circle);
transition.setInterpolator(Interpolator.LINEAR);
transition.play();
Rotate Transition
RotateTransition은 회전 애니메이션을 만들어요. Node의 rotate 프로퍼티를 주기적으로 업데이트하는 방식으로 동작하며, 각도 값은 도(degree) 단위로 지정해요.
Rectangle rectangle = new Rectangle(125, 125, 50, 50);
rectangle.setFill(Color.GREEN);
RotateTransition transition = new RotateTransition(Duration.seconds(5), rectangle);
transition.setFromAngle(0);
transition.setToAngle(360);
transition.setInterpolator(Interpolator.LINEAR);
transition.play();
Scale Transition
ScaleTransition은 노드의 크기가 변하는 스케일 애니메이션을 만들어요. Node의 scaleX, scaleY, scaleZ 프로퍼티를 주기적으로 업데이트하는 방식으로 동작해요.
Circle circle = new Circle(150, 150, 50, Color.GREEN);
ScaleTransition transition = new ScaleTransition(Duration.seconds(5), circle);
transition.setToX(0.1);
transition.setToY(0.1);
transition.setInterpolator(Interpolator.LINEAR);
transition.play();
Stroke Transition
StrokeTransition은 셰이프의 선(stroke) 색상이 변하는 애니메이션을 만들어요. Shape의 stroke 프로퍼티를 주기적으로 업데이트하는 방식으로 동작해요.
Circle circle = new Circle(150, 150, 50, Color.GREEN);
circle.setStrokeWidth(5);
StrokeTransition transition = new StrokeTransition(Duration.seconds(5), circle);
transition.setFromValue(Color.GREEN);
transition.setToValue(Color.BLACK);
transition.setInterpolator(Interpolator.LINEAR);
transition.play();
Sequential Transition
SequentialTransition은 일련의 애니메이션을 순서대로 재생해요. 시퀀스에서 마지막이 아닌 Animation에 Duration.INDEFINITE를 사용하는 것은 권장하지 않아요. 뒤따르는 모든 애니메이션을 영원히 막아 버리기 때문이에요.
Pause Transition
PauseTransition은 지정된 duration만큼 멈춤을 만들어요. 프로퍼티가 아무것도 변하지 않는 SequentialTransition 안에서 지연을 만들 때 유용해요.
Circle circle = new Circle(150, 150, 20, Color.GREEN);
circle.setStrokeWidth(5);
ScaleTransition smaller = new ScaleTransition(Duration.seconds(1.5));
smaller.setToX(0.25);
smaller.setToY(0.25);
smaller.setInterpolator(Interpolator.LINEAR);
ScaleTransition larger = new ScaleTransition(Duration.seconds(1.5));
larger.setToX(1);
larger.setToY(1);
larger.setInterpolator(Interpolator.LINEAR);
SequentialTransition transition = new SequentialTransition(
circle,
smaller,
new PauseTransition(Duration.seconds(2)),
larger
);
transition.play();
이 코드는 부모 전이인 SequentialTransition에만 Node를 설정하고, 개별 자식 전이에는 설정하지 않는다는 점을 기억해 두세요. 자식 전이는 암시적으로 부모 전이의 Node를 사용해요.
Parallel Transition
ParallelTransition은 애니메이션 그룹을 병렬로 재생해요.
Rectangle rectangle = new Rectangle(50, 50, 10, 10);
rectangle.setFill(Color.GREEN);
TranslateTransition translate = new TranslateTransition(Duration.seconds(5));
translate.setToX(200);
translate.setToY(200);
translate.setInterpolator(Interpolator.LINEAR);
RotateTransition rotate = new RotateTransition(Duration.seconds(5));
rotate.setFromAngle(0);
rotate.setToAngle(360);
rotate.setInterpolator(Interpolator.LINEAR);
ParallelTransition transition = new ParallelTransition(rectangle, translate, rotate);
transition.play();
Timeline
Timeline은 어떤 WritableValue<T>에 대해서든 자유 형식의 Animation을 정의하는 데 사용돼요. 내장 전이 중 어느 것도 필요한 프로퍼티에 동작하지 않을 때 유용해요. Timeline은 연속적인 KeyFrame들의 모음으로 구성되는데, 각 KeyFrame은 시간상의 한 순간을 담고 있어요. 이들이 모여 전체 지속 시간 동안 대상 프로퍼티가 어떻게 변화하는지를 지정해요.
경고: 실행 중인
Timeline은 FX 런타임에서 참조하고 있어요. 무한 타임라인의 경우 애니메이션 프로퍼티를 가진 객체가 가비지 컬렉션되지 않아 메모리 누수가 발생할 수 있어요. 따라서 더 이상 필요 없을 때는 timeline 인스턴스를 반드시 중지하세요.
KeyFrame
KeyFrame은 애니메이션 시퀀스에서 특정 순간(Cue Point)을 나타내며, 주어진 Duration 동안 변하는 KeyValue 인스턴스들의 모음으로 구성돼요. KeyFrame은 이름을 가질 수 있는데, 이 이름으로 애니메이션 안에서 해당 KeyFrame을 식별할 수 있어요. 예를 들어 playFrom(String cuePoint)로 특정 KeyFrame부터 시작하는 데 사용할 수 있어요. 또한 이 큐 포인트에 도달했을 때 호출되는 onFinished 구현을 제공할 수도 있어요.
KeyValue
KeyValue는 WritableValue<T>와 타입 T의 대상 값 사이의 매핑을 설정해요. 값의 변화를 정의하는 데 사용돼요. 여기에 Interpolator를 추가로 정의하면 이 값이 변하는 비율을 설정할 수 있어요. KeyValue 클래스는 불변(immutable)이에요.
예제
다음 Timeline 예제는 5초 동안 x 방향으로 200px 이동하는 Circle을 만들어요:
Circle circle = new Circle(50, 150, 10, Color.GREEN);
KeyValue x = new KeyValue(circle.translateXProperty(), 200);
KeyFrame frame = new KeyFrame(Duration.seconds(5), x);
Timeline timeline = new Timeline(frame);
timeline.play();
Interpolator
추상 클래스 Interpolator는 시간에 따라 값이 변하는 비율을 정의해요. 애니메이션의 부드러움에 영향을 주는 요소예요. 일반적인 보간 기법을 위한 내장 구현이 여러 가지 제공돼요.
참고: 기본적으로 ParallelTransition과 SequentialTransition을 제외한 모든 전이는 Interpolator#EASE_BOTH를 사용해요.
Interpolator에 대한 시각화는 위의 Timeline 예제를 통해 확인할 수 있어요.
Discrete
Interpolator.DISCRETE 인터폴레이터는 중간 단계 없이 값 사이에서 갑작스러운 전이를 만들어요.
Circle circle = new Circle(50, 150, 10, Color.GREEN);
KeyValue x = new KeyValue(circle.translateXProperty(), 200, Interpolator.DISCRETE);
KeyFrame frame = new KeyFrame(Duration.seconds(5), x);
Timeline timeline = new Timeline(frame);
timeline.play();
Linear
Interpolator.LINEAR 인터폴레이터는 시간이 지나면서 값 사이에 일정한 변화 비율을 만들어요.
Ease In
Interpolator.EASE_IN 인터폴레이터는 애니메이션을 느리게 시작하고 진행될수록 가속해요.
Ease Out
Interpolator.EASE_OUT 인터폴레이터는 빠르게 시작하고 진행될수록 느려져요.
Ease Both
Interpolator.EASE_BOTH 인터폴레이터는 천천히 시작해서 중간에 가속하고 끝으로 갈수록 다시 느려져요. EASE_IN과 EASE_OUT의 특징을 결합한 것이에요.
추가로 Interpolator.SPLINE과 Interpolator.TANGENT 보간을 위한 정적 팩토리 메서드 두 가지도 있어요.
Animation Timer
추상 클래스 AnimationTimer는 애니메이션을 만들 수 있는 가장 저수준의 옵션이에요. 활성 상태인 동안 매 프레임마다 handle(long now) 메서드가 호출돼요. 타임스탬프 now는 현재 프레임의 시간을 나노초 단위로 나타낸 값이며, 그 프레임 동안 호출되는 모든 AnimationTimer에 동일하게 전달돼요. 또한 AnimationTimer는 애니메이션의 수명을 관리하기 위한 start()와 stop() 메서드를 제공해요.
참고: handle 메서드는 JavaFX Application Thread에서 호출되므로, 오래 걸리거나 블로킹되는 연산은 피해야 해요. 초당 30프레임의 부드러운 프레임레이트를 유지하려면 전체 JavaFX 애플리케이션이 프레임당 이상적으로는 33밀리초를 넘지 않게 할당해야 해요.
결론
이 튜토리얼에서는 javafx.animation 패키지를 살펴보고 JavaFX 애플리케이션 안에서 동적인 애니메이션을 만드는 방법을 배웠어요. 먼저 기본이 되는 Animation 클래스를 이해하고, 이후 애니메이션을 만들고 제어하는 서로 다른 방식을 제공하는 Transition과 Timeline 클래스로 넘어갔어요. 게다가 여러 Interpolator 예제를 통해 애니메이션의 진행을 제어하는 방법까지 익혔어요. 마지막으로 정밀한 프레임별 업데이트를 가능하게 하는 AnimationTimer 클래스를 다뤘어요. 이 도구들을 갖추면 이제 JavaFX 애플리케이션에 풍부한 애니메이션을 만들 수 있게 됐어요.
더 알아보기 (Learn more)
- JavaFX Application Basic Structure By Example — JavaFX 애플리케이션의 기본 구조
- JavaFX Fundamentals — JavaFX 기초 시리즈 전체
- Download and Setup JavaFX — JavaFX 설치와 환경 설정
javafx.animation패키지 API 문서