애니메이션 위젯은 기본적으로 정해진 애니메이션을 제공해준다.(AnimatedOpacity 같은)
하지만 위젯 크기나 움직이는 애니메이션을 넣으려면 커스텀 애니메이션을 사용해야 한다.
그러기 위해 AnimationController
가 필요하다.
AnimationController
는 애니메이션의 시간을 담당하는 클래스이며 애니메이션의 정보를 가지고 있다고 보면 된다.
아래 예제는 기존 위젯이 아닌 근본적으로 Animation을 컨트롤링 하는 방법이다.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget {
const TestPage({super.key});
State<TestPage> createState() => _TestPageState();
}
class _TestPageState extends State<TestPage>
with SingleTickerProviderStateMixin {
// 2-1. Mixin 추가
// 1. 변수 생성
late final AnimationController _animationController;
bool isSelected = false;
void _onPressed() {
// 3. 애니메이션 값 구하기
if (isSelected) {
isSelected = false;
_animationController.reverse();
} else {
isSelected = true;
_animationController.forward();
}
}
void initState() {
// 2-2. 초기화
_animationController = AnimationController(
vsync: this,
lowerBound: 1,
upperBound: 1.5,
value: 1,
duration: const Duration(milliseconds: 300),
);
// 4. Event Listener 추가해서 값이 변경될때 setState
_animationController.addListener(() {
print(_animationController.value);
setState(() {});
});
super.initState();
}
void dispose() {
_animationController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
const SizedBox(height: 150),
CupertinoButton.filled(
onPressed: _onPressed,
child: const Text("Button"),
),
const SizedBox(height: 100),
Transform.scale(
scale: _animationController.value,
child: AnimatedOpacity(
opacity: isSelected ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: Container(
color: Colors.red,
width: 100,
height: 100,
),
),
)
],
),
),
);
}
}
초기화 및 필수 속성인 vsync
를 사용하려면 with SingleTickerProviderStateMixin
사용해야함.
vsync
는 애니메이션 최적화 및 언제 재생할지 시간을 세어주는 등의 기능을 위해 필요하다.
addListener로 값이 바뀔때 setState를 실행시켜준다. 일종의 Trigger.
offscreen 애니메이션의 불필요한 리소스를 막는다고 한다. (공식문서 내용)
즉, 위젯이 안 보일 때는 애니메이션이 작동하지 않도록 하는것이다.
current tree가 활성화된 동안만 tick 하는 단일 ticker를 제공한다.
current tree가 활성화된 동안 : 위젯이 화면에 보일 때만
Ticker는 시계라고 생각하면 이해하기 쉽다. 매 초도, 매 밀리초도 아니고 애니메이션의 프레임마다 callback을 호출해준다.
vsync에 ticker
애니메이션이 ticker를 잡을 거고 애니메이션이 재생되어야 할 때가 오면 Ticker가 그걸 애니메이션에게 알려준다. Ticker는 그걸 매 프레임마다 할 것이다.
vsync는 애니메이션 재생을 도와주며 위젯이 위젯_트리에 있을 때만 Ticker를 유지해 준다는 것이다.
그리고 Ticker를 만들기 위해서는 SingleTickerProviderStateMixin
사용.
위의 소스와 다른점과 장점은
Event Listener와 setState를 사용하지 않아도 된다는 점이다.
animate하고 싶은것과 animation을 분리 할 수 있다.
기능적으로는 큰 차이는 없지만 AnimatedBuilder
를 사용하는게 더 좋아보인다.
// 4. Event Listener 추가해서 값이 변경될때 setState
_animationController.addListener(() {
print(_animationController.value);
setState(() {});
});
이부분은 삭제한다.
Transform.scale(
scale: _animationController.value,
child: AnimatedOpacity(
opacity: isSelected ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: Container(
color: Colors.red,
width: 100,
height: 100,
),
),
),
위의 build 소스코드는 아래와 같이 수정
AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _animationController.value,
child: child,
);
},
child: AnimatedOpacity(
opacity: isSelected ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: Container(
color: Colors.red,
width: 100,
height: 100,
),
),
),