1. 애니메이션을 구현해보자.
애니메이션에는 explicit(명시적), implicit(암묵적) 2가지의 종류가 있다. 아래 내용이 잘 정리되어있다.
https://velog.io/@broccolism/Flutter-%ED%94%8C%EB%9F%AC%ED%84%B0-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%A0%84%EC%B2%B4%EB%B3%B4%EA%B8%B0
[Flutter] 플러터 애니메이션 전체보기
플러터에서는 2가지 방법으로 애니메이션을 구현할 수 있다.
velog.io
명시적은 매뉴얼로 사용자가 직접 만드는 것이고, 암묵적인 방법은 플러터에서 제공하는 애니메이션을 가져다 쓰는 것이다.
먼저 명시적 방법을 알아보자.
- categories.dart
import 'package:flutter/material.dart' ;
import 'package:meals/data/dummy_data.dart' ;
import 'package:meals/models/meal.dart' ;
import 'package:meals/widgets/category_grid_item.dart' ;
import 'package:meals/screens/meals.dart' ;
import 'package:meals/models/category.dart' ;
class CategoriesScreen extends StatefulWidget {
const CategoriesScreen ({
super . key ,
required this . availableMeals ,
}) ;
final List < Meal > availableMeals ;
@override
State < CategoriesScreen > createState () => _CategoriesScreenState () ;
}
class _CategoriesScreenState extends State < CategoriesScreen >
with SingleTickerProviderStateMixin {
late AnimationController _animationController ;
@override
void initState () {
super . initState () ;
_animationController = AnimationController (
vsync : this ,
duration : const Duration (milliseconds : 300 ) ,
lowerBound : 0 ,
upperBound : 1 ,
) ;
_animationController . forward () ;
}
@override
void dispose () {
_animationController . dispose () ;
super . dispose () ;
}
void _selectCategory ( BuildContext context , Category category) {
final filteredMeals = widget . availableMeals
. where ((meal) => meal . categories . contains (category . id))
. toList () ;
Navigator . of (context) . push (
MaterialPageRoute (
builder : (ctx) => MealsScreen (
title : category . title ,
meals : filteredMeals ,
) ,
) ,
) ; // Navigator.push(context, route)
}
@override
Widget build ( BuildContext context) {
return AnimatedBuilder (
animation : _animationController ,
child : GridView (
padding : const EdgeInsets . all ( 24 ) ,
gridDelegate : const SliverGridDelegateWithFixedCrossAxisCount (
crossAxisCount : 2 ,
childAspectRatio : 3 / 2 ,
crossAxisSpacing : 20 ,
mainAxisSpacing : 20 ,
) ,
children : [
// availableCategories.map((category) => CategoryGridItem(category: category)).toList()
for ( final category in availableCategories)
CategoryGridItem (
category : category ,
onSelectCategory : () {
_selectCategory (context , category) ;
} ,
)
] ,
) ,
builder : (context , child) => Padding (
padding : EdgeInsets . only (
top : 100 - _animationController . value * 100 ,
) ,
child : child) ,
) ;
}
}
1) 먼저 stateful 상태로 바꿔준다. (애니메이션이 계속 움직이면서 리빌딩 되어야하기 때문)
2) late AnimationController _animationController; --> late를 사용함으로써, init후에 할당하겠다는 것 @override void initState() { -->init시 (build위젯 실행 전) 아래를 먼저 실행 super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), lowerBound: 0, upperBound: 1, ); _animationController.forward(); -->1번만 실행하겠다는 뜻, 반복 실행도 가능하고 여러가지 기능이 있어보인다. } @override -->build가 끝나면 해당 컨트롤러를 dispose를 메모리에서 삭제해주자 (최적화) void dispose() { _animationController.dispose(); super.dispose(); }
3) build 부분을 보면, AnimatedBuilder로 감싸준다. 그리고, animation은 컨트롤러를, child에는 바뀌지 않아야할 부분을 넣고 builder에서 어떻게 애니메이션화 할 지 설정한다. 여기서는 padding을 통해 카테고리 화면이 아래에서 위로 올라오게끔 설정했다. top 패딩값이 초기에는 100이었다가 점차 0이 되면서 애니메이션한다. (_animationController.value 초기값이 0이고 최고 1이다 0->1로 변경되는것, 따라서 값을 대입하면 top 패딩값은 100-> 0으로 된다)
그리고 마지막 child에는 위에서 설정한 child값을 넣는다. (최적화를 위해, child값은 리빌딩 되지 않는다.)