Search code examples
flutterdartflutter-dependencies

How to synchronise multiple scroll controller when there are multiple single child scroll view independent of each other


I want all the rows should get scrolled together with multiple controller and without use of linked_scroll_controller.

This code snippet serves as an example. I acknowledge that the issue could be resolved by enveloping the column with a single-child scroll view instead of rows. However, the stipulation is to address this problem without utilizing that approach and linked_scroll_controller.

import 'package:flutter/material.dart';

class SynchroniseScroll extends StatelessWidget {
  SynchroniseScroll({super.key});
  final ScrollController header = ScrollController();
  final ScrollController body = ScrollController();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('DropdownFormField Example'),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          ...List.generate(
            4,
            (index) => Padding(
              padding: const EdgeInsets.only(top: 18.0),
              child: SingleChildScrollView(
                controller: index == 0 ? header : body,
                scrollDirection: Axis.horizontal,
                child: Row(
                  children: [
                    ...List.generate(
                      25,
                      (index) => Container(
                        height: 100,
                        width: 100,
                        color: index.isEven
                            ? Colors.yellowAccent
                            : Colors.purpleAccent,
                      ),
                    )
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Inspect the output provided below; when I scroll the row at the 2nd index, the remaining rows do not scroll in tandem.

enter image description here


Solution

  • You need to add listeners to your ScrollControllers and then act upon the changes reported through those listeners. In the code below, there are listeners added to the controllers (one for each row) and in the _handleScrollNotification method you can see how all 3 other ScrollControllers are told to jumpTo the same location as the one that was reported by the one ScrollController that recorded a change:

    class SynchroniseScroll extends StatefulWidget {
      const SynchroniseScroll({super.key});
    
      @override
      State<SynchroniseScroll> createState() => _SynchroniseScrollState();
    }
    
    class _SynchroniseScrollState extends State<SynchroniseScroll> {
      List<ScrollController> controllers = [];
    
      @override
      void initState() {
        controllers = List.generate(4, (index) => ScrollController());
        controllers.forEach((controller) =>
            controller.addListener((() => _handleScrollNotification(controller))));
        super.initState();
      }
    
      void _handleScrollNotification(ScrollController controller) {
        print(controller.position.pixels);
        controllers
            .where((element) => element != controller)
            .forEach((contr) => contr.jumpTo(controller.position.pixels));
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('DropdownFormField Example'),
          ),
          body: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: controllers
                .map((controller) => Padding(
                      padding: const EdgeInsets.only(top: 18.0),
                      child: SingleChildScrollView(
                        controller: controller,
                        scrollDirection: Axis.horizontal,
                        child: Row(
                          children: [
                            ...List.generate(
                              25,
                              (index) => Container(
                                height: 100,
                                width: 100,
                                color: index.isEven
                                    ? Colors.yellowAccent
                                    : Colors.purpleAccent,
                              ),
                            )
                          ],
                        ),
                      ),
                    ))
                .toList(),
          ),
        );
      }
    
      @override
      void dispose() {
        controllers.forEach((e) => e.dispose());
        super.dispose();
      }
    }