Flutter Animation for AppBar text in PageView - flutter

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,
),
),
);
},
),
);
}
}

Related

AnimatedList unnecessary rebuilds

I'm animating the removal and addition of items to my list using an AnimatedList whose data is from a firestore stream.
I use riverpod to manage my state and noticed my List state rebuilds whenever I navigator to another screen. It also rebuilds on hot reload.
StateNotifier class for Nav bar
class NavigationState extends StateNotifier<Widget> {
NavigationState() : super(_homeScreen);
static const HomeScreen _homeScreen = HomeScreen();
static const SettingScreen _settingScreen = SettingScreen();
static Screens _page = Screens.home;
Screens get page => _page;
void selectPage(Screens page) {
switch (page) {
case Screens.home:
_page = Screens.home;
state = _homeScreen;
break;
case Screens.settings:
_page = Screens.settings;
state = _settingScreen;
}
}
}
Nav bar class
class CustomNav extends ConsumerWidget {
const CustomNav({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final navIndex = ref.watch(navigationStateProvider.notifier);
return AnimatedContainer(
key: UniqueKey(),
curve: Curves.ease,
duration: const Duration(milliseconds: 1000),
padding: const EdgeInsets.only(top: 5),
height: 58,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
NavigationIcon(
icon: "assets/svg/home.svg",
color: navIndex.page == Screens.home
? Colors.green
: const Color.fromRGBO(133, 144, 132, 1),
label: "Home",
onPressed: () {
ref.read(navigationStateProvider.notifier).selectPage(
Screens.home);
},
),
NavigationIcon(
key: UniqueKey(),
icon: "assets/svg/setting.svg",
color: navIndex.page == Screens.settings
? Colors.green
: const Color.fromRGBO(133, 144, 132, 1),
label: "Settings",
onPressed: () {
ref
.read(navigationStateProvider.notifier)
.selectPage(Screens.settings);
}),
],
),
);
}
}
Home Screen
class HomeScreen extends ConsumerStatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
ConsumerState<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends ConsumerState<HomeScreen>
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
late final TabController _tabController;
final List<UserProperties> _firebaseList = [];
final pref = Preferences();
final GlobalKey<AnimatedListState> _key = GlobalKey();
#override
void initState() {
_tabController = TabController(length: 2, vsync: this);
super.initState();
updateKeepAlive();
}
void _addItem(List<QueryDocumentSnapshot> data) {
for (var element in data) {
final userProperties =
UserProperites.fromJson(element.data() as Map<String, dynamic>);
if (!_firebaseList.contains(userProperties)) {
_firebaseList.add(userProperties);
final itemIndex = data.indexOf(element);
_key.currentState
?.insertItem(itemIndex, duration: const Duration(seconds: 5));
}
}
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
}
#override
Widget build(BuildContext context) {
super.build(context);
final selectedIndex = ref.watch(tabIndexProvider.notifier);
final checkBoxState = ref.watch(mainCheckBoxProvider.state);
_tabController.addListener(() {
checkBoxState.update((state) => false);
selectedIndex.state = _tabController.index;
});
final userId = pref.pref.getString("userId");
return Scaffold(
key: UniqueKey(),
body: Column(
key: UniqueKey(),
children: [
const SizedBox(
height: 15,
),
Expanded(
child: StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
key: UniqueKey(),
stream: FirebaseFirestore.instance
.collection("users")
.doc(userId!)
.collection("properties")
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container();
}
var data = snapshot.data!.docs;
_addItem(data);
return AnimatedList(
padding: const EdgeInsets.only(bottom: 100),
key: _key,
initialItemCount: _firebaseList.length,
itemBuilder: (context, index, animation) {
final userPro = _firebaseList.elementAt(index);
return Padding(
padding: const EdgeInsets.all(2.0),
child: GestureDetector(
onTap: () => Navigator.pushNamed(
context, "details"),
child: PropertyTile(
key: ObjectKey(index),
image: user.image,
name: user.name),
),
),
);
});
}),
),
],
),
);
}
#override
bool get wantKeepAlive => true;
}
I've been on this issue for a while, that's part of the reason UniqueKey() is littered everywhere.
I thought of using Provider.select, but I don't really know what to select since I'm using an enum.
Prior to AnimatedList, everything works fine, and no unnecessary rebuilds.

StreamBuilder not updating widget

i have a Widget that has an AppBar with a progress bar, and a PageView with 4 pages, when moving between pages i am increasing / decreasing the progress bar.
I'm trying to do all the logic in my ViewModel.
This is my ViewModel (omitted non relevant stuff):
class RegisterViewModel extends BaseViewModel with RegisterViewModelInputs, RegisterViewModelOutputs {
final StreamController _sexStreamController = StreamController<int>.broadcast();
final StreamController _progressBarController = StreamController<double>.broadcast();
final StreamController _currentIndexController = StreamController<int>.broadcast();
final StreamController _isBackEnabled = StreamController<bool>.broadcast();
double _progress = 0.25;
int _index = 0;
#override
setCurrentIndex(int index) {
currentIndex.add(index);
}
#override
increaseProgress() {
if (_progress <= 1.0) {
_progress += 0.25;
progress.add(_progress);
}
}
#override
decreaseProgress() {
if (_progress > 0) {
_progress -= 0.25;
progress.add(_progress);
}
}
#override
setIsBackEnabled(int index) {
_isBackEnabled.add(index > 0 ? true : false);
}
#override
nextPage() {
if (_index < 4) {
_index++;
increaseProgress();
setCurrentIndex(_index);
}
}
#override
previousPage() {
if (_index > 0) {
_index--;
decreaseProgress();
setCurrentIndex(_index);
}
}
#override
Sink get currentIndex => _currentIndexController.sink;
#override
Sink get progress => _progressBarController.sink;
#override
Sink get isBackEnabled => _isBackEnabled.sink;
#override
Stream<int> get outputCurrentIndex => _currentIndexController.stream.map((currentIndex) => currentIndex);
#override
Stream<double> get outputProgress => _progressBarController.stream.map((progress) => progress);
#override
Stream<bool> get outputIsBackEnabled => outputIsBackEnabled.map((isEnabled) => isEnabled);
}
And here is my View:
class RegisterView extends StatefulWidget {
const RegisterView({Key? key}) : super(key: key);
#override
_RegisterViewState createState() => _RegisterViewState();
}
class _RegisterViewState extends State<RegisterView> {
final RegisterViewModel _viewModel = getIt<RegisterViewModel>();
final PageController _pageController = PageController(initialPage: 0);
final FixedExtentScrollController _weightScrollController = FixedExtentScrollController(initialItem: 80);
final FixedExtentScrollController _ageScrollController = FixedExtentScrollController(initialItem: 13);
final FixedExtentScrollController _heightScrollController = FixedExtentScrollController(initialItem: 13);
#override
void initState() {
_bind();
super.initState();
}
#override
void dispose() {
_viewModel.dispose();
super.dispose();
}
_bind() {
_viewModel.start();
}
#override
Widget build(BuildContext context) {
_viewModel.outputCurrentIndex.listen((index) {
_pageController.animateToPage(index, duration: const Duration(milliseconds: 1000), curve: Curves.ease);
});
List<Widget> pagesList = [
SexPage(
onConfirm: (sex) {
_viewModel.setSex(sex);
_viewModel.nextPage();
},
),
AgePage(
scrollController: _ageScrollController,
),
WeightPage(scrollController: _weightScrollController),
HeightPage(scrollController: _heightScrollController),
];
return Scaffold(
backgroundColor: ColorManager.backgroundColor,
appBar: AppBar(
systemOverlayStyle: SystemUiOverlayStyle(
statusBarColor: ColorManager.backgroundColor,
statusBarBrightness: Brightness.dark,
statusBarIconBrightness: Brightness.dark,
),
centerTitle: true,
title: AppBarWidget(_pageController),
elevation: AppSize.s0,
),
body: PageView(
reverse: true,
controller: _pageController,
physics: NeverScrollableScrollPhysics(),
children: [...pagesList],
),
);
}
}
class AppBarWidget extends StatelessWidget {
final PageController pageController;
final RegisterViewModel _viewModel = getIt<RegisterViewModel>();
AppBarWidget(
this.pageController, {
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
_viewModel.outputCurrentIndex.listen((index) {
pageController.animateToPage(index, duration: const Duration(milliseconds: 1000), curve: Curves.ease);
});
return Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
flex: 1,
child: InkWell(
child: Text(
AppStrings.skip,
style: Theme.of(context).textTheme.labelMedium,
),
onTap: () => _viewModel.nextPage(),
),
),
Expanded(
flex: 4,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: AppPadding.p60),
child: Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(pi),
child: StreamBuilder<double>(
stream: _viewModel.outputProgress,
builder: (context, snapshot) {
return Progresso(
progress: snapshot.data ?? 0,
progressStrokeCap: StrokeCap.round,
backgroundStrokeCap: StrokeCap.round,
progressColor: ColorManager.primary,
backgroundColor: ColorManager.progressBarBackgroundGrey,
progressStrokeWidth: 10.0,
backgroundStrokeWidth: 10.0,
);
}),
),
),
),
StreamBuilder<int>(
stream: _viewModel.outputCurrentIndex,
builder: (context, snapshot) {
return Expanded(
flex: 1,
child: (snapshot.data ?? 0) > 0
? InkWell(
child: Row(
children: [
Text(
AppStrings.back,
style: Theme.of(context).textTheme.labelMedium,
),
Icon(
Icons.arrow_forward_ios,
color: ColorManager.subtitleGrey,
),
],
),
onTap: () => _viewModel.previousPage(),
)
: Container(),
);
}),
],
);
}
}
When i'm calling _viewModel.previousPage() & _viewModel.previousPage()` from the AppBarWidget, the progress bar view is updated, and there is a scroll animation to the next page.
But for some reason if the onConfirm callback:
onConfirm: (sex) {
_viewModel.setSex(sex);
_viewModel.nextPage();
}
is called from within SexPage, the scroll animation is working, but the progress bar view and the isBackEnabled is not updating.
I have checked and a new value is being added to the _progressBarController sink, but for some reason the StreamBuilder does not receive it? same for the isBackEnabled stream..
What am i doing wrong?
And another question i have is where should I listen to the outputCurrentIndex stream, and call _pageController.animateToPage()?
Apparently i had an issue with my Dependency Injection.
I'm using get_it and i used registerFactory, instead of registerLazySingleton.
Which probably made me have 2 separate ViewModels in each widget.

Flutter: change color for first item of the list in the top when stop scrolling in listview

I have the example code below.
class ExampleScroll extends StatefulWidget {
const ExampleScroll({Key? key}) : super(key: key);
#override
_ExampleScrollState createState() => _ExampleScrollState();
}
class _ExampleScrollState extends State<ExampleScroll> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return Card(
child: Container(
height: 100,
color: //TODO: change the color only first item of the list when stoping the scroll
alignment: Alignment.center,
child: Text(
index.toString(),
style:
const TextStyle(fontSize: 36.0, fontWeight: FontWeight.bold),
),
),
);
},
),
),
);
}
}
Screenshot:
If see the above screenshot, I stop the scrolling 3x times.
First, when the initial (open this screen), the first item of the list in the top is index 0
Second, when I stop and the first item of the list in the top is index 13
Third, when I stop and the first item of the list in the top is index 17.
So when I scroll the list and stop, first item of the list in the top should be change to green, and the others is white.
For above screenshot (example) I stop 3x times and the first item of the list in the top is in the index 0, 13, 17, so it's change to green.
1. Using ScrollController
class ExampleScroll extends StatefulWidget {
const ExampleScroll({Key? key}) : super(key: key);
#override
_ExampleScrollState createState() => _ExampleScrollState();
}
class _ExampleScrollState extends State<ExampleScroll> {
late ScrollController _scrollController;
final int _itemCount = 20;
int _cardPosition = 0;
void _scrollListenerWithItemCount() {
int itemCount = _itemCount;
double scrollOffset = _scrollController.position.pixels;
double viewportHeight = _scrollController.position.viewportDimension;
double scrollRange = _scrollController.position.maxScrollExtent -
_scrollController.position.minScrollExtent;
int firstVisibleItemIndex =
(scrollOffset / (scrollRange + viewportHeight) * itemCount).floor();
if (_scrollController.position.atEdge) {
bool isTop = _scrollController.position.pixels == 0;
if (isTop) {
_cardPosition = 0;
} else {
_cardPosition = firstVisibleItemIndex + 1;
}
} else {
_cardPosition = firstVisibleItemIndex + 1;
}
setState(() {});
}
#override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(_scrollListenerWithItemCount);
}
#override
void dispose() {
super.dispose();
_scrollController.removeListener(_scrollListenerWithItemCount);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView.builder(
controller: _scrollController,
itemCount: _itemCount,
itemBuilder: (context, index) {
return Card(
child: Container(
height: 100,
color: _cardPosition == index ? Colors.green : Colors.white,
alignment: Alignment.center,
child: Text(
index.toString(),
style: const TextStyle(
fontSize: 36.0, fontWeight: FontWeight.bold),
),
),
);
},
),
),
);
}
}
References:
https://github.com/flutter/flutter/issues/19941#issuecomment-522587489
https://stackoverflow.com/a/54539182/8291686
2. Using Area
package: https://pub.dev/packages/inview_notifier_list
I'm not sure if I understand what you mean exactly. but try this out:
class ExampleScroll extends StatefulWidget {
const ExampleScroll({Key? key}) : super(key: key);
#override
_ExampleScrollState createState() => _ExampleScrollState();
}
class _ExampleScrollState extends State<ExampleScroll> {
Timer? timer;
ScrollController controller = ScrollController();
#override
void initState() {
controller.addListener(() {
if (timer != null) {
timer!.cancel();
timer = Timer(Duration(milliseconds: 300), () {
setState(() {
timer = null;
});
});
} else {
timer = Timer(Duration(milliseconds: 300), () {
setState(() {
timer = null;
});
});
}
setState(() {});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView.builder(
itemCount: 20,
controller: controller,
itemBuilder: (context, index) {
return Card(
child: Container(
height: 100,
color: timer == null
? Colors.black
: Colors
.green, // I want change this color only for first item
alignment: Alignment.center,
child: Text(
index.toString(),
style: const TextStyle(
fontSize: 36.0, fontWeight: FontWeight.bold),
),
),
);
},
),
),
);
}
}
in this code. all tiles change color. for changing only top tile. I recommend using VisibilityDetector

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 Listview issue adding items at the top

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.