Flutter ScrollController maxScrollExtent equals to 0.0 - flutter

I created a design for a chat application, and tried to scroll to end automatically, I used this code
scrollController.animateTo(
scrollController.position.maxScrollExtent,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 500),
);
nothing happens, ofc the list has the controller applied
child: ListView.builder(
padding: EdgeInsets.symmetric(vertical: 10),
shrinkWrap: true,
controller: scrollController,
itemCount: chatMessage.length,
itemBuilder: (context, index) {
return ChatBubble(
chatMessage: chatMessage[index],
isDark: widget._isDark,
);
},
)
I tried to print the maxScrollExtent to see if something was wrong.
Both the max and min scrollExtent are equals to 0.0.
The animateTo function is also call in a postFrameCallback.
The listview length is always 20 has, for now, it's a simple list.

For me, the issue was that my ListView was inside a ScrollView. Removing the ScrollView fixed the issue.

Related

automatically scrolling to a specific pageview when a button is pressed in Flutter

I have a horizontal list of 10 containers wrapped using PageView builder.
PageView.builder(
controller: _pageController,
itemCount: items.length,
itemBuilder:
(BuildContext context, int index) {
return ContainerItem(index);
},
),
I have another button which on pressing, should scroll the containers to get to that specific container. How do I achieve this
Using your PageController you can achieve this, the purpose of the controller is to control so....
This is what you are looking for:
_pageController.animateToPage(
index,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
index is the page to want to scroll to, from 0 to the (items.length - 1). If you have 3 items, then setting index to 0 will represent the first...and so on
Look at the docs: https://api.flutter.dev/flutter/widgets/PageController-class.html

AnimatedList auto scrolling to last item and back

I've implemented an Animated list with SlideTransition like this
#override
Widget build(BuildContext context) {
return Expanded(
child: Container(
child: ListView(
children: [
// Other widgets
animatedList(),
],
),
),
);
}
Widget animatedList() {
return AnimatedList(
shrinkWrap: true,
key: _myKeyList,
initialItemCount: _myItemsList.length,
itemBuilder: (context, index, animation) {
return SlideTransition(
position: animation.drive(_offset),
child: _buildMyItemTile[index],
);
},
);
}
where _offset variable is a Tween animation. Each item of list is inserted and animated with a delay of 500 milliseconds.
Now, when all items are added to AnimatedList, i would like that AnimatedList content scroll automatically from first item to last (and back) continuously for show all its content.
How can i do?
Just do it
add a controller
final controll = ScrollController();
then add this controller in the AnimatedList
AnimatedList(
controller: controll,
...
)
Make a function to call the last position or the first
0 for first position and 1 for last position or true false, I don't know, use your imagination
void getLastItem(int 0){
var position = int == 0
? controll.position.minScrollExtent
: controll.position.maxScrollExtent;
controll.animateTo(
position,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInCubic,
)
}
I hope I helped you.

How to preserve the state of each list object in Flutter

I have a web-socket that sends images with some data and than I display them inside AnimatedList. The problem is that every new data comes in, it reload every single item the list which reloads every image. Also because image is from base64 converted, it can't be cashed.
Is there any way to preserve the state of each object in AnimatedList so that when new item is added, it doesn't reload all other objects/images in the list.
AnimatedList sample:
AnimatedList(
key: node.validListKey,
controller: widget.scrollController,
padding: EdgeInsets.fromLTRB(10, 10, 0, 10),
initialItemCount: node.data.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: CurvedAnimation(
parent: animation,
curve: Interval(0, 0.2, curve: Curves.ease)),
child: SizeTransition(
sizeFactor:
CurvedAnimation(parent: animation, curve: Curves.ease),
child: DataContainer(data[index])
),
);
},
),
Base decode sample:
child: AspectRatio(
aspectRatio: 1 / 1,
child: ExtendedImage.memory(
base64.decode(data.base),
fit: BoxFit.cover,
loadStateChanged: (state) {
if (state.extendedImageLoadState ==
LoadState.completed) {
return ExtendedRawImage(
image: state.extendedImageInfo?.image,
);
}
return Center(child: CircularProgressIndicator());
},
),
),
UPDATE#1
It seems that the problem is about how I insert items in the list. Because I insert new items at start of the list, for some reason it calls init state only for the last element, but not the recently added one. When I insert at the end of the list, problem is solved. Is there any way how I could force to call init state for the recently added item instead of the last?
Insert item example:
array.add(data);
array.insert(0, data);
if (key.currentState != null)
key.currentState.insertItem(0, duration: _insertDuation);
UPDATE#2
So I was able to fix the first problem that my images was reloading by wrapping AnimatedList inside SingleChildScrollView, adding reverse: true, shrinkWrap: true and by adding decode inside initState method.
SingleChildScrollView(
child : ListView.builer(
shrinkWrap: true,
reverse: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
...
},
)
);
But now the problem is with remove method. When I try to remove item from the list, base widget content stays the same, but image changes by one index. I have tried adding unique key for each widget, but that forces to reload each image when data has been added.
_data.removeAt(0);
if (_validListKey.currentState != null)
_validListKey.currentState.removeItem(
0, (context, animation) => Container(),
duration: Duration.zero);
You should do the base64.decode(data.base) in your initState function (or an async function that was triggered in initState) and cache the results.
Your build function should not trigger this decoding, or should not run any code with a side effect. Right now at every build you are doing a decode and giving a different Uint8List instance to ExtendedImage. If ExtendedImage is implemented correctly, it should not do any UI changes when it's given the same reference in a new build() call.

Flutter PageView, can i animate removing items from list?

I'm pretty new to flutter and i'm trying to do some animation on a PageView. to be precise, I want to animate removing an item.
I've tried serveral ways to animate it and apart from a solution, the way how you guys would solve such a problem would also be helpful for my flutter skils.
What I've tried so far:
Animating the padding and opacity
the problem with this is that when i set the padding in the setState in the onLongPress it rebuilds the widget and it overrides the padding again with the active or inactive CardPadding (i think)
Animating the width and height
I just can't seem to get both of these values to work
Animating the viewportFraction on the PageViewController
Would not know how to go about this and if it would be possible to do this only for a specific 'Page'
Below is the (stripped down) code I've written thus far.
class Main extends StatefulWidget {
#override
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
int activeCard = 0;
EdgeInsets inActiveCardPadding = EdgeInsets.symmetric(vertical: 120.0, horizontal: 20.0);
EdgeInsets activeCardPadding = EdgeInsets.symmetric(vertical: 105.0, horizontal: 10.0);
PageController pageController = PageController(
initialPage: 0,
viewportFraction: 0.8,
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Stack(
children: <Widget>[
PageView.builder(
itemCount: PlantCareApp.plants.length,
controller: pageController,
onPageChanged: (activeCardIndex) {
setState(() {
this.activeCard = activeCardIndex;
});
},
itemBuilder: (context, cardIndex) {
return AnimatedContainer(
padding: (activeCard == cardIndex) ? activeCardPadding : inActiveCardPadding;,
duration: Duration(milliseconds: 250),
child: PlantCard(
PlantCareApp.plants[cardIndex],
onTap: () {
Navigator.pushNamed(context, PlantDetailScreen.route, arguments: PlantCareApp.plants[cardIndex]);
},
onLongPress: () {
setState(() {
//
// ANIMATE OR TRIGGER ANIMATION HERE
//
// do the actual removing
/*
PlantCareApp.plants[cardIndex].remove(); // remove from db
PlantCareApp.plants.removeAt(cardIndex); // remove from List
*/
});
//PlantCareApp.plants[cardIndex].remove();
},
),
);
},
),
],
),
),
);
}
}
Any help will be greatly appreciated! How would you guys tackle a problem like this, or how would you tackle this specific use case.
I guess actually animating viewportFraction would be the nicest because of the adjecent 'Pages' moving toward each other as well?
Thanks!
I'm not certain if this is what you are looking for, but here goes.
One way of doing this is simply using the provided Widgets within Flutter. Two of these will help you out: AnimatedList and Dismissible.
Now, you could do something like this:
// define somewhere
final _animatedListGK = GlobalKey<AnimatedListState>();
// put in a function somewhere
return AnimatedList(
key: _animatedListGK,
padding: const EdgeInsets.all(0),
initialItemCount: PlantCareApp.plants.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: _buildDismissibleRow(context, index, PlantCareApp.plants[index])
);
}
);
Note: you don't have to use the _animatedListGK global key per se, it depends on whether you can use AnimatedList.of(context) or not. Although it is the easier way.
The _animatedListGK is simply a Global Key that provides access to the AnimatedList so you can perform insertions/removals with animation.
Your dismissible row might look something like:
Widget _buildDismissibleRow(BuildContext context, int index, PlantModel plantModel) {
return Dismissible(
key: ValueKey<String>(plantModel.someKey),
direction: DismissDirection.startToEnd,
background: Container(color: Colors.red),
onDismissed: (direction) {
// You could use:
// AnimatedList.of(context)
_animatedListGK.currentState.removeItem(
index,
(context, animation) => Container(),
duration: Duration.zero
);
},
child: _buildContent(context, index, plantModel)
);
}
You could also do it without a dismissible row or even within the child of the dismissible row (_buildContent() for example). Something similar to:
// You could use:
// AnimatedList.of(context)
_animatedListGK.currentState.removeItem(
index,
(context, animation) {
return FadeTransition(
opacity: CurvedAnimation(parent: animation, curve: Interval(0.5, 1.0)),
child: SizeTransition(
sizeFactor: CurvedAnimation(parent: animation, curve: Interval(0.0, 1.0)),
child: _builContent(context, index, plantModel)
)
);
},
duration: const Duration(milliseconds: 300)
);
Notice how the SizeTransition simply "calls itself" by calling _builContent(context, index, plantModel)? That's how you can animate the row itself (out of existence).
Be sure to watch the videos in the aforementioned documentation pages! They will help understanding certain constructs.
A preview of what the dismissible might look like:
A preview of what the SizedTransition might look like:

Listview Scrolling to widget with variable height

I have a Listview filled with Text Widgets of variable height:
ListView.builder(
controller: _controller,
itemCount: items.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(items[index].text));
})
I use a ScrollController (_controller) to animate the list and scroll down when the user clicks a button.
_moveDown() {
_controller.animateTo(_controller.offset + 100,
curve: Curves.linear, duration: Duration(milliseconds: 100));
}
I have no issues scrolling in fixed increments (100px in the example above), but I can't figure out how to calculate the height of the widgets in the list so the user can scroll among the list items.
Is there a way to get the height of each of the elements of the list?
Yes, there is.
You need to give a Key, for example a GlobalKey to your child widget, which by your example, looks like a Padding, then you can find the height of the rendering widget. Your method to handle your scroll could then be something like this where myKey is each items key.
void _moveDown(GlobalKey myKey) {
final keyContext = myKey.currentContext;
if (keyContext != null) {
final box = keyContext.findRenderObject() as RenderBox;
_scrollController.animateTo(_scrollController.offset + box.size.height,
duration: Duration(milliseconds: 100), curve: Curves.linear);
}
}