AnimatedList显示与ListModel保持同步的卡片列表。当新的item被添加到ListModel或从ListModel中删除时,相应的卡片在UI上也会被添加或删除,并伴有动画效果。

    Android screenshot

    点击一个item选择它,再次点击它会取消选择。点击’+’插入选定的item,点击’ - ‘删除选定的item。 tap处理器会从ListModel<E>中添加或删除items,ListModel<E>List<E>的简单封装 ,用于保持和AnimatedList的同步。 列表模型为其动画列表提供了一个GlobalKey。它使用该键来调用由AnimatedListState定义的insertItem和removeItem方法。

    通过flutter create命令创建一个新项目,并用下面的代码替换lib/main.dart的内容来尝试运行一下。

    1. // Copyright 2017 The Chromium Authors. All rights reserved.
    2. // Use of this source code is governed by a BSD-style license that can be
    3. // found in the LICENSE file.
    4. import 'package:flutter/foundation.dart';
    5. import 'package:flutter/material.dart';
    6. class AnimatedListSample extends StatefulWidget {
    7. @override
    8. _AnimatedListSampleState createState() => new _AnimatedListSampleState();
    9. }
    10. class _AnimatedListSampleState extends State<AnimatedListSample> {
    11. final GlobalKey<AnimatedListState> _listKey = new GlobalKey<AnimatedListState>();
    12. ListModel<int> _list;
    13. int _selectedItem;
    14. int _nextItem; // The next item inserted when the user presses the '+' button.
    15. @override
    16. void initState() {
    17. super.initState();
    18. _list = new ListModel<int>(
    19. listKey: _listKey,
    20. initialItems: <int>[0, 1, 2],
    21. removedItemBuilder: _buildRemovedItem,
    22. );
    23. _nextItem = 3;
    24. }
    25. // Used to build list items that haven't been removed.
    26. Widget _buildItem(BuildContext context, int index, Animation<double> animation) {
    27. return new CardItem(
    28. animation: animation,
    29. item: _list[index],
    30. selected: _selectedItem == _list[index],
    31. onTap: () {
    32. setState(() {
    33. _selectedItem = _selectedItem == _list[index] ? null : _list[index];
    34. });
    35. },
    36. );
    37. }
    38. // Used to build an item after it has been removed from the list. This method is
    39. // needed because a removed item remains visible until its animation has
    40. // completed (even though it's gone as far this ListModel is concerned).
    41. // The widget will be used by the [AnimatedListState.removeItem] method's
    42. // [AnimatedListRemovedItemBuilder] parameter.
    43. Widget _buildRemovedItem(int item, BuildContext context, Animation<double> animation) {
    44. return new CardItem(
    45. animation: animation,
    46. item: item,
    47. selected: false,
    48. // No gesture detector here: we don't want removed items to be interactive.
    49. );
    50. }
    51. // Insert the "next item" into the list model.
    52. void _insert() {
    53. final int index = _selectedItem == null ? _list.length : _list.indexOf(_selectedItem);
    54. _list.insert(index, _nextItem++);
    55. }
    56. // Remove the selected item from the list model.
    57. void _remove() {
    58. if (_selectedItem != null) {
    59. _list.removeAt(_list.indexOf(_selectedItem));
    60. setState(() {
    61. _selectedItem = null;
    62. });
    63. }
    64. }
    65. @override
    66. Widget build(BuildContext context) {
    67. return new MaterialApp(
    68. home: new Scaffold(
    69. appBar: new AppBar(
    70. title: const Text('AnimatedList'),
    71. actions: <Widget>[
    72. new IconButton(
    73. icon: const Icon(Icons.add_circle),
    74. onPressed: _insert,
    75. tooltip: 'insert a new item',
    76. ),
    77. new IconButton(
    78. icon: const Icon(Icons.remove_circle),
    79. onPressed: _remove,
    80. tooltip: 'remove the selected item',
    81. ),
    82. ],
    83. ),
    84. body: new Padding(
    85. padding: const EdgeInsets.all(16.0),
    86. child: new AnimatedList(
    87. key: _listKey,
    88. initialItemCount: _list.length,
    89. itemBuilder: _buildItem,
    90. ),
    91. ),
    92. ),
    93. );
    94. }
    95. }
    96. /// Keeps a Dart List in sync with an AnimatedList.
    97. ///
    98. /// The [insert] and [removeAt] methods apply to both the internal list and the
    99. /// animated list that belongs to [listKey].
    100. ///
    101. /// This class only exposes as much of the Dart List API as is needed by the
    102. /// sample app. More list methods are easily added, however methods that mutate the
    103. /// list must make the same changes to the animated list in terms of
    104. /// [AnimatedListState.insertItem] and [AnimatedList.removeItem].
    105. class ListModel<E> {
    106. ListModel({
    107. @required this.listKey,
    108. @required this.removedItemBuilder,
    109. Iterable<E> initialItems,
    110. }) : assert(listKey != null),
    111. assert(removedItemBuilder != null),
    112. _items = new List<E>.from(initialItems ?? <E>[]);
    113. final GlobalKey<AnimatedListState> listKey;
    114. final dynamic removedItemBuilder;
    115. final List<E> _items;
    116. AnimatedListState get _animatedList => listKey.currentState;
    117. void insert(int index, E item) {
    118. _items.insert(index, item);
    119. _animatedList.insertItem(index);
    120. }
    121. E removeAt(int index) {
    122. final E removedItem = _items.removeAt(index);
    123. if (removedItem != null) {
    124. _animatedList.removeItem(index, (BuildContext context, Animation<double> animation) {
    125. return removedItemBuilder(removedItem, context, animation);
    126. });
    127. }
    128. return removedItem;
    129. }
    130. int get length => _items.length;
    131. E operator [](int index) => _items[index];
    132. int indexOf(E item) => _items.indexOf(item);
    133. }
    134. /// Displays its integer item as 'item N' on a Card whose color is based on
    135. /// the item's value. The text is displayed in bright green if selected is true.
    136. /// This widget's height is based on the animation parameter, it varies
    137. /// from 0 to 128 as the animation varies from 0.0 to 1.0.
    138. class CardItem extends StatelessWidget {
    139. const CardItem({
    140. Key key,
    141. @required this.animation,
    142. this.onTap,
    143. @required this.item,
    144. this.selected: false
    145. }) : assert(animation != null),
    146. assert(item != null && item >= 0),
    147. assert(selected != null),
    148. super(key: key);
    149. final Animation<double> animation;
    150. final VoidCallback onTap;
    151. final int item;
    152. final bool selected;
    153. @override
    154. Widget build(BuildContext context) {
    155. TextStyle textStyle = Theme.of(context).textTheme.display1;
    156. if (selected)
    157. textStyle = textStyle.copyWith(color: Colors.lightGreenAccent[400]);
    158. return new Padding(
    159. padding: const EdgeInsets.all(2.0),
    160. child: new SizeTransition(
    161. axis: Axis.vertical,
    162. sizeFactor: animation,
    163. child: new GestureDetector(
    164. behavior: HitTestBehavior.opaque,
    165. onTap: onTap,
    166. child: new SizedBox(
    167. height: 128.0,
    168. child: new Card(
    169. color: Colors.primaries[item % Colors.primaries.length],
    170. child: new Center(
    171. child: new Text('Item $item', style: textStyle),
    172. ),
    173. ),
    174. ),
    175. ),
    176. ),
    177. );
    178. }
    179. }
    180. void main() {
    181. runApp(new AnimatedListSample());
    182. }

    也可以参考: