Flutter: Get Current Scroll Offset in ListView/GridView

Updated: September 1, 2023 By: A Goodman Post a comment

This article shows you how to get the current scroll offset (that indicates the distance to the top, in pixels) in a ListView or other scrollable widgets in Flutter like GridView, SingleSchildScrollView, CustomScrollView, etc. We’ll discover the mechanism and then examine a complete example of applying that mechanism in practice.

What Is The Point?

To calculate the scroll offset in a scrollable widget in Flutter, the first thing to do is to initialize a ScrollController instance:

final ScrollController _controller = ScrollController();

Then connect this controller to the scrollable widget:

ListView.builder(
        // attact the controller
        controller: _controller,
        /* ... */
),

Now you can add a listener and get the result like so:

@override
void initState() {
    _controller.addListener(() {
        // print out the scroll offset
        print(_controller.offset);
    });
    super.initState();
}

If the list view is scrolled to its end, the scroll offset will be equal to or greater than the following:

controller.position.maxScrollExtent

To attain a better understanding, see the complete example below.

Example

App Preview

The demo app we’re going to make contains an app bar, a list view (which renders a lot of items), and a bottom bar. The scroll offset will be shown in the bottom bar. The background color of the bottom bar depends on the scroll offset:

  • If the scroll offset is equal to or less than zero, the background is green (it’s possible that a scroll offset is a negative number)
  • If you scroll to the end of the list view, the background color will be red. In addition, a snackbar with the message “You have reached the end of the list view” will appear
  • In the remaining cases, the bottom bar’s background is blue

Words might be confusing and boring. Here’s how it works:

This example aims to demonstrate how you can retrieve the scroll position in a scrollable widget and detect whether it is scrolled to the bottom/top. Updating the UI every time the user scrolls might be heavy and expensive.

The Code

The full source code in main.dart (with explanations in the comments):

// main.dart
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // Remove the debug banner
      debugShowCheckedModeBanner: false,
      title: 'KindaCode.com',
      theme: ThemeData(
        useMaterial3: true,
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  // Generate dummy data to fill the list view
  final List _items = List.generate(50, (index) => 'Item $index');

  // The controller that is assigned to the list view
  final ScrollController _controller = ScrollController();

  // The scroll offset
  double _scrollOffset = 0;

  // The maximum scroll offset
  // In other words, this means the user has reached the bottom of the list view
  double? _maxOffset;

  @override
  void initState() {
    _controller.addListener(() {
      _maxOffset = _controller.position.maxScrollExtent;
      setState(() {
        _scrollOffset = _controller.offset;
        if (_maxOffset != null && _scrollOffset >= _maxOffset!) {
          ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
              content: Text('You have reached the end of the list view')));
        } else {
          ScaffoldMessenger.of(context).removeCurrentSnackBar();
        }
      });
    });

    super.initState();
  }

  // Discards any resources used by the scroll controller object
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('KindaCode.com')),
      // implement the list view
      body: ListView.builder(
        // attact the controller
        controller: _controller,
        itemBuilder: ((context, index) => Card(
              key: ValueKey(_items[index]),
              margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
              elevation: 6,
              color: Colors.amber.shade100,
              child: ListTile(
                title: Text(_items[index]),
              ),
            )),
        itemCount: _items.length,
      ),
      // display the scroll offset
      bottomNavigationBar: BottomAppBar(
        elevation: 6,
        // set the background color of the bottom bar based on the the current offset position
        // if at the top: green
        // if at the bottom: red
        // otherwise: blue
        color: _scrollOffset <= 0
            ? Colors.green
            : _maxOffset != null && _scrollOffset >= _maxOffset!
                ? Colors.red
                : Colors.blue,
        child: Padding(
          padding: const EdgeInsets.only(top: 20, left: 20),
          child: Text(
            "Offset: ${_scrollOffset.toString()}",
            style: const TextStyle(
                fontSize: 21, color: Colors.white, fontWeight: FontWeight.bold),
          ),
        ),
      ),
    );
  }
}

Conclusion

You’ve learned how to find the scroll offset position in a scrollable widget in Flutter. If you’d like to explore more new and interesting stuff about ListView, GridView, and things like that, take a look at the following articles:

You can also tour around our Flutter topic page or Dart topic page for the most recent tutorials and examples.

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

Related Articles