이전 글에서는 암시적 애니메이션에 대해서 작성했었습니다.
왜 암시적 애니메이션이 아닌 명시적 애니메이션을 쓰는가라는 이유는 어쩌면 명확합니다.
명시적 애니메이션은 암시적 애니메이션보다는 난이도가 있기 때문에 오늘 글은 조금 길지도 모르겠습니다.😅
이전 글에서 명확한 설명을 드리지 못한거 같아 다시 정리해보겠습니다.
명시적(Explicit) 애니메이션을 사용하는 주된 이유는 애니메이션의 상태와 동작을 제어할 수 있기 때문입니다.
명시적 애니메이션은 개발자에게 애니메이션의 시작, 중지, 방향 전환, 속도 조절 등을 정밀하게 조작할 수 있는 능력을 부여합니다.
다음은 명시적 애니메이션을 사용하는 몇 가지 주요 이유입니다:
명시적 애니메이션을 통해 개발자는 애니메이션의 진행을 정확하게 제어할 수 있습니다.
예를 들어, AnimationController를 사용하면 언제든지 애니메이션을 시작, 중지, 또는 반복할 수 있으며,
애니메이션을 특정 지점에서 시작하거나, 역방향으로 진행할 수 있습니다.
복잡한 애니메이션 시퀀스를 구현할 때 명시적 애니메이션은 특히 유용합니다.
여러 애니메이션 속성을 동시에 조합하거나 연속적으로 트리거해야 할 경우, 명시적 애니메이션을 통해 각 단계를 정확히 조절할 수 있습니다.
애니메이션을 애플리케이션 로직과 통합해야 하는 경우, 예를 들어 사용자의 입력이나 네트워크 응답에 따라 애니메이션을 변경해야 할 때,
명시적 애니메이션을 사용하면 이러한 상황을 효과적으로 처리할 수 있습니다.
실행 시간(runtime)에 애니메이션 파라미터를 동적으로 변경할 필요가 있을 때 명시적 애니메이션을 사용하면 유연하게 대응할 수 있습니다.
예를 들어, 애니메이션의 지속 시간이나 곡선(curve)을 사용자의 선택에 따라 변경할 수 있습니다.
복수의 애니메이션 요소를 정확한 타이밍에 맞추어 동기화해야 할 때 명시적 애니메이션을 사용하면 각 요소의 타이밍을 조절하여 완벽하게 동기화시킬 수 있습니다.
이는 특히 시각적으로 복잡한 인터랙션을 구현할 때 중요합니다.
암시적(Implicit) 애니메이션도 유용하지만, 개발자가 애니메이션의 라이프사이클을 제어할 수 있는 옵션이 제한적입니다.
암시적 애니메이션은 주로 간단하고 빠르게 구현할 수 있는 UI 변화에 적합하며, 복잡한 제어가 필요하지 않은 상황에서 사용하기 좋습니다.
반면, 명시적 애니메이션은 보다 복잡하고 세밀한 애니메이션 효과가 필요할 때 선택하는 것이 좋습니다.
먼저 기본적인 애니메이션 컨트롤러 설정부터 시작해 보겠습니다.
AnimationController _animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 3),
);
애니메이션 컨트롤러는 Flutter에서 애니메이션을 정밀하게 제어하기 위한 핵심 도구입니다.
Flutter의 애니메이션 시스템은 상당히 강력하며, AnimationController는 이 시스템의 중심적인 요소 중 하나입니다.
이 컨트롤러는 애니메이션의 시작, 중지, 반복 등을 관리하고, 애니메이션의 현재 상태나 진행도를 추적할 수 있게 해줍니다.
AnimationController는 애니메이션의 "진행 시간"을 관리합니다.
이 컨트롤러를 사용하여 애니메이션의 길이(duration), 방향(direction), 그리고 스타일(curve style) 등을 정의할 수 있습니다.
컨트롤러의 값은 보통 0.0에서 1.0 사이로 변화하며, 이 값은 애니메이션의 시작점과 종료점을 나타냅니다.
AnimationController를 생성할 때 몇 가지 중요한 매개변수를 설정해야 합니다:
duration: 애니메이션의 전체 길이를 정의합니다.
이는 Duration 객체를 통해 설정되며, 예를 들어 Duration(seconds: 1)은 애니메이션의 길이가 1초임을 의미합니다.
vsync: 이는 애니메이션의 성능을 최적화하기 위한 매개변수로, 위젯의 라이프사이클에 애니메이션을 동기화시키는 역할을 합니다.
일반적으로 vsync에는 TickerProvider를 구현한 위젯의 인스턴스(this)가 전달됩니다.
Flutter에서 애니메이션을 효과적으로 사용하기 위해 중요한 개념들 중 하나는 vsync, TickerProvider, 그리고 ticker입니다.
이들은 애니메이션의 성능을 최적화하고 앱의 리소스를 효율적으로 관리하는 데 핵심적인 역할을 합니다.
vsync는 Vertical Synchronization(수직 동기화)의 약자로, 화면이 갱신되는 속도에 애니메이션을 맞추는 기능을 합니다.
화면 갱신률, 즉 리프레시 레이트에 맞추어 애니메이션을 업데이트함으로써, 애니메이션의 부드러움을 유지하고 스크린 티어링(screen tearing) 현상을 방지할 수 있습니다.
Ticker 객체는 매 프레임마다 콜백을 호출하는 역할을 합니다. 이 콜백에서는 일반적으로 애니메이션 상태를 업데이트하거나 화면을 다시 그리는 작업을 수행합니다.
Ticker는 AnimationController와 함께 사용되어, 애니메이션이 실행되어야 할 정확한 시간을 제어할 수 있게 도와줍니다.
TickerProvider는 Ticker 객체를 생성하는 역할을 합니다. Ticker는 매 프레임마다 콜백을 호출하는 타이머와 유사한 객체로,
Flutter의 애니메이션 엔진이 각 프레임을 언제 업데이트할지 결정할 때 사용됩니다.
SingleTickerProviderStateMixin과 TickerProviderStateMixin은 TickerProvider 인터페이스를 구현한 믹스인이며,
각각 단일 Ticker와 여러 Ticker를 위한 상태 관리를 제공합니다.
Ticker는 각 애니메이션 프레임당 한 번 콜백을 호출합니다. 얼마나 자주 호출하시는지 아시겠죠?
Ticker가 필요한 이유는 애니메이션이 초당 60프레임으로 최대한 빠르게 실행되어야 하기 때문입니다.
vsync는 애니메이션을 현재 위젯 트리와 동기화시키는 역할을 하며, this를 통해 TickerProvider를 전달합니다. duration은 애니메이션의 지속 시간을 설정합니다.
Flutter에서는 다양한 타입의 애니메이션을 지원합니다. 여기서는 네 가지 주요 애니메이션에 대해 알아보겠습니다.
DecorationTween: 위젯의 배경, 테두리 등을 애니메이션화합니다.
Tween: 숫자 값을 애니메이션화하여 회전, 크기 조정 등에 사용됩니다.
Tween: 위치 이동을 위한 애니메이션을 제어합니다.
각 애니메이션 타입은 animate() 메소드를 통해 컨트롤러에 연결됩니다.
이 메소드는 애니메이션의 실제 동작을 정의하는 CurvedAnimation와 같은 다른 애니메이션 객체를 받을 수 있습니다.
아래는 위에서 설명한 각 애니메이션을 통합하여 만든 예제입니다.
SlideTransition(
position: _offset,
child: ScaleTransition(
scale: _scale,
child: RotationTransition(
turns: _rotation,
child: DecoratedBoxTransition(
decoration: _animation,
child: const SizedBox(width: 300, height: 300),
),
),
),
);
이 코드는 위치 이동, 크기 조정, 회전, 그리고 배경 색상 변경을 결합한 복합 애니메이션을 보여줍니다.
애니메이션을 제어하기 위해서는 다음과 같은 버튼들을 사용할 수 있습니다.
ElevatedButton(onPressed: _animationController.forward, child: const Text('Play')),
ElevatedButton(onPressed: _animationController.stop, child: const Text('Pause')),
ElevatedButton(onPressed: _animationController.reverse, child: const Text('Rewind')),
각 버튼은 애니메이션을 시작, 정지, 그리고 역재생하는 기능을 수행합니다.
import 'package:flutter/material.dart';
class ExplicitAnimationScreen extends StatefulWidget {
const ExplicitAnimationScreen({super.key});
State<ExplicitAnimationScreen> createState() =>
_ExplicitAnimationScreenState();
}
class _ExplicitAnimationScreenState extends State<ExplicitAnimationScreen>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 3),
)..addListener(() => _updateAnimationValue());
late final Animation<Decoration> _animation = _buildDecorationAnimation();
late final Animation<double> _rotation = _buildRotationAnimation();
late final Animation<double> _scale = _buildScaleAnimation();
late final Animation<Offset> _offset = _buildOffsetAnimation();
late final CurvedAnimation _curve =
CurvedAnimation(parent: _animationController, curve: Curves.elasticOut);
bool _looping = false;
final ValueNotifier<double> _value = ValueNotifier(0.0);
void dispose() {
_animationController.dispose();
super.dispose();
}
void _updateAnimationValue() {
_value.value = _animationController.value;
}
Animation<Decoration> _buildDecorationAnimation() {
return DecorationTween(
begin: BoxDecoration(
color: Colors.red, borderRadius: BorderRadius.circular(20)),
end: BoxDecoration(
color: Colors.blue, borderRadius: BorderRadius.circular(100)),
).animate(_curve);
}
Animation<double> _buildRotationAnimation() =>
Tween<double>(begin: 0, end: 0.5).animate(_curve);
Animation<double> _buildScaleAnimation() =>
Tween<double>(begin: 1, end: 1.1).animate(_curve);
Animation<Offset> _buildOffsetAnimation() =>
Tween<Offset>(begin: Offset.zero, end: const Offset(0, -0.5))
.animate(_curve);
void _toggleLooping() {
setState(() {
_looping = !_looping;
_looping
? _animationController.repeat(reverse: true)
: _animationController.stop();
});
}
void _onSliderChanged(double value) {
_value.value = value;
_animationController.value = value;
}
Widget _buildAnimationControls() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _animationController.forward, child: const Text('Play')),
ElevatedButton(
onPressed: _animationController.stop, child: const Text('Pause')),
ElevatedButton(
onPressed: _animationController.reverse,
child: const Text('Rewind')),
ElevatedButton(
onPressed: _toggleLooping,
child: Text(_looping ? 'Stop' : 'Start')),
],
);
}
Widget _buildSlider() {
return ValueListenableBuilder<double>(
valueListenable: _value,
builder: (context, value, _) {
return Slider(value: value, onChanged: _onSliderChanged);
},
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Explicit Animations')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildAnimatedWidget(),
const SizedBox(height: 50),
_buildAnimationControls(),
const SizedBox(height: 25),
_buildSlider(),
],
),
),
);
}
Widget _buildAnimatedWidget() {
return SlideTransition(
position: _offset,
child: ScaleTransition(
scale: _scale,
child: RotationTransition(
turns: _rotation,
child: DecoratedBoxTransition(
decoration: _animation,
child: const SizedBox(width: 300, height: 300),
),
),
),
);
}
}
ValueNotifier는 요약하자면 매우 몹시 중요한 애니메이션 최적화 기법입니다.
Flutter에서 ValueNotifier는 상태 관리를 위한 간단하고 효율적인 클래스입니다.
ValueNotifier를 사용하면 특정 값의 변경을 감지하고, 해당 값에 의존하는 위젯들을 효율적으로 업데이트할 수 있습니다.
ValueNotifier는 Listenable을 확장한 클래스로, 단일 데이터 값의 변경을 알리는 데 사용됩니다.
이 클래스는 내부적으로 값을 저장하며, 이 값이 변경될 때마다 리스너들에게 알림을 보냅니다.
ValueNotifier의 주요 사용 사례는 간단한 데이터 변화에 대해 리스너들(주로 UI 위젯)을 업데이트하는 것입니다.
간결성: 복잡한 상태 관리 솔루션에 비해 코드가 훨씬 간결하고 이해하기 쉽습니다.
효율성: ValueNotifier는 값이 실제로 변경될 때만 리스너들을 업데이트하므로, 불필요한 위젯 재구성을 방지합니다.
직관성: 특정 값의 변경을 감지하고, 해당 값에 의존하는 위젯만 업데이트하는 직관적인 방법을 제공합니다.
ValueNotifier는 특정 값에 대한 감시와 그에 따른 반응을 구현할 때 사용됩니다.
예를 들어, 사용자 인터페이스에서 슬라이더의 값이 변경될 때마다 연관된 텍스트를 업데이트하고 싶다면 ValueNotifier를 사용할 수 있습니다.
UI와 데이터 동기화: ValueNotifier를 사용하면 UI 컴포넌트와 데이터 사이의 동기화를 유지할 수 있어, 데이터가 변경될 때 UI가 자동으로 반영됩니다.
리소스 최적화: Flutter의 렌더링 시스템은 매우 빠르고 효율적이지만, 불필요한 위젯 재구성은 성능에 부담을 줄 수 있습니다.
ValueNotifier는 필요할 때만 위젯을 업데이트함으로써 리소스 사용을 최적화합니다.
ValueNotifier는 Flutter에서 간단한 상태 관리가 필요할 때 매우 유용합니다. 복잡한 상태 관리 시스템이 과도하거나 불필요할 때,
ValueNotifier를 사용하면 개발 프로세스를 간소화하고 성능을 최적화할 수 있습니다.
Flutter에서 명시적 애니메이션을 사용하면 애니메이션의 각 단계를 정밀하게 제어할 수 있습니다.
이를 통해 사용자에게 풍부한 시각적 경험을 제공하고, 앱의 상호작용성을 크게 향상시킬 수 있습니다.
이 글을 통해 명시적 애니메이션의 기초를 이해하고, 실제로 어떻게 구현하는지에 대한 감을 잡으셨기를 바랍니다.
https://docs.flutter.dev/ui/widgets/animation
간단하게 앞에 Animated가 들어가면 암시적, 뒤에 Transition이 들어가면 명시적 애니메이션입니다.