Flutter Listview issue adding items at the top - flutter

I'm trying to create a simple lisview with animated items. Everything is working fine, BUT when I tried to add the items at the top the behavior of the animated item is wrong
Here is my state class
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
List<ItemAnimado> lista = [];
void _incrementCounter() {
setState(() {
_counter++;
lista.add(ItemAnimado(_counter.toString()));
//lista.insert(0, ItemAnimado(_counter.toString()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView.builder( itemCount: lista.length, itemBuilder: ( context, index,) {
return lista[index];
},),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Here is my animatedItem
class _ItemAnimadoState extends State<ItemAnimado> with TickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> animation;
#override
void initState() {
super.initState();
_controller = AnimationController(duration: Duration(seconds: 1), vsync: this);
animation = Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(CurvedAnimation(parent: _controller, curve: Curves.bounceOut));
_controller.forward();
}
#override
Widget build(BuildContext context) {
return SlideTransition(
position: animation,
//duration: Duration(seconds: 1),
child: Card(
elevation: 9,
color: Colors.white,
child: ListTile(
title: Text(widget.texto),
),
),
);
}
}
https://giphy.com/gifs/d5Yd3FZFNNKuTr5ku5

I found out how to make the code works as I wanted.
The State class is almost the same. BUT, now I have a globalKey for the AnimatedList.
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
void _incrementCounter() {
setState(() {
Provider.of<ItemProvider>(context).addItem(
ItemAnimado(texto: 'New item'), 0);
});
}
#override
Widget build(BuildContext context) {
final _itemProvider = Provider.of<ItemProvider>(context);
_itemProvider.providerKey(_listKey);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: AnimatedList(
key: _listKey,
initialItemCount: _itemProvider.listaItems.length,
itemBuilder: (context, index, animation) {
return SlideTransition(
//key: _myListKey,
position: animation.drive(Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
)),
child: Card(
elevation: 9,
color: Colors.white,
child: ListTile(
title: Text(_itemProvider.listaItems[index].texto),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
But the most important part is in how I manage the data. I'm using a Provider
I'm sending the Key to the Provider and in here I manage the insertItem inside the addItem function
class ItemProvider with ChangeNotifier {
List<ItemAnimado> _listaItems = [
ItemAnimado(texto: '1'),
ItemAnimado(texto: '2'),
ItemAnimado(texto: '3'),
];
GlobalKey<AnimatedListState> _listKey;
void providerKey(GlobalKey<AnimatedListState> key ){
_listKey = key;
}
List<ItemAnimado> get listaItems {
return [..._listaItems];
}
addItem(ItemAnimado nuevo, int index){
_listaItems.insert(0,nuevo);
_listKey.currentState.insertItem(index);
notifyListeners();
}
}
Here is a Gif with the final result
http://www.giphy.com/gifs/QybBE5E8kUUtxIbsnw

This is a case where you need to use Keys in your widgets. Here's a complete example:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
List<ItemAnimado> lista = [];
void _incrementCounter() {
setState(() {
_counter++;
lista.add(ItemAnimado(_counter.toString(), key: ValueKey(_counter)));
//lista.insert(0, ItemAnimado(_counter.toString()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView.builder( itemCount: lista.length, itemBuilder: ( context, index,) {
return lista[index];
},),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class ItemAnimado extends StatefulWidget {
final String texto;
ItemAnimado(this.texto, {Key key}) : super(key: key);
_ItemAnimadoState createState() => _ItemAnimadoState();
}
class _ItemAnimadoState extends State<ItemAnimado> with TickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> animation;
#override
void initState() {
super.initState();
_controller = AnimationController(duration: Duration(seconds: 1), vsync: this);
animation = Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(CurvedAnimation(parent: _controller, curve: Curves.bounceOut));
_controller.forward();
}
#override
Widget build(BuildContext context) {
return SlideTransition(
position: animation,
//duration: Duration(seconds: 1),
child: Card(
elevation: 9,
color: Colors.white,
child: ListTile(
title: Text(widget.texto),
),
),
);
}
}
Notice where we added key: ValueKey(_counter) in the creation of your ItemAnimado. The key tells Flutter which widgets have already been created and which ones have been newly added.
For more on Keys, see this video.

Related

Flutter Animation for AppBar text in PageView

I want to make the title fade and the title should disappear when the previous screen reaches the mid Screen. The next title should fade till midway and the and a new title for the next screen should fade in. How can I add this animation I am using AnimatedOpacity and AnimatedSwitcher but it doesn't work. The code files are:
The Data Class
import 'package:flutter/material.dart';
class DataModel {
List<String> titles = ['Red1', 'Blue2', 'Green3'];
List<String> content = ['Red1', 'Blue2', 'Green3'];
}
The Main Screen
import 'package:flutter/material.dart';
import 'package:ui_design/model/data.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 100));
animation = CurvedAnimation(parent: parent, curve: curve)
}
//
DataModel dataModel = DataModel();
int currentPageIndex = 0;
bool isPageChanged = true;
//
#override
Widget build(BuildContext context) {
var appBarTitle = dataModel.titles[currentPageIndex];
return Scaffold(
appBar: AppBar(
leading: const Icon(Icons.menu),
title: Text(
appBarTitle,
),
),
body: PageView.builder(
scrollDirection: Axis.horizontal,
itemCount: dataModel.content.length,
onPageChanged: (value) => setState(() {
appBarTitle = dataModel.titles[currentPageIndex];
}),
itemBuilder: (BuildContext context, int index) {
currentPageIndex = index;
print('Current page index $currentPageIndex');
return Container(
alignment: Alignment.center,
child: Text(
dataModel.content[index],
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
);
},
),
);
}
}
// AnimatedSwitcher(
// duration: const Duration(milliseconds: 500),
// transitionBuilder: (Widget child, Animation<double> animation) {
// return FadeTransition(child: child, opacity: animation);
// },
// child: Image.asset(
// imageList[currentIndex.toInt()],
// key: ValueKey<int>(currentIndex),
// ),
You can try widget, I am using PageController and Opcaity on title.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
DataModel dataModel = DataModel();
int currentPageIndex = 0;
double opacity = 1;
late final PageController pageController = PageController(
initialPage: 0,
)..addListener(() {
debugPrint(pageController.page.toString());
opacity =
double.tryParse(pageController.page.toString().substring(1)) ?? 1;
if (opacity < .5) {
opacity = 1 - opacity * 2;
}
setState(() {});
});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: const Icon(Icons.menu),
title: Opacity(
opacity: opacity,
child: Text(
dataModel.titles[currentPageIndex],
),
),
),
body: PageView.builder(
controller: pageController,
scrollDirection: Axis.horizontal,
itemCount: dataModel.content.length,
onPageChanged: (value) {
debugPrint("on page changed: $value");
currentPageIndex = value;
setState(() {});
},
itemBuilder: (BuildContext context, int index) {
return Container(
alignment: Alignment.center,
child: Text(
dataModel.content[index],
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
);
},
),
);
}
}

Animating title change in ExpansionTile

I have an ExpansionTile that have different titles in expanded\collapsed state.
class _ExpandablePaneState extends State<ExpandablePane>
with SingleTickerProviderStateMixin {
bool isExpanded = false;
AnimationController _controller;
Animation<double> _iconTurns;
static final Animatable<double> _easeInTween =
CurveTween(curve: Curves.easeIn);
static final Animatable<double> _halfTween =
Tween<double>(begin: 0.0, end: 0.5);
Duration _kExpand = Duration(milliseconds: 250);
Widget _myAnimatedWidget;
#override
void initState() {
super.initState();
_controller = AnimationController(duration: _kExpand, vsync: this);
_iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
_controller.value = 0.0;
_myAnimatedWidget = widget.collapsedTitle;
}
#override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
onExpansionChanged: (value) {
if (value) {
_controller.forward();
} else {
_controller.reverse();
}
setState(() {
isExpanded = value;
_myAnimatedWidget =
isExpanded ? widget.expandedTitle : widget.collapsedTitle;
});
},
title: Expanded(
child: Stack(children: [
AnimatedSwitcher(
duration: Duration(milliseconds: 2500),
transitionBuilder: (child, animation) => ScaleTransition(
child: child,
scale: animation,
),
child: _myAnimatedWidget,
),
Positioned.fill(
child: Align(
alignment: Alignment.centerRight,
child: RotationTransition(
turns: _iconTurns,
child: const Icon(Icons.expand_more),
),
),
)
]),
),
children: widget.content,
),
);
}
}
I want to make an animation between these states, how I can achieve it?
I tried AnimatedSwitcher, but it didn't work. I'm totally don't see an animation.
You can copy paste run full code below
You can wrap _myAnimatedWidget with Container and provide key: ValueKey<bool>(isExpanded)
From official example https://api.flutter.dev/flutter/widgets/AnimatedSwitcher-class.html
This key causes the AnimatedSwitcher to interpret this as a "new"
child each time the count changes, so that it will begin its animation
when the count changes.
I also remove Expanded in title
code snippet
child: Container(
key: ValueKey<bool>(isExpanded), child: _myAnimatedWidget),
working demo
full code
import 'package:flutter/material.dart';
class ExpandablePane extends StatefulWidget {
Widget expandedTitle;
Widget collapsedTitle;
List<Widget> content;
ExpandablePane({this.expandedTitle, this.collapsedTitle, this.content});
#override
_ExpandablePaneState createState() => _ExpandablePaneState();
}
class _ExpandablePaneState extends State<ExpandablePane>
with SingleTickerProviderStateMixin {
bool isExpanded = false;
AnimationController _controller;
Animation<double> _iconTurns;
static final Animatable<double> _easeInTween =
CurveTween(curve: Curves.easeIn);
static final Animatable<double> _halfTween =
Tween<double>(begin: 0.0, end: 0.5);
Duration _kExpand = Duration(milliseconds: 250);
Widget _myAnimatedWidget;
#override
void initState() {
super.initState();
_controller = AnimationController(duration: _kExpand, vsync: this);
_iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
_controller.value = 0.0;
_myAnimatedWidget = widget.collapsedTitle;
}
#override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
onExpansionChanged: (value) {
if (value) {
_controller.forward();
} else {
_controller.reverse();
}
setState(() {
isExpanded = value;
_myAnimatedWidget =
isExpanded ? widget.expandedTitle : widget.collapsedTitle;
});
},
title: Stack(children: [
AnimatedSwitcher(
duration: Duration(milliseconds: 2500),
transitionBuilder: (child, animation) => ScaleTransition(
child: child,
scale: animation,
),
child: Container(
key: ValueKey<bool>(isExpanded), child: _myAnimatedWidget),
),
Positioned.fill(
child: Align(
alignment: Alignment.centerRight,
child: RotationTransition(
turns: _iconTurns,
child: const Icon(Icons.expand_more),
),
),
)
]),
children: widget.content,
),
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
ExpandablePane(
expandedTitle: Text("expand"),
collapsedTitle: Text("collapsed"),
content: [Text("1"), Text("2"), Text("3")],
),
],
),
),
);
}
}

Flutter can't keep the state of tabs using PageTransitionSwitcher

I am struggling with animations package and I want to use animation with BottomNavigationBar.
Without animation, I can save my state using IndexedStack.
If I wrap IndexedStack inside PageTransitionSwitcher it doesn't work. In particular:
animations are not showing but state is kept
if I use key property of my IndexedStack, animations are showing but state is not working.
How can i fix it? I don't know how to set up keys.
Thank you very much!!
class MainScreen extends StatefulWidget {
static String id = 'loading_screen';
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _selectedPage = 0;
List<Widget> pageList = List<Widget>();
#override
void initState() {
pageList.add(PrimoTab());
pageList.add(SecondoTab());
pageList.add(TerzoTab());
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Bottom tab'),
),
body: PageTransitionSwitcher(
transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
child: child,
transitionType: SharedAxisTransitionType.horizontal,
);
},
child: IndexedStack(
index: _selectedPage,
children: pageList,
//key: ValueKey<int>(_selectedPage), NOT WORKING
),
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.directions_car),
label: 'First Page',
),
BottomNavigationBarItem(
icon: Icon(Icons.airplanemode_active),
label: 'Second Page',
),
BottomNavigationBarItem(
icon: Icon(Icons.directions_bike),
label: 'Third Page',
),
],
currentIndex: _selectedPage,
selectedItemColor: Colors.lightGreen,
unselectedItemColor: Colors.lightBlueAccent,
onTap: _onItemTapped,
),
);
}
void _onItemTapped(int index) {
setState(() {
_selectedPage = index;
});
}
}
Each tab is just a column with two text widgets, a button, and a text counter (for testing the state of each tab):
class PrimoTab extends StatefulWidget {
#override
_PrimoTabState createState() => _PrimoTabState();
}
class _PrimoTabState extends State<PrimoTab> {
int cont = -1;
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'TAB 1 - TEXT 1',
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'TAB 1 - TEXT 2',
),
),
FlatButton(
onPressed: () {
setState(() {
cont++;
});
},
child: Text("CLICK"),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Valore contatore $cont',
),
),
],
),
);
}
}
UPDATE 1: Using just
pageList[_selectedPage],
instead of
IndexedStack(
...
)
but not working (animations ok but state is not kept)
UPDATE 2 WITH SOLUTION (main.dart):
void main() {
runApp(
MaterialApp(
home: MainScreen(),
),
);
}
class MainScreen extends StatefulWidget {
static String id = 'loading_screen';
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _selectedPage = 0;
List<Widget> pageList = List<Widget>();
#override
void initState() {
pageList.add(PrimoTab());
pageList.add(SecondoTab());
pageList.add(TerzoTab());
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Bottom tab'),
),
body: AnimatedIndexedStack(
index: _selectedPage,
children: pageList,
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.directions_car),
label: 'First Page',
),
BottomNavigationBarItem(
icon: Icon(Icons.airplanemode_active),
label: 'Second Page',
),
BottomNavigationBarItem(
icon: Icon(Icons.directions_bike),
label: 'Third Page',
),
],
currentIndex: _selectedPage,
selectedItemColor: Colors.lightGreen,
unselectedItemColor: Colors.lightBlueAccent,
onTap: _onItemTapped,
),
);
}
void _onItemTapped(int index) {
setState(() {
_selectedPage = index;
});
}
}
class AnimatedIndexedStack extends StatefulWidget {
final int index;
final List<Widget> children;
const AnimatedIndexedStack({
Key key,
this.index,
this.children,
}) : super(key: key);
#override
_AnimatedIndexedStackState createState() => _AnimatedIndexedStackState();
}
class _AnimatedIndexedStackState extends State<AnimatedIndexedStack>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
int _index;
#override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 150),
);
_animation = Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.ease,
),
);
_index = widget.index;
_controller.forward();
super.initState();
}
#override
void didUpdateWidget(AnimatedIndexedStack oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.index != _index) {
_controller.reverse().then((_) {
setState(() => _index = widget.index);
_controller.forward();
});
}
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Opacity(
opacity: _controller.value,
child: Transform.scale(
scale: 1.015 - (_controller.value * 0.015),
child: child,
),
);
},
child: IndexedStack(
index: _index,
children: widget.children,
),
);
}
}
There are few ways to fix your situation. None of them is perfectly simple and there already lots of discussion here and here, trying to merge IndexedStack with PageTransitionSwitcher. No solution so far I saw.
I collect following possible ways to achieve this:
Store state somewhere else and pass into child. I haven't seen any method can stop PageTransitionSwitcher from rebuilding child widget. If you don't mine the child widget rebuild, it may be the most straight forward method to do with this.
Use Custom IndexedStack with animation. like this and this. It works well with the feature in IndexStack that children won't rebuild, but the animation is not as good as PageTransitionSwitcher and it can only show 1 widget in one time.
This was the closest solution I found. You get little animation while preserving the state of the page
class AnimatedIndexedStack extends StatefulWidget {
final int index;
final List<Widget> children;
const AnimatedIndexedStack({
Key key,
this.index,
this.children,
}) : super(key: key);
#override
_AnimatedIndexedStackState createState() => _AnimatedIndexedStackState();
}
class _AnimatedIndexedStackState extends State<AnimatedIndexedStack>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
int _index;
#override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 150),
);
_animation = Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.ease,
),
);
_index = widget.index;
_controller.forward();
super.initState();
}
#override
void didUpdateWidget(AnimatedIndexedStack oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.index != _index) {
_controller.reverse().then((_) {
setState(() => _index = widget.index);
_controller.forward();
});
}
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Opacity(
opacity: _controller.value,
child: Transform.scale(
scale: 1.015 - (_controller.value * 0.015),
child: child,
),
);
},
child: IndexedStack(
index: _index,
children: widget.children,
),
);
}
}

How to make sure a widget is visible on the screen?

In my Application, I have an AnimatedList in one of my pages. Items are being added to the list by pressing a button. I animate the scrollView when an Item is being inserted to the list. Sometimes the list grows How can I find out than an Item is still visible on the screen at a moment when the list gets too long?
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:async';
class AnimatedListSample extends StatefulWidget {
#override
_AnimatedListSampleState createState() => _AnimatedListSampleState();
}
class _AnimatedListSampleState extends State<AnimatedListSample> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
ListModel<int> _list;
final ScrollController _controller = ScrollController();
int _nextItem;
#override
void initState() {
super.initState();
_list = ListModel<int>(
listKey: _listKey,
initialItems: <int>[0, 1, 2],
);
_nextItem = 3;
}
Widget _buildItem(
BuildContext context, int index, Animation<double> animation) {
return CardItem(
animation: animation,
item: _list[index],
);
}
void _insert() {
_list.insert(_list.length, _list.length + 1);
Timer(
Duration(milliseconds: 300),
() => _controller.animateTo(
_controller.position.maxScrollExtent,
curve: Curves.easeIn,
duration: const Duration(milliseconds: 300),
));
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('AnimatedList'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.add_circle),
onPressed: _insert,
)
],
),
body: AnimatedList(
controller: _controller,
key: _listKey,
initialItemCount: _list.length,
itemBuilder: _buildItem,
),
),
);
}
}
class ListModel<E> {
ListModel({
#required this.listKey,
Iterable<E> initialItems,
}) : assert(listKey != null),
_items = List<E>.from(initialItems ?? <E>[]);
final GlobalKey<AnimatedListState> listKey;
final List<E> _items;
AnimatedListState get _animatedList => listKey.currentState;
void insert(int index, E item) {
_items.insert(index, item);
_animatedList.insertItem(index);
}
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, #required this.animation, #required this.item})
: assert(animation != null),
assert(item != null && item >= 0),
super(key: key);
final Animation<double> animation;
final int item;
#override
Widget build(BuildContext context) {
TextStyle textStyle = Theme.of(context).textTheme.headline4;
return SizeTransition(
axis: Axis.vertical,
sizeFactor: animation,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
child: SizedBox(
height: 80,
child: Card(
color: Colors.primaries[item % Colors.primaries.length],
child: Center(
child: Text('Item $item', style: textStyle),
),
),
),
),
);
}
}
You can use the visibility_detector package, which fires a callback whenever the visibility of a widget changes. So you can wrap each of the widgets in your list with the VisibilityDetector widget and have the callback change the state as the visibility changes. You can then handle visibility changes however you need based on your application.

Flutter: Spinning sync icon in AppBar

How can I animate an IconButton placed in a AppBar? The sync icon should spinning while a database synchronisation is running.
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Dashboard"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.sync), // <-- Icon
onPressed: () {
print("sync");
// start spinning
syncDatabaseFull(); // Returns future and resolves when sync is finish
},
)
],
),
body: Center(
child: RaisedButton(
child: Text('HOME screen'),
onPressed: () {
},
),
),
);
}
}
You can copy paste run full code below
You can extend AnimatedWidget and pass callback
example code below simulate syncDatabaseFull run for 5 seconds
code snippet
class AnimatedSync extends AnimatedWidget {
VoidCallback callback;
AnimatedSync({Key key, Animation<double> animation, this.callback})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return Transform.rotate(
angle: animation.value,
child: IconButton(
icon: Icon(Icons.sync), // <-- Icon
onPressed: () => callback()),
);
}
}
actions: <Widget>[
AnimatedSync(
animation: rotateAnimation,
callback: () async{
controller.forward();
await syncDatabaseFull();
controller.stop();
controller.reset();
},
),
],
working demo
full code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class AnimatedSync extends AnimatedWidget {
VoidCallback callback;
AnimatedSync({Key key, Animation<double> animation, this.callback})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return Transform.rotate(
angle: animation.value,
child: IconButton(
icon: Icon(Icons.sync), // <-- Icon
onPressed: () => callback()),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation colorAnimation;
Animation rotateAnimation;
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
Future<bool> syncDatabaseFull() async{
await Future.delayed(Duration(seconds: 5), () {
});
return Future.value(true);
}
#override
void initState() {
controller =
AnimationController(vsync: this, duration: Duration(seconds: 200));
rotateAnimation = Tween<double>(begin: 0.0, end: 360.0).animate(controller);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
AnimatedSync(
animation: rotateAnimation,
callback: () async{
controller.forward();
await syncDatabaseFull();
controller.stop();
controller.reset();
},
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
CustomedSpinningIconButton class
import 'package:flutter/material.dart';
class SpinningIconButton extends AnimatedWidget {
final VoidCallback onPressed;
final IconData iconData;
final AnimationController controller;
SpinningIconButton({Key key, this.controller, this.iconData, this.onPressed})
: super(key: key, listenable: controller);
Widget build(BuildContext context) {
final Animation<double> _animation = CurvedAnimation(
parent: controller,
// Use whatever curve you would like, for more details refer to the Curves class
curve: Curves.linearToEaseOut,
);
return RotationTransition(
turns: _animation,
child: IconButton(
icon: Icon(iconData),
onPressed: onPressed,
),
);
}
}
How to use it:
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
AnimationController _animationController;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 1)
);
}
#override
Widget build(BuildContext context) {
...
actions: <Widget>[
SpinningIconButton(
controller: _animationController,
iconData: Icons.sync,
onPressed: () async {
// Play the animation infinitely
_animationController.repeat();
// Sleep 1.5 seconds or await the Async method
print('Something has finished.');
// Complete current cycle of the animation
_animationController.forward(from: _animationController.value);
},
)
],
...
}