[Solved] How to scroll underlying widget in Flutter?

Question

Asked by DarkMath on December 04, 2021 (source).

I have a simple app with two screens. The first screen is a scrollable ListView and the second screen is basically empty and transparent. If I pushed the second screen with Navigator.push() on top of the first screen I'd like to be able to scroll the underlying first screen.

Here is my code:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView.builder(
        itemBuilder: (context, index) {
          return Text("$index");
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
            context,
            PageRouteBuilder<void>(
              opaque: false, // push route with transparency
              pageBuilder: (context, animation, secondaryAnimation) => Foo(),
            ),
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}


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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white.withOpacity(0.5),
      appBar: AppBar(
        title: Text("I'm on top"),
      ),
    );
  }
}

How can scroll the list in the backgound while the second screen is in the foreground?

Answer

Question answered by DarkMath (source).

I finally found a satisfying answer that does not contain any kind of dirty workarounds. I use a Listener in the second screen to detect OnPointerMoveEvents which are basically scroll events.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final ScrollController controller = ScrollController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView.builder(
        controller: controller,
        itemBuilder: (context, index) {
          return Text("$index");
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
            context,
            PageRouteBuilder<void>(
              opaque: false, // push route with transparency
              pageBuilder: (context, animation, secondaryAnimation) => Foo(
                controller: controller,
              ),
            ),
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

class Foo extends StatefulWidget {
  final ScrollController controller;
  const Foo({required this.controller, Key? key}) : super(key: key);

  @override
  State<Foo> createState() => _FooState();
}

class _FooState extends State<Foo> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white.withOpacity(0.5),
      appBar: AppBar(
        title: Text("I'm on top"),
      ),
      body: Listener(
        onPointerMove: (event){
          var newPosition = widget.controller.position.pixels - event.delta.dy;
          widget.controller.jumpTo(newPosition);
        },
        child: Container(
          height: MediaQuery.of(context).size.height,
          width: MediaQuery.of(context).size.width,
          color: Colors.red.withOpacity(0.5),
        ),
      ),
    );
  }
}
FLUTTER
SHARE: