Disable scrolling of CustomScrollView while scrolling without setState Flutter
Solution 1
When the build method is called, all widgets in that build method will be rebuild except for const
widgets, but const
widget cannot accept dynamic arguments (only a constant values).
Riverpod
provides a very good solution in this case,
With ProviderScope
you can pass arguments by inherited widget
instead of widget constructor (as when passing arguments using navigation) so the contractor can be const
.
Example :
Data module
TLDR you need to use Freezed
package or override the == operator
and the hashCode
almost always because of dart issue.
class DataClass {
final int age;
final String name;
const DataClass(this.age, this.name);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is DataClass && other.age == age && other.name == name;
}
@override
int get hashCode => age.hashCode ^ name.hashCode;
}
setting our ScopedProvider
as a global variable
final dataClassScope = ScopedProvider<DataClass>(null);
The widget we use in our list
class MyChildWidget extends ConsumerWidget {
const MyChildWidget();
@override
Widget build(BuildContext context, ScopedReader watch) {
final data = watch(dataClassScope);
// Note for better optimaization
// in case you are sure the data you are passing to this widget wouldn't change
// you can just use StatelessWidget and set the data as:
// final data = context.read(dataClassScope);
// use ConsumerWidget (or Consumer down in this child widget tree) if the data has to change
print('widget with name\n${data.name} rebuild');
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20),
child: Text(
'Name : ${data.name}\nAge ${data.age}',
textAlign: TextAlign.center,
),
),
);
}
}
finally the main CustomScrollView
widget
class MyMainWidget extends StatefulWidget {
const MyMainWidget();
@override
State<MyMainWidget> createState() => _MyMainWidgetState();
}
class _MyMainWidgetState extends State<MyMainWidget> {
bool canScroll = true;
void changeCanScrollState() {
setState(() => canScroll = !canScroll);
print('canScroll $canScroll');
}
final dataList = List.generate(
20,
(index) => DataClass(10 * index, '$index'),
);
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
changeCanScrollState();
},
child: CustomScrollView(
shrinkWrap: true,
physics: canScroll
? BouncingScrollPhysics()
: NeverScrollableScrollPhysics(),
slivers: [
for (int i = 0; i < dataList.length; i++)
ProviderScope(
overrides: [
dataClassScope.overrideWithValue(dataList[i]),
],
child: const MyChildWidget(),
),
],
),
),
);
}
}
Don't forget to wrap the MaterialApp
with ProviderScope
.
runApp(
ProviderScope(
child: MyApp(),
),
);
Solution 2
Try this solution use const constructor for child widget so it won't rebuild unless widget changed
class MyHomePage extends StatelessWidget {
ValueNotifier<ScrollPhysics> canScroll =
ValueNotifier(const BouncingScrollPhysics());
MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: NotificationListener(
onNotification: (ScrollNotification notif) {
if (notif is ScrollUpdateNotification) {
if (canScroll.value.runtimeType == BouncingScrollPhysics &&
notif.metrics.pixels > 100) {
canScroll.value = const NeverScrollableScrollPhysics();
debugPrint("End false");
}
}
if (notif is ScrollEndNotification) {
if (canScroll.value.runtimeType == NeverScrollableScrollPhysics) {
debugPrint("End");
Future.delayed(const Duration(milliseconds: 300),
() => canScroll.value = const BouncingScrollPhysics());
debugPrint("End1");
}
}
return true;
},
child: ValueListenableBuilder(
valueListenable: canScroll,
builder:
(BuildContext context, ScrollPhysics scrollType, Widget? child) =>
CustomScrollView(
physics: scrollType,
slivers: [
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.black,
),
),
SliverToBoxAdapter(
child: Column(
children: [
Container(
height: 100,
color: Colors.blue,
),
Container(
height: 200,
color: Colors.grey,
),
Container(
height: 200,
color: Colors.blue,
),
Container(
height: 200,
color: Colors.grey,
),
Container(
height: 200,
color: Colors.blue,
),
],
),
),
],
),
),
),
);
}
}
nicover
Updated on January 01, 2023Comments
-
nicover over 1 year
I have multiple widget and lists within
CustomScrollView
and I would like to stopCustomScrollView
to scroll while scrolling on some pixels bound condition.I can use
NeverScrollPhysics()
to stop it but I don't want to usesetState()
function here because the CustomScrollview content with lists is big enough to make the screen laggy while reloading on scroll.Also tried with
Provider
but the builder is providing only a child widget which is not working with sliver list.Here is the code using
setState()
:NotificationListener( onNotification: (ScrollNotification notif) { if(notif is ScrollUpdateNotification) { if (canScroll && notif.metrics.pixels > 100) { canScroll = false; setState(() {}); } } if(notif is ScrollEndNotification) { if(!canScroll) { canScroll = true; setState(() {}); } } return true; }, child: CustomScrollView( shrinkWrap: true, physics: canScroll ? BouncingScrollPhysics() : NeverScrollableScrollPhysics(), slivers: [ SliverToBoxAdapter(), List(), List(), ], ), ),
Is there a way to reload only the
CustomScrollView
without its child ? Otherwise any workaround to prevent scrolling in this case ?Thanks for help
-
Benyamin over 2 yearsuse Stream instead of setState.
-
Tom3652 over 2 yearsDid you mean
StreamBuilder
? It's the same asProvider
in this case, would reload the entire content of theCustomScrollView
. -
Mohammed Alfateh over 2 yearsYou need a state management solution, such as bloc or riverpod.
-
nicover over 2 years@7mada I'm already using Provider but it doesn't solve this
-
Mohammed Alfateh over 2 yearsI know Provider won't solve this problem, but RiverPod and Bloc can, if you want to use RiverPod I can write you an answer that will solve the problem.
-
nicover over 2 yearsIf i understand correctly, with Riverpod i am able to not reload items in CustomScrollView when it's rebuilt ? And only reload constructor param ? If yes, then please write an answer and I'll test . If it's working I'll migrate my project to RiverPod. Thanks for help
-
Mäddin over 2 yearsWhy do you use
shrinkWrap: true
? Without that line it should not be laggy withsetState(...)
. -
nicover over 2 years@Mäddin I'm using this because shrinkWrap is killing the top BouncingPhysics but no at bottom . I need this. And I tried with and without there no difference
-
Mohammed Alfateh over 2 yearsSorry for being late @nicover, I didn't get a reply notification :(. Any way I will write the answer now.
-
-
nicover over 2 yearsI can't create const Constructor unfortunately . My lists contain users item with multiple param variable . Thanks for help
-
dhruvanbhalara over 2 years@nicover above solution work mostly with less rebuild time can you give a try? And try to split slivers content with stateless & stateful widget
-
nicover over 2 yearsI tried your code. Unfortunately I can't do to this in my use case . slivers item are rebuilt and my lists make the screen laggy on scroll. Anyway your answer is useful for another case
-
nicover over 2 yearsThanks a lot for this . Didn't know about Riverpod