Search code examples
flutterdartflutter-layoutflutter-animation

Detect if Scrollable widget is scrolled manually or programatically


Is there a way to detect if a scroll in

  1. PageView (PageController) or
  2. Scrollable widget (ScrollController)

is done manually by a user or programatically using jumpTo() or animateTo() on the controller objects.

Wrapping the scrollable widget in a NotificationListener also does not give us any such callback or flag.

I found a solution around this but I find it a little hacky and would like to know if there is a better solution for it:

My Solution: Wrap the Scrollable widget in a GestureDetector widget and use the onTapUp and onTapDown callback functions to and set a flag (isScrollManual) to check if the scroll is manual or programatically.

Here is the code:

import 'package:flutter/material.dart';

final Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyPageControllers(),
        ),
      ),
    );
  }
}

class MyPageControllers extends StatefulWidget {
  @override
  _MyPageControllersState createState() => _MyPageControllersState();
}

class _MyPageControllersState extends State<MyPageControllers> {

  PageController _controller1;
  bool isScrollManual = false;

  @override
  void initState() {
    super.initState();
    _controller1 = PageController();

    _controller1.addListener(() {
      if(isScrollManual){
        /// Manual Scroll
      }else{
        /// Programmatic scroll
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: GestureDetector(
        onTapDown: (tapDownDetails){
          isScrollManual = true;
          setState(() {});
        },
        onTapUp: (tapUpDetails){
          isScrollManual = false;
          setState(() {});
        },
        child: PageView.builder(
          controller: _controller1,
          itemBuilder: _itemBuilder,
        ),
      ),
    );
  }

  Widget _itemBuilder(BuildContext context, int index) =>
      Container(
        color: Colors.primaries[index % Colors.primaries.length],
        child: Center(
          child: Text(
            index.toString(),
            style: TextStyle(color: Colors.white, fontSize: 60),
          ),
        ),
      );
}

Solution

  • The only working solution I found was to wrap the Scrollable widget in a GestureDetector widget and use the onTapUp and onTapDown callback functions to and set a flag (isScrollManual) to check if the scroll is manual or programatically.

    Here is the code:

    import 'package:flutter/material.dart';
    
    final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
          debugShowCheckedModeBanner: false,
          home: Scaffold(
            body: Center(
              child: MyPageControllers(),
            ),
          ),
        );
      }
    }
    
    class MyPageControllers extends StatefulWidget {
      @override
      _MyPageControllersState createState() => _MyPageControllersState();
    }
    
    class _MyPageControllersState extends State<MyPageControllers> {
    
      PageController _controller1;
      bool isScrollManual = false;
    
      @override
      void initState() {
        super.initState();
        _controller1 = PageController();
    
        _controller1.addListener(() {
          if(isScrollManual){
            /// Manual Scroll
          }else{
            /// Programmatic scroll
          }
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.white,
          body: GestureDetector(
            onTapDown: (tapDownDetails){
              isScrollManual = true;
              setState(() {});
            },
            onTapUp: (tapUpDetails){
              isScrollManual = false;
              setState(() {});
            },
            child: PageView.builder(
              controller: _controller1,
              itemBuilder: _itemBuilder,
            ),
          ),
        );
      }
    
      Widget _itemBuilder(BuildContext context, int index) =>
          Container(
            color: Colors.primaries[index % Colors.primaries.length],
            child: Center(
              child: Text(
                index.toString(),
                style: TextStyle(color: Colors.white, fontSize: 60),
              ),
            ),
          );
    }