How to update visible ScrollablePositionedList items - flutter

I have a ScrollablePositionedList and I want to update the background color of every visible list item during a touch event. I am able to change the background color of the selected item in the list but finding it difficult to access/update every other item on list.
Here is what I have so far,
body: SizedBox(
height: 100,
child: WillPopScope(
onWillPop: () {
var position = itemPositionsListener.itemPositions.value.first.index;
print(position);
//trigger leaving and use own data
Navigator.pop(context, false);
//we need to return a future
return Future.value(false);
},
child: ScrollablePositionedList.builder(
scrollDirection: Axis.horizontal,
physics: NeverScrollableScrollPhysics(),
initialScrollIndex: startPosition,
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
itemCount: 14,
itemBuilder: (context, index) =>
MonthTile(getWidth(), months[index],(MonthTile tile){
itemScrollController.scrollTo(
index: getPosition(index),
duration: Duration(seconds: 1),
curve: Curves.easeInOutCubic);
// }
})))
),
What I'm basically aiming for is to have the selected item revert to its original color when another item is selected. I guess my question is, is there widget one can use to accomplish this?.

Related

Auto Card Flip on Listview.builder flutter

I am trying to use a flip_card flutter package to flip items in a list view builder. I want each item flip automatically and with different random time. How can I achieve that.
ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: length,
itemBuilder: (context, index) {
return FlipCard(
fill: Fill.fillBack, // Fill the back side of the card to make in the same size as the front.
direction: FlipDirection.HORIZONTAL, // default
front: Container(
child: Text('Front'),
),
back: Container(
child: Text('Back'),
),
);

GestureDetector is not working with Dismissible Widget

I am trying to get the drag offsets while using Dismissible widget. So tried to wrap it with GestureDetector but its onHorizontalDragStart is not working. Tried the either way ie by putting GestureDetector as child of Dismissible but then Dismissible stopped working.
How to solve this? Thnx...
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
var item = items[index];
return GestureDetector(
onHorizontalDragStart: (e) {
print(e);
},
child: Dismissible(
key: ValueKey(item),
background: Container(
color: Colors.teal,
),
child: ListTile(
title: Text("item $item"),
),
onDismissed: (d) {
items.remove(item);
},
),
);
},
);
Nested Gesture Widgets
The reason you are having this issue is because both of those widgets receive touch input and when you have two widgets that receive touch input, long story short the child wins that battle. Here is the long story. So both of your inputs from your Dismissible and GestureDetector are sent to what is called a GestureArena. There the arena takes into account multiple different factors but the end of the story is the child always wins. You can fix this issue by defining your own RawGestureDetector with its own GestureFactory which will change the way the arena performs.
ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
var item = items[index];
return Listener(
child: Dismissible(
key: ValueKey(item),
child: ListTile(
title: Text('This is some text'),
),
onDismissed: (DismissDirection direction) {
items.remove(index);
},
),
onPointerMove: (PointerMoveEvent move) {
if (move.localDelta.dx > 1) {//Ideally this number whould be
//(move.localDelta.dx != 0) but that is too sensitive so you can play with
//this number till you find something you like.
print(move.position);
}
},
);
},
);
I want to give all credit to Nash the author of Flutter Deep Dive: Gestures this is a great article I highly recommend you check it out.

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.

How to pass a listview.builder in flutter_slidable (Slidable)

This is just a simple todo list app. I want to implement a feature which lets me mark a task as complete when swiping right and delete the task when swiping left. This app uses a database and stores an added task in a list of type database (List<TodoItem> Itemlist= <TodoItem>[];).
Is it possible to use flutter_slidable?.
I have tried using Dismissible but i could only get it to delete the task.
List<TodoItem> Itemlist= <TodoItem>[];
SingleChildScrollView(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: Itemlist.length,
itemBuilder: (BuildContext context, int index){
String item = Itemlist[index].toString();
return Dismissible(
key: Key(UniqueKey().toString()),
onDismissed: (direction){
setState((){
deleteItem(Itemlist[index].id, index);
}
);
},
background: Container(
child: Icon(Icons.delete),
color: Colors.red,
alignment: Alignment.centerLeft,
),
child: Itemlist[index],
);
}
),
)
I want to get the same result as below but am unsure on how to pass the listview.builder in Slidable.
Wrap your tile widget with Slidable
Setup "actionPane" and "actions" parameters for Slidable

Refresh indicator doesn't work when list doesn't fill the whole page

I have a page the has a list with refresh indicator. When I have many elements in the list (filling the whole view and more, i.e. I can scroll), the refresh indicator works.
However, when I have only one or two elements in the list (nothing to scroll), then the refresh indicator doesn't work. Is this the expected behaviour? Is there a way to get the indicator to work also for short lists?
I tried wrapping the list view (child of refresh indicator) with a scroll bar but the problem still persists.
Future<void> _onRefresh() async {
_getPeople();
}
#override
Widget build(BuildContext context) {
super.build(context);
return new Container(
child: _items.length == 0
? Center(child: CircularProgressIndicator())
: new RefreshIndicator(
onRefresh: _onRefresh,
child: Scrollbar(
child: new ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return PeopleCard(
People: _items[index],
source: 2,
);
},
itemCount: _items.length,
),
)),
);
}
If the length of the items list is 1 or 2 or any amount that doesn't need scrolling, refresh indicator should also work.
Set the physics to AlwaysScrollableScrollPhysics just like the below code.
new RefreshIndicator(
key: _refreshIndicatorKey,
color: Colors.blue,
onRefresh: (){
setState(() {
_future = fetchPosts(http.Client());
_getAvalailableSlots();
});
},
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
itemCount: data.length,
itemBuilder: (context, i) {
//do something
}
)
)
Case 1:
Use AlwaysScrollableScrollPhysics() in your Listview or SingleChildScrollView.
physics: const AlwaysScrollableScrollPhysics(),
Case 2:
If you want to use other scroll physics, use this way...
physics: BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
Case 3:
Make sure you didn't make shrinkWrap, true. ShrinkWrap has to be false like default.
shrinkWrap: true. ❌
shrinkWrap: false. ✅
shrinkWrap: false,
Case 4:
If you have any custom widgets other than a listview, use stack and listview this way...
RefreshIndicator(
onRefresh: () async {
print('refreshing');
},
Stack(
children: [
YourWidget(),
ListView(
physics: const AlwaysScrollableScrollPhysics(),
),
],
),
),
To use with BouncingScrollPhysics:
RefreshIndicator(
onRefresh: () async {
},
child: ListView.builder(
physics: BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
itemBuilder: (context, index) {
return Text("${index}");
}),
To make RefreshIndicator always appear, set physics: const AlwaysScrollableScrollPhysics() for the scrollable child like below.
ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: <Widget>[widget.bodyWidget]
)
If you still have some errors, the below might help.
ListView widget needs to have height value to be "always" scrollable vertically. So wrap it with SizedBox or some other proper widget and set the height.
How to fix the height? Get the height of viewport using MediaQuery.of(context).size.height and, if needed, subtract the value of non-scrollable part.
If you use SingleChildScrollView and it's not working with AlwaysScrollableScrollPhysics(), it is because the child doesn't have height. fix the child's height or just use ListView.
To sum up, the following worked for me.
sizes.bottomNavigationBar and sizes.appBar are the constant variables I made to customize the appbar and navbar.
RefreshIndicator(
onRefresh: widget.onRefresh,
child: SizedBox(
height: MediaQuery.of(context).size.height -
sizes.bottomNavigationBar -
sizes.appBar,
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: <Widget>[widget.bodyWidget]
)),
)
If you're finding any reliability, Refresh indicator does not show up part of the official doc would help.
If you're using ListView inside of Column with Expanded.
Try Removing
shrinkWrap:true