Flutter Animation 플러터 애니메이션 - AnimatedList
Flutter 위젯은 스크롤, 내비게이션, 아이콘 및 글꼴과 등 중요한 플랫폼 차이점이 모두 통합되어 있어 iOS와 Android 모두에서 네이티브 수준의 성능을 보여줍니다. 동일한 코드를 통해 유지보수와 성능을 동시에 가져갈 수 있어 개발자들은 높은 효율성과 확장성을 보장 받을 수 있습니다. 이것이 flutter 주저없이 선택 할 수 있는 이유이기도 합니다.
오늘은 항목이 추가되거나 제거될 때 애니메이션을 적용하여 스크롤 컨테이너를 구현할 수 있는 AnimatedList 프로그래밍에 대해 알아보겠습니다.
AnimatedList class
수많은 앱이 동적 리스트를 기반으로 만들어집니다. 빈번하게 항목이 추가되고 삭제되며 변경되곤 합니다. 애니메이션이 없다면 이런 과정을 원활하게 표현할 수 없습니다. AnimatedList는 리스트를 동적으로 표현하여 사용자에게 멋진 경험을 제공해 줍니다.
Contructors
항목을 추가하거나 제거할 때 애니메이션을 적용하는 스크롤 컨테이너를 만듭니다.
const AnimatedList({
Key? key,
required this.itemBuilder,
this.initialItemCount = 0,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.controller,
this.primary,
this.physics,
this.shrinkWrap = false,
this.padding,
this.clipBehavior = Clip.hardEdge,
})
Required Parameter
itemBuilder : 리스트의 모든 인덱스에서 구현되어야 목록을 정의해 줍니다 index는 각 항목의 고유한 구성 요소를 구분해 줍니다. animation은 해당 항목이 추가될 때 자동으로 구동되는 애니메이션을 구현할 수 있습니다.
AnimatedList(
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
return SizeTransition(
position: animation.drive(MyTween());
child: MyListItem(_myItems[index]);
);
}
);
initialItemCount : 초기 목록 카운트입니다. 항목이 추가되거나 삭제될 때마다 해당 필드가 변경되며 이후 AnimatedListState에 정의된 함수를 호출하여 애니메이션을 구현 할 수 있습니다.
AnimatedListState.insertItem
AnimatedListState.removeItem
AnimatedListState.removeItem
key : AnimatedList와 State를 연결하기 위해 글로벌 키를 사용합니다. 글로벌 키를 통해 어떤 위치에서도 AnimatedList에 정의 된 함수(insertItem, removeItem)에 접근할 수 있습니다.
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
...
AnimatedList(
key: _listKey,
physics: const BouncingScrollPhysics(),
initialItemCount: _list.length,
itemBuilder: _buildItem,
),
...
_listKey.currentState.insertItem(index);
_listKey.currentState.removeItem(index, (context, animation) => ...);
Properties
key → Key?
한 위젯이 트리의 다른 위젯의 접근방법을 제어합니다
final, inherited
physics → ScrollPhysics?
스크롤 뷰가 표현되는 방식입니다.
final
initialItemCount → int
목록이 시작되는 항목 수입니다
final
itemBuilder → AnimatedListItemBuilder
항목 위젯을 빌드하기 위해 필요에 따라 호출됩니다.
final
전체 Code
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
late ListModel<int> _list;
int? _selectedItem;
late int _nextItem;
@override
void initState() {
super.initState();
_list = ListModel<int>(
listKey: _listKey,
initialItems: <int>[0, 1, 2],
removedItemBuilder: _buildRemovedItem,
);
_nextItem = 3;
}
Widget _buildItem(BuildContext context, int index, Animation<double> animation) {
return CardItem(
animation: animation,
item: _list[index],
selected: _selectedItem == _list[index],
onTap: () {
setState(() {
_selectedItem = _selectedItem == _list[index] ? null : _list[index];
});
},
);
}
Widget _buildRemovedItem(int item, BuildContext context, Animation<double> animation) {
return CardItem(
animation: animation,
item: item,
selected: false,
);
}
void _insert() {
final int index = _selectedItem == null ? _list.length : _list.indexOf(_selectedItem!);
_list.insert(index, _nextItem++);
}
void _remove() {
if (_selectedItem != null) {
_list.removeAt(_list.indexOf(_selectedItem!));
setState(() {
_selectedItem = null;
});
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: const Icon(Icons.add_circle, size: 40),
onPressed: _insert,
tooltip: 'insert a new item',
),
IconButton(
icon: const Icon(Icons.remove_circle, size: 40),
onPressed: _remove,
tooltip: 'remove the selected item',
),
],
),
Expanded(
child: Container(
margin: const EdgeInsets.only(top: 10.0),
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
color: Colors.grey[300],
child: AnimatedList(
key: _listKey,
physics: const BouncingScrollPhysics(),
initialItemCount: _list.length,
itemBuilder: _buildItem,
),
),
)
],
);
}
}
typedef RemovedItemBuilder = Widget Function(int item, BuildContext context, Animation<double> animation);
class ListModel<E> {
final GlobalKey<AnimatedListState> listKey;
final RemovedItemBuilder removedItemBuilder;
final List<E> _items;
ListModel({
required this.listKey,
required this.removedItemBuilder,
Iterable<E>? initialItems,
}) : _items = List<E>.from(initialItems ?? <E>[]);
AnimatedListState? get _animatedList => listKey.currentState;
void insert(int index, E item) {
_items.insert(index, item);
_animatedList!.insertItem(index);
}
E removeAt(int index) {
final E removedItem = _items.removeAt(index);
if (removedItem != null) {
_animatedList!.removeItem(
index,
(context, animation) => removedItemBuilder(index, context, animation),
);
}
return removedItem;
}
int get length => _items.length;
E operator [](int index) => _items[index];
int indexOf(E item) => _items.indexOf(item);
}
class CardItem extends StatelessWidget {
const CardItem({
Key? key,
this.onTap,
this.selected = false,
required this.animation,
required this.item,
}) : assert(item >= 0),
super(key: key);
final Animation<double> animation;
final VoidCallback? onTap;
final int item;
final bool selected;
@override
Widget build(BuildContext context) {
TextStyle textStyle = Theme.of(context).textTheme.headline4!;
if (selected) textStyle = textStyle.copyWith(color: Colors.redAccent[400]);
return Padding(
padding: const EdgeInsets.all(2.0),
child: SizeTransition(
sizeFactor: animation,
axis: Axis.vertical,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: SizedBox(
height: 80,
child: Card(
color: Colors.primaries[item % Colors.primaries.length][100],
child: Center(
child: Text('Item $item', style: textStyle),
),
),
),
),
),
);
}
}


댓글
댓글 쓰기