I'm working on a widget that consists of several scrolling widgets (list views and a custom widget). When I scroll any of those list views the custom widget must be scrolled as well. Just to be clear the custom widget isn't actually a scroll view, but a stateful widget that updates its viewport content when a scroll position of the list views is changed.
Currently I'm using a scroll controller for each list and listener to sync the positions by calling jumpTo for the other controllers. But this seems a clumsy way to achieve what I need.
ScrollController exposes the interface to control multiple positions, and because behind the scene it's jumpTo method just calls jumpTo for each ScrollPosition object in positions list, I hoped that there is a way to utilize this for synchronization of multiple scroll views by sharing the same controller. And it seems I'm not the only one who had the same idea (ScrollController attached to multiple scroll views). But the only recommendation I could find is to use a controller per scroll view, which I already do.
Clearly I'm not completely understand how to work with scroll views in Flutter. So my questions are the following. What is the purpose of positions in the ScrollController? What is the design decision behind it and how to use it properly.
If you want to use a single scrollController for many scrollable widgets(ListView,Gridview,etc) the scrollController save each ScrollView in a Iterable so if you want to jumpTo you need to use the argument called positions which is a Iterable<ScrollPosition> type.
Somethin like this:
final _scrollController = ScrollController();
PageView(
controller: _pageController,
children: <Widget>[
_ListView(controller: _scrollController),
_ListView(controller: _scrollController),
_ListView(controller: _scrollController),
],
)
void _moveScroll(){
_scrollController.animateTo(
// ignore: invalid_use_of_protected_member
_scrollController.positions.first.maxScrollExtent,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut);
}
Related
Flutter's DraggableScrollableSheet Widget allows a sheet to be dragged into position and then continue scrolling the content without the user needing to start a new gesture (lift their finger from the screen).
This Widget however relies on a single scroll controller being declared which makes adding say a Navigator into that sheet with multiple scrolling pages difficult.
Instead I tried to mimic the behaviour with a Listener Widget that replaces the Draggable element and an attachable scroll listener that can modify the scroll position of any ScrollController it is attached to until the draggable sheet (Listener) updates the state to say that scrolling is now allowed.
It works perfectly but relies on overriding the ScrollController's position:
// state variable
bool allowScroll = false
if(! allowScroll){
scrollController?.position.setPixels(0);
}
However, that may not be the most efficient way to go about it. Essentially I am overriding the ScrollPosition as apposed to ignoring gestures on the scrollable widget.
Other options such as:
Changing scroll physics from Never to Always
IgnorePointer / Absorb Pointer
Changing HitTestBehaviour
do not allow the same gesture to be used and require the user to lift their finger after the state change and initiate a new gesture. That would not be the correct behaviour. The above works correctly, it just feels like a hack and I wondered if there was a better approach.
So this listview is dynamically loaded from api, when I scroll on page (up/down) outside of listview it works perfectly, but when I scroll from inside listview I am unable to scroll on the page. Any ideas on what I can change?
It seems you have used Nested ListView.
Just declare:
ScrollController _scrollController = ScrollController();
And call _scrollController to both the properties of contoller in listviews.
I think setting the primary property to false in the listview will solve your problem
On iOS, tapping the status bar makes PrimaryScrollController go to the top:
https://flutter.dev/docs/resources/platform-adaptations#return-to-top
On iOS, tapping the OS status bar scrolls the primary scroll controller to the top position. There is no equivalent behavior on Android.
My PrimaryScrollController is attached to a ListView with reverse:true, so tapping the status bar makes it scroll to the bottom.
Docs say PrimaryScrollView handles ScrollAction if not handled by another scroll controller.
https://api.flutter.dev/flutter/widgets/PrimaryScrollController-class.html
If a ScrollAction is not handled by an otherwise focused part of the application, the ScrollAction will be evaluated using the scroll view associated with a PrimaryScrollController
How can I handle scroll actions myself so I can reverse the direction PrimaryScrollController goes when the status bar is tapped?
The easiest way to accomplish this is likely going to be reversing the items in your list instead of using the reverse: true flag of ListView.
For example:
ListView(
children: [
Container(),
Container(),
].reversed.toList(),
),
Trying to solve this any other way would get pretty involved. Since we don't have access to the StatusBar we can't override its behavior or listen for taps on it.
So I have a SingleChildScrollView() whose child is a Column() with different widgets inside it. I have 3 BUTTONS on the app bar. Each for 3 widgets I want to jump to.
When I press the button, I want the UI to automatically scroll to the mapped Widget. Just like we see that effect in websites.
How can I achieve this ?
You can create a ScrollController and pass it to the controller parameter of your scrolling widget. Then you can use the animateTo method to animate to an offset.
Ex.
ScrollController controller = ScrollController();
//In build
SingleChildScrollView(
controller: controller,
child: ...,
)
//In each button onPressed/onTap
controller.animateTo(offset);
I have a scrollable widget, say a ListView, which contains some special widget.
How can I prevent the scrollable to scroll when the user tries to scroll by starting to scroll on top of that widget?
In other words, I want that widget to be like a "hole" that prevents the scrollable to sense gestures there.
Wrap the widget with GestureDetector and implement empty gesture functions in it.
GestureDetector(
onVerticalDragUpdate: (_) {},
child: YourWidget
),
The answer is not satisfying because it prevents the child from receiving gestures (at least on native platforms).
Only acceptable solution I found -- in my case with a PageView -- is to disable the scroll gestures when the page with the gesturedetector child displays.
If anyone has a cleaner solution, please let us know.