I'm using Flutter version 1.12.13+hotfix.
I'm looking for a solution to be able to scroll inside a ListView and when reached the bottom, automatically give scroll lead to the parent ListView.
The first solution to achieve that is to use "physics: ClampingScrollPhysics()" with "shrinkWrap: true". So I apply this solution to all sub Listview except first one (the red) because I need to wrap it inside a sized Container().
The problem come from the first one... ClampingScrollPhysics() didn't work with sized Container() !
So, when I scroll the red Listview and reach its bottom, scroll stoping... I need to put my finger outside this ListView to be able again to scroll.
@override
Widget build(BuildContext context) {
super.build(context);
print("build MySongs");
return ListView(
children: <Widget>[
Container(
height: 170,
margin: EdgeInsets.all(16),
child: ListView(
children: <Widget>[
Container(color: Colors.red, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
Container(color: Colors.red, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
Container(color: Colors.red, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
],
),
),
Container(
margin: EdgeInsets.all(16),
child: ListView(
physics: ClampingScrollPhysics(),
shrinkWrap: true,
children: <Widget>[
Container(color: Colors.orange, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
Container(color: Colors.orange, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
Container(color: Colors.orange, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
],
),
),
Container(
margin: EdgeInsets.all(16),
child: ListView(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
children: <Widget>[
Container(color: Colors.blue, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
Container(color: Colors.blue, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
Container(color: Colors.blue, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
],
),
),
Container(
margin: EdgeInsets.all(16),
child: ListView(
physics: ClampingScrollPhysics(),
shrinkWrap: true,
children: <Widget>[
Container(color: Colors.green, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
Container(color: Colors.green, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
Container(color: Colors.green, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
],
),
),
],
);
}
Maybe in need post this question on Flutter github issue :/
Thanks for Hamed Hamedi solution :) ! I made a better solution, I think, based on NotificationListener ! (I discovered this functionnality thanks to him).
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8),
color: Colors.yellow,
child: ListView.builder(
controller: controller,
itemBuilder: (c, i) =>
i == 10
? Container(
height: 150,
color: Colors.red,
child: NotificationListener<OverscrollNotification>(
onNotification: (OverscrollNotification value) {
if (value.overscroll < 0 && controller.offset + value.overscroll <= 0) {
if (controller.offset != 0) controller.jumpTo(0);
return true;
}
if (controller.offset + value.overscroll >= controller.position.maxScrollExtent) {
if (controller.offset != controller.position.maxScrollExtent) controller.jumpTo(controller.position.maxScrollExtent);
return true;
}
controller.jumpTo(controller.offset + value.overscroll);
return true;
},
child: ListView.builder(
itemBuilder: (c, ii) => Text('-->' + ii.toString()),
itemCount: 20,
),
),
)
: Text(i.toString()),
itemCount: 45,
),
);
}
The solution wrapped into StatelessWidget :
import 'package:flutter/material.dart';
class ScrollParent extends StatelessWidget {
final ScrollController controller;
final Widget child;
ScrollParent({this.controller, this.child});
@override
Widget build(BuildContext context) {
return NotificationListener<OverscrollNotification>(
onNotification: (OverscrollNotification value) {
if (value.overscroll < 0 && controller.offset + value.overscroll <= 0) {
if (controller.offset != 0) controller.jumpTo(0);
return true;
}
if (controller.offset + value.overscroll >= controller.position.maxScrollExtent) {
if (controller.offset != controller.position.maxScrollExtent) controller.jumpTo(controller.position.maxScrollExtent);
return true;
}
controller.jumpTo(controller.offset + value.overscroll);
return true;
},
child: child,
);
}
}
To go further, take a look of other implementation of NotificationListener which can be useful for pagination :). You can try also this :
NotificationListener<ScrollStartNotification>(
onNotification: (ScrollStartNotification value) {
final ScrollMetrics metrics = value.metrics;
if (!metrics.atEdge || metrics.pixels != 0) return true;
print("Your callback here");
return true;
},
child: child,
)
Or this :
NotificationListener<ScrollEndNotification>(
onNotification: (ScrollEndNotification value) {
final ScrollMetrics metrics = value.metrics;
if (!metrics.atEdge || metrics.pixels == 0) return true;
print("Your callback here");
return true;
},
child: child,
)
A tricky way could be using NotificationListener
. Put a Overscroll Notification Listener over your child scroll widget then ignore the pointer in case of overscroll. To let the child widget to scroll again in opposite direction, you have to set ignoring false after a short time. A detailed code sample:
class _MyHomePageState extends State<MyHomePage> {
var _scrollParent = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.yellow,
child: ListView.builder(
itemBuilder: (c, i) => i == 10
? Container(
height: 150,
color: Colors.red,
child: IgnorePointer(
ignoring: _scrollParent,
child: NotificationListener<OverscrollNotification>(
onNotification: (_) {
setState(() {
_scrollParent = true;
});
Timer(Duration(seconds: 1), () {
setState(() {
_scrollParent = false;
});
});
return false;
},
child: ListView.builder(
itemBuilder: (c, ii) => Text('-->' + ii.toString()),
itemCount: 100,
),
),
),
)
: Text(i.toString()),
itemCount: 100,
),
),
);
}
}
There would be some flaws like double scrolling requirement by user to activate parent scroll event (first one will ignore the pointer), or using timer to disable ignoring that leads to misbehavior in fast scrolling actions. But the implementation simplicity towards other solutions would be immense.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With