How to determine if a DraggableScrollableController is animating in Flutter? - flutter

I have a DraggableScrollableSheet that has a DraggableScrollableController. I'm calling .animateTo() on the DraggableScrollableController. Is there any way to tell outside of the sheet, in another widget maybe, that the sheet is animating? Some kind of isAnimating type property where the default is false, then when you call animateTo and you check this property it is true and when it completes, back to false?

you can wrap it with NotificationListener:
this will show the extent of your draggableScrollableSheet.
NotificationListener<DraggableScrollableNotification>(
onNotification: (notification) {
print("${notification.extent}");
},
child: DraggableScrollableSheet(
initialChildSize: .70,
minChildSize: .70,
builder:
(BuildContext context, ScrollController scrollcontroller) {
return Container(
child: ListView.builder(
controller: scrollcontroller,
itemCount: widget.songs.length,
itemBuilder: (BuildContext context, int index) {
return buildSongRow(widget.songs[index]);
}),
);
}),
)
then you can set some logic like this :
bool isAnimating = notification.extent != notification.maxExtent;
please try it and tell me.

Related

modalBottomSheet + ListView, how to sync scroll?

I have a modalBottomSheet with a scrolling list view inside of it.
I want for the scroll control to be transferred to the bottom sheet when the listView reaches the top, so that it can be dragged to dismiss.
Now, the listView gets to the top and even if I keep dragging the bottom sheet will not be dismissed on drag.
Thank you!
Try this way
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) {
return DraggableScrollableSheet(
maxChildSize: 1,
initialChildSize: .5,
expand: false,
builder: (context, scrollController) {
return ListView.builder(
itemCount: 44,
controller: scrollController,
itemBuilder: (context, index) => ListTile(
title: Text("item $index"),
),
);
},
);
},
);

SliverAppBar not scrolling when nested ListView uses a ScrollController

Minimum reproducible code:
final _controller = ScrollController();
#override
Widget build() {
return NestedScrollView(
headerSliverBuilder: (_, __) => [SliverAppBar(expandedHeight: 300)],
body: ListView.builder(
controller: _controller, // Removing this solves the issue.
itemCount: 100,
itemBuilder: (_, i) => Text('$i'),
),
);
}
If I scroll my ListView, the SliverAppBar doesn't scroll but if I remove the controller property then it does scroll.
So, how can I use the controller and make the SliverAppBar to scroll with the ListView (i.e. the standard behavior)?
Note: I don't want to use the CustomScrollView as my tree hierarchy won't let me make use of it that well.
You should make sure that the child is wrapped
final _controller = ScrollController();
#override
Widget build() {
return NestedScrollView(
headerSliverBuilder: (_, __) => [SliverAppBar(expandedHeight: 300)],
body: ListView.builder(
//add this to the child ListView
shrinkWrap: true
controller: _controller, // Removing this solves the issue.
itemCount: 100,
itemBuilder: (_, i) => Text('$i'),
),
);
}
Just move the scroll controller to NestedScrollView like this:
Widget build() {
return NestedScrollView(
headerSliverBuilder: (_, __) => [SliverAppBar(expandedHeight: 300)],
body: ListView.builder(
//add this to the child ListView
shrinkWrap: true,
itemCount: 100,
itemBuilder: (_, i) => Text('$i'),
),
controller: _controller,
);
}
That worked for me, I had a waterfalls flow using the scroll controller. and I need the sliver appear style.

My modalBottomSheet is draggable just on edges

I have a DraggableScrollableSheet inside of a showModalBottomSheet builder. I have set isDismissible: true for modal sheet but my widget is draggable just on edges. That is because I have a ListView inside of DraggableScrollableSheet.
What I should do for when the list is overscrolled on top the drag to start?
Here goes my code:
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
isDismissible: true,
backgroundColor: Colors.transparent,
enableDrag: true,
builder: (BuildContext context) {
return StackedSheet(
backgroundColor: backgroundColor,
onBackgroundColor: onBackgroundColor,
padding: padding,
minChildSize: minChildSize,
maxChildSize: maxChildSize,
onClose: onClose,
child: SafeArea(
top: false,
child: child,
),
);
},
);
Where StackedSheet is:
LayoutBuilder(
builder: (context, constraints) {
return DraggableScrollableSheet(
key: Key('$_childSize'),
initialChildSize: min(_childSize, widget.maxChildSize),
minChildSize:
min(_childSize - _childSize * 0.2, widget.minChildSize),
maxChildSize: min(_childSize, widget.maxChildSize),
expand: false,
builder: (
BuildContext context,
ScrollController scrollController,
) {
return ListView.separated( ...blah blah)
}
Here is a GIF:
Instead of bouncing, my desire is to start to be dragged down.
In my opinion you can use scroll controller to dismiss bottomsheet on reaching top something like this
if (_controller.offset <= _controller.position.minScrollExtent &&
!_controller.position.outOfRange) {
.....
.....
);
}
You can stop list from bouncing at top using shrinkWrap=true in Listview.
And to dismiss the bottomsheet you can refer to this stackoverflow post
I haven't tried this code but hopefully it will help you in some way.
Also, I think it will look good if you will keep it the way it is now by inserting a little "-" at top on right side of "X" ,on which user can touch to slide down.

ScrollController jumpto when ListView.builder complete

I am creating a chat with firebase in flutter and I want that when the listview builder completes it can go to the end of the list (Last message).
This is what my buildmethod looks like:
return Scaffold(
backgroundColor: Color(0xFFf1e4e8),
body: Stack(
children: [
Container(
padding: EdgeInsets.only(bottom: 125),
child: StreamBuilder(
stream: userBloc.chat(widget.chatID),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else if(snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
return ListView.builder(
controller: scrollController,
physics: BouncingScrollPhysics(),
itemCount: snapshot.data.size,
itemBuilder: (context, index) {
return ChatMessage(
isUserMessage: isUserMessage(snapshot,index),
message:
snapshot.data.docs[index].data()['Message'],
timeStamp:snapshot.data.docs[index].data()['Timestamp']);
},
);
}
}),
);
What is the correct way to do it?
In your initState(), you can register a callback with addPostFrameCallback and it will get called once after the first frame completes rendering. You could use this callback to scroll to the bottom of your ListView.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback( (_) {
_scrollToBottomOfListView();
}
}
Attach addListener to your scrollcontroller and then use jumpTo function accordingly. You can follow this example
ScrollController _scrollController;
double _lastEndOfScroll = 0;
_scrollController.addListener(() {
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
if (maxScroll == currentScroll) {
_lastEndOfScroll = maxScroll;
}
});
_scrollController.jumpTo(_lastEndOfScroll);
The simplest solution is the following:
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('<MessagesColection>')
.orderBy('<Time field>',descending: true)
.snapshots(),
builder: (context,snapshot) {
return ListView.builder(
//The reversed list will put the list backwards.
//The start of the list will start at the bottom.
reverse: true,
controller: scrollController,
itemCount: snapshot.data.size,
itemBuilder: (context, index) {
return ChatMessage(snapshot);
},
);
}
),
In the previous code, what was done was to invert the list, the most recent messages will be at the bottom, and order the records in descending order, that is, from the most recent to the least recent. In this way the most recent messages will be at the beginning of the list (in this case at the bottom), and the least at the bottom of the list (in this case at the top).

DraggableScrollableSheet doesn't give the current position of the sheet when dragging

In flutter we have a widget DraggableScrollableSheet. Now I want the current position or the current size of the child while it is dragging. There is currently no way to get that value. It gives a ScrollController in its builder method but that is for when the list is scrolling and not when its being dragged. So is there another way by which I can track the current drag position of the list?
I tried adding NotificationListener but that only gives me dragstartnofification and dragendnotification and nothing for dragupdate:
DraggableScrollableSheet(
initialChildSize: .70,
minChildSize: .70,
builder:
(BuildContext context, ScrollController scrollcontroller) {
return NotificationListener(
onNotification: (notification) {
print("$notification");
},
child: Container(
child: ListView.builder(
controller: scrollcontroller,
itemCount: widget.songs.length,
itemBuilder: (BuildContext context, int index) {
return buildSongRow(widget.songs[index]);
}),
),
);
}),
I found the solution to this. Flutter team updated the ScrollableDraggableSheet class in the Flutter v1.7.3. They added a class called DraggableScrollableNotification which you can listen to to get the current extend of the child when dragging.
NotificationListener<DraggableScrollableNotification>(
onNotification: (notification) {
print("${notification.extent}");
},
child: DraggableScrollableSheet(
initialChildSize: .70,
minChildSize: .70,
builder:
(BuildContext context, ScrollController scrollcontroller) {
return Container(
child: ListView.builder(
controller: scrollcontroller,
itemCount: widget.songs.length,
itemBuilder: (BuildContext context, int index) {
return buildSongRow(widget.songs[index]);
}),
);
}),
)
in the above sample notification.extent will give you the current extent(position) of the child when dragging.
Note: this is currently in the master channel and not in the stable
channel. Switch to the master channel by running flutter channel master
and then flutter upgrade to make it work for now.