Flutter Animation 플러터 애니메이션 - AnimatedList

 


Flutter 위젯은 스크롤, 내비게이션, 아이콘 및 글꼴과 등 중요한 플랫폼 차이점이 모두 통합되어 있어 iOS와 Android 모두에서 네이티브 수준의 성능을 보여줍니다. 동일한 코드를 통해 유지보수와 성능을 동시에 가져갈 수 있어 개발자들은 높은 효율성과 확장성을 보장 받을 수 있습니다. 이것이 flutter 주저없이 선택 할 수 있는 이유이기도 합니다. 

오늘은 항목이 추가되거나 제거될 때 애니메이션을 적용하여 스크롤 컨테이너를 구현할 수 있는 AnimatedList 프로그래밍에 대해 알아보겠습니다. 

 

AnimatedList class

수많은 앱이 동적 리스트를 기반으로 만들어집니다. 빈번하게 항목이 추가되고 삭제되며 변경되곤 합니다. 애니메이션이 없다면 이런 과정을 원활하게 표현할 수 없습니다. AnimatedList는 리스트를 동적으로 표현하여 사용자에게 멋진 경험을 제공해 줍니다. 

Contructors
항목을 추가하거나 제거할 때 애니메이션을 적용하는 스크롤 컨테이너를 만듭니다.

  const AnimatedList({
    Keykey,
    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 contextint indexAnimation<doubleanimation) {
      return SizeTransition(
        position: animation.drive(MyTween());
        child: MyListItem(_myItems[index]);
      );
    }
  );


initialItemCount : 초기 목록 카운트입니다. 항목이 추가되거나 삭제될 때마다 해당 필드가 변경되며 이후 AnimatedListState에 정의된 함수를 호출하여 애니메이션을 구현 할 수 있습니다. 
AnimatedListState.insertItem 
AnimatedListState.removeItem 

key : AnimatedList와 State를 연결하기 위해 글로벌 키를 사용합니다. 글로벌 키를 통해 어떤 위치에서도 AnimatedList에 정의 된 함수(insertItem, removeItem)에 접근할 수 있습니다. 

  final GlobalKey<AnimatedListState_listKey = GlobalKey<AnimatedListState>();
...
AnimatedList(
   key_listKey,
    physicsconst BouncingScrollPhysics(),
    initialItemCount_list.length,
    itemBuilder_buildItem,
  ),

...
_listKey.currentState.insertItem(index);
_listKey.currentState.removeItem(index, (contextanimation) => ...);


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({Keykey}) : super(keykey);

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title_title,
      homeScaffold(
        appBarAppBar(titleconst Text(_title)),
        bodyconst MyStatefulWidget(),
      ),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Keykey}) : super(keykey);

  @override
  State<MyStatefulWidgetcreateState() => _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>[012],
      removedItemBuilder_buildRemovedItem,
    );
    _nextItem = 3;
  }

  Widget _buildItem(BuildContext contextint indexAnimation<doubleanimation) {
    return CardItem(
      animationanimation,
      item_list[index],
      selected_selectedItem == _list[index],
      onTap: () {
        setState(() {
          _selectedItem = _selectedItem == _list[index] ? null : _list[index];
        });
      },
    );
  }

  Widget _buildRemovedItem(int itemBuildContext contextAnimation<doubleanimation) {
    return CardItem(
      animationanimation,
      itemitem,
      selectedfalse,
    );
  }

  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(
          mainAxisAlignmentMainAxisAlignment.end,
          children: [
            IconButton(
              iconconst Icon(Icons.add_circlesize40),
              onPressed_insert,
              tooltip'insert a new item',
            ),
            IconButton(
              iconconst Icon(Icons.remove_circlesize40),
              onPressed_remove,
              tooltip'remove the selected item',
            ),
          ],
        ),
        Expanded(
          childContainer(
            marginconst EdgeInsets.only(top10.0),
            paddingconst EdgeInsets.symmetric(horizontal10.0vertical10.0),
            colorColors.grey[300],
            childAnimatedList(
              key_listKey,
              physicsconst BouncingScrollPhysics(),
              initialItemCount_list.length,
              itemBuilder_buildItem,
            ),
          ),
        )
      ],
    );
  }
}

typedef RemovedItemBuilder = Widget Function(int itemBuildContext contextAnimation<doubleanimation);

class ListModel<E> {
  final GlobalKey<AnimatedListStatelistKey;
  final RemovedItemBuilder removedItemBuilder;
  final List<E_items;

  ListModel({
    required this.listKey,
    required this.removedItemBuilder,
    Iterable<E>? initialItems,
  }) : _items = List<E>.from(initialItems ?? <E>[]);

  AnimatedListStateget _animatedList => listKey.currentState;

  void insert(int indexE item) {
    _items.insert(indexitem);
    _animatedList!.insertItem(index);
  }

  E removeAt(int index) {
    final E removedItem = _items.removeAt(index);
    if (removedItem != null) {
      _animatedList!.removeItem(
        index,
        (contextanimation) => removedItemBuilder(indexcontextanimation),
      );
    }
    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({
    Keykey,
    this.onTap,
    this.selected = false,
    required this.animation,
    required this.item,
  })  : assert(item >= 0),
        super(keykey);

  final Animation<doubleanimation;
  final VoidCallbackonTap;
  final int item;
  final bool selected;

  @override
  Widget build(BuildContext context) {
    TextStyle textStyle = Theme.of(context).textTheme.headline4!;
    if (selectedtextStyle = textStyle.copyWith(colorColors.redAccent[400]);
    return Padding(
      paddingconst EdgeInsets.all(2.0),
      childSizeTransition(
        sizeFactoranimation,
        axisAxis.vertical,
        childGestureDetector(
          behaviorHitTestBehavior.opaque,
          onTaponTap,
          childSizedBox(
            height80,
            childCard(
              colorColors.primaries[item % Colors.primaries.length][100],
              childCenter(
                childText('Item $item'styletextStyle),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

댓글