1

I have multiple widget and lists within CustomScrollView and I would like to stop CustomScrollView to scroll while scrolling on some pixels bound condition.

I can use NeverScrollPhysics() to stop it but I don't want to use setState() 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

9
  • use Stream instead of setState. Commented Sep 11, 2021 at 15:57
  • 2
    Did you mean StreamBuilder ? It's the same as Provider in this case, would reload the entire content of the CustomScrollView. Commented Sep 11, 2021 at 16:02
  • You need a state management solution, such as bloc or riverpod. Commented Sep 16, 2021 at 0:21
  • @7mada I'm already using Provider but it doesn't solve this Commented Sep 16, 2021 at 9:10
  • I 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. Commented Sep 16, 2021 at 16:07

3 Answers 3

2
+50

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(),
    ),
  );
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot for this . Didn't know about Riverpod
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,
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

3 Comments

I can't create const Constructor unfortunately . My lists contain users item with multiple param variable . Thanks for help
@nicover above solution work mostly with less rebuild time can you give a try? And try to split slivers content with stateless & stateful widget
I 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
0

Are you just need to stop the user from scrolling it? I think you can try to controller the list to a fixed position by using jumoTo.

  ...
  final _controller = ScrollController();

  @override
  Widget build(BuildContext context) {
    return NotificationListener(
      onNotification: (ScrollNotification notif) {
        if (notif is ScrollUpdateNotification) {
          if (notif.metrics.pixels > 100) {
            _controller.jumpTo(100)
          }
        }
        return true;
      },
      child: CustomScrollView(
        controller: _controller,
        ...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.