mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
228 lines
7.0 KiB
Dart
228 lines
7.0 KiB
Dart
// Copyright 2014 The Flutter 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() => _AnimatedListSampleState();
|
|
}
|
|
|
|
class _AnimatedListSampleState extends State<AnimatedListSample> {
|
|
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
|
|
ListModel<int> _list;
|
|
int _selectedItem;
|
|
int _nextItem; // The next item inserted when the user presses the '+' button.
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_list = 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 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 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;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('AnimatedList'),
|
|
actions: <Widget>[
|
|
IconButton(
|
|
icon: const Icon(Icons.add_circle),
|
|
onPressed: _insert,
|
|
tooltip: 'insert a new item',
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.remove_circle),
|
|
onPressed: _remove,
|
|
tooltip: 'remove the selected item',
|
|
),
|
|
],
|
|
),
|
|
body: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: 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 = initialItems?.toList() ?? <E>[];
|
|
|
|
final GlobalKey<AnimatedListState> listKey;
|
|
final Widget Function(E item, BuildContext context, Animation<double> animation) 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;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
TextStyle textStyle = Theme.of(context).textTheme.headline4;
|
|
if (selected)
|
|
textStyle = textStyle.copyWith(color: Colors.lightGreenAccent[400]);
|
|
return Padding(
|
|
padding: const EdgeInsets.all(2.0),
|
|
child: SizeTransition(
|
|
axis: Axis.vertical,
|
|
sizeFactor: animation,
|
|
child: GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: onTap,
|
|
child: SizedBox(
|
|
height: 128.0,
|
|
child: Card(
|
|
color: Colors.primaries[item % Colors.primaries.length],
|
|
child: Center(
|
|
child: Text('Item $item', style: textStyle),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
runApp(AnimatedListSample());
|
|
}
|
|
|
|
/*
|
|
Sample Catalog
|
|
|
|
Title: AnimatedList
|
|
|
|
Summary: An AnimatedList for displaying a list of cards that stay
|
|
in sync with an app-specific ListModel. When an item is added to or removed
|
|
from the model, the corresponding card animates in or out of view.
|
|
|
|
Description:
|
|
Tap an item to select it, tap it again to unselect. Tap '+' to insert at the
|
|
selected item, '-' to remove the selected item. The tap handlers add or
|
|
remove items from a `ListModel<E>`, a simple encapsulation of `List<E>`
|
|
that keeps the AnimatedList in sync. The list model has a GlobalKey for
|
|
its animated list. It uses the key to call the insertItem and removeItem
|
|
methods defined by AnimatedListState.
|
|
|
|
Classes: AnimatedList, AnimatedListState
|
|
|
|
Sample: AnimatedListSample
|
|
|
|
See also:
|
|
- The "Components-Lists: Controls" section of the material design specification:
|
|
<https://material.io/guidelines/components/lists-controls.html#>
|
|
*/
|