AnimatedList显示与ListModel保持同步的卡片列表。当新的item被添加到ListModel或从ListModel中删除时,相应的卡片在UI上也会被添加或删除,并伴有动画效果。
点击一个item选择它,再次点击它会取消选择。点击’+’插入选定的item,点击’ - ‘删除选定的item。
tap处理器会从ListModel<E>中添加或删除items,ListModel<E>是List<E>的简单封装 ,用于保持和AnimatedList的同步。
列表模型为其动画列表提供了一个GlobalKey。它使用该键来调用由AnimatedListState定义的insertItem和removeItem方法。
通过flutter create命令创建一个新项目,并用下面的代码替换lib/main.dart的内容来尝试运行一下。
// Copyright 2017 The Chromium Authors. All rights reserved.// Use of this source code is governed by a BSD-style license that can be// found in the LICENSE file.import 'package:flutter/foundation.dart';import 'package:flutter/material.dart';class AnimatedListSample extends StatefulWidget {@override_AnimatedListSampleState createState() => new _AnimatedListSampleState();}class _AnimatedListSampleState extends State<AnimatedListSample> {final GlobalKey<AnimatedListState> _listKey = new GlobalKey<AnimatedListState>();ListModel<int> _list;int _selectedItem;int _nextItem; // The next item inserted when the user presses the '+' button.@overridevoid initState() {super.initState();_list = new ListModel<int>(listKey: _listKey,initialItems: <int>[0, 1, 2],removedItemBuilder: _buildRemovedItem,);_nextItem = 3;}// Used to build list items that haven't been removed.Widget _buildItem(BuildContext context, int index, Animation<double> animation) {return new CardItem(animation: animation,item: _list[index],selected: _selectedItem == _list[index],onTap: () {setState(() {_selectedItem = _selectedItem == _list[index] ? null : _list[index];});},);}// Used to build an item after it has been removed from the list. This method is// needed because a removed item remains visible until its animation has// completed (even though it's gone as far this ListModel is concerned).// The widget will be used by the [AnimatedListState.removeItem] method's// [AnimatedListRemovedItemBuilder] parameter.Widget _buildRemovedItem(int item, BuildContext context, Animation<double> animation) {return new CardItem(animation: animation,item: item,selected: false,// No gesture detector here: we don't want removed items to be interactive.);}// Insert the "next item" into the list model.void _insert() {final int index = _selectedItem == null ? _list.length : _list.indexOf(_selectedItem);_list.insert(index, _nextItem++);}// Remove the selected item from the list model.void _remove() {if (_selectedItem != null) {_list.removeAt(_list.indexOf(_selectedItem));setState(() {_selectedItem = null;});}}@overrideWidget build(BuildContext context) {return new MaterialApp(home: new Scaffold(appBar: new AppBar(title: const Text('AnimatedList'),actions: <Widget>[new IconButton(icon: const Icon(Icons.add_circle),onPressed: _insert,tooltip: 'insert a new item',),new IconButton(icon: const Icon(Icons.remove_circle),onPressed: _remove,tooltip: 'remove the selected item',),],),body: new Padding(padding: const EdgeInsets.all(16.0),child: new AnimatedList(key: _listKey,initialItemCount: _list.length,itemBuilder: _buildItem,),),),);}}/// Keeps a Dart List in sync with an AnimatedList.////// The [insert] and [removeAt] methods apply to both the internal list and the/// animated list that belongs to [listKey].////// This class only exposes as much of the Dart List API as is needed by the/// sample app. More list methods are easily added, however methods that mutate the/// list must make the same changes to the animated list in terms of/// [AnimatedListState.insertItem] and [AnimatedList.removeItem].class ListModel<E> {ListModel({@required this.listKey,@required this.removedItemBuilder,Iterable<E> initialItems,}) : assert(listKey != null),assert(removedItemBuilder != null),_items = new List<E>.from(initialItems ?? <E>[]);final GlobalKey<AnimatedListState> listKey;final dynamic removedItemBuilder;final List<E> _items;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, (BuildContext context, Animation<double> animation) {return removedItemBuilder(removedItem, context, animation);});}return removedItem;}int get length => _items.length;E operator [](int index) => _items[index];int indexOf(E item) => _items.indexOf(item);}/// Displays its integer item as 'item N' on a Card whose color is based on/// the item's value. The text is displayed in bright green if selected is true./// This widget's height is based on the animation parameter, it varies/// from 0 to 128 as the animation varies from 0.0 to 1.0.class CardItem extends StatelessWidget {const CardItem({Key key,@required this.animation,this.onTap,@required this.item,this.selected: false}) : assert(animation != null),assert(item != null && item >= 0),assert(selected != null),super(key: key);final Animation<double> animation;final VoidCallback onTap;final int item;final bool selected;@overrideWidget build(BuildContext context) {TextStyle textStyle = Theme.of(context).textTheme.display1;if (selected)textStyle = textStyle.copyWith(color: Colors.lightGreenAccent[400]);return new Padding(padding: const EdgeInsets.all(2.0),child: new SizeTransition(axis: Axis.vertical,sizeFactor: animation,child: new GestureDetector(behavior: HitTestBehavior.opaque,onTap: onTap,child: new SizedBox(height: 128.0,child: new Card(color: Colors.primaries[item % Colors.primaries.length],child: new Center(child: new Text('Item $item', style: textStyle),),),),),),);}}void main() {runApp(new AnimatedListSample());}
也可以参考:
- Material Design规范中的“Components-Lists:Controls”部分。
- 本示例源代码在 examples/catalog/lib/animated_list.dart.
