Skip to content

NestedScrollView scrolls inner view without scrolling outer view #136199

Description

@rorystephenson

Is there an existing issue for this?

Steps to reproduce

Example reproduction for Android:

  1. Start with the code from the first NestedScrollView example.
  2. Set the physics of both the NestedScrollView and the nested CustomScrollView to BouncingScrollPhysics() (not necessary on iOS where it is the default).
  3. Drag the finger down on the screen, holding, then flick it up. The bug does not get triggered every time, it takes some tries.

Expected results

When flicking up the inner scroll content should first scroll back to it's zero scroll position, then the outer scroll view should scroll to its maximum scroll position before the inner scroll view starts scrolling.

Actual results

The inner scroll view is scrolled without the outer scroll view getting scrolled. When I then subsequently scroll a little further down the outer scroll view gets scrolled and then the inner scroll view continues scrolling.

Code sample

Note the code sample could potentially me more minimal but I preferred to start from the example code and make the minimal changes necessary to reproduce.

Code sample
import 'package:flutter/material.dart';

/// Flutter code sample for [NestedScrollView].

void main() => runApp(const NestedScrollViewExampleApp());

class NestedScrollViewExampleApp extends StatelessWidget {
  const NestedScrollViewExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: NestedScrollViewExample(),
    );
  }
}

class NestedScrollViewExample extends StatelessWidget {
  const NestedScrollViewExample({super.key});

  @override
  Widget build(BuildContext context) {
    final List<String> tabs = <String>['Tab 1', 'Tab 2'];
    return DefaultTabController(
      length: tabs.length, // This is the number of tabs.
      child: Scaffold(
        body: NestedScrollView(
          physics: const BouncingScrollPhysics(),
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
            // These are the slivers that show up in the "outer" scroll view.
            return <Widget>[
              SliverOverlapAbsorber(
                // This widget takes the overlapping behavior of the SliverAppBar,
                // and redirects it to the SliverOverlapInjector below. If it is
                // missing, then it is possible for the nested "inner" scroll view
                // below to end up under the SliverAppBar even when the inner
                // scroll view thinks it has not been scrolled.
                // This is not necessary if the "headerSliverBuilder" only builds
                // widgets that do not overlap the next sliver.
                handle:
                    NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                sliver: SliverAppBar(
                  title:
                      const Text('Books'), // This is the title in the app bar.
                  pinned: true,
                  expandedHeight: 150.0,
                  // The "forceElevated" property causes the SliverAppBar to show
                  // a shadow. The "innerBoxIsScrolled" parameter is true when the
                  // inner scroll view is scrolled beyond its "zero" point, i.e.
                  // when it appears to be scrolled below the SliverAppBar.
                  // Without this, there are cases where the shadow would appear
                  // or not appear inappropriately, because the SliverAppBar is
                  // not actually aware of the precise position of the inner
                  // scroll views.
                  forceElevated: innerBoxIsScrolled,
                  bottom: TabBar(
                    // These are the widgets to put in each tab in the tab bar.
                    tabs: tabs.map((String name) => Tab(text: name)).toList(),
                  ),
                ),
              ),
            ];
          },
          body: TabBarView(
            // These are the contents of the tab views, below the tabs.
            children: tabs.map((String name) {
              return SafeArea(
                top: false,
                bottom: false,
                child: Builder(
                  // This Builder is needed to provide a BuildContext that is
                  // "inside" the NestedScrollView, so that
                  // sliverOverlapAbsorberHandleFor() can find the
                  // NestedScrollView.
                  builder: (BuildContext context) {
                    return CustomScrollView(
                      physics: const BouncingScrollPhysics(),
                      // The "controller" and "primary" members should be left
                      // unset, so that the NestedScrollView can control this
                      // inner scroll view.
                      // If the "controller" property is set, then this scroll
                      // view will not be associated with the NestedScrollView.
                      // The PageStorageKey should be unique to this ScrollView;
                      // it allows the list to remember its scroll position when
                      // the tab view is not on the screen.
                      key: PageStorageKey<String>(name),
                      slivers: <Widget>[
                        SliverOverlapInjector(
                          // This is the flip side of the SliverOverlapAbsorber
                          // above.
                          handle:
                              NestedScrollView.sliverOverlapAbsorberHandleFor(
                                  context),
                        ),
                        SliverPadding(
                          padding: const EdgeInsets.all(8.0),
                          // In this example, the inner scroll view has
                          // fixed-height list items, hence the use of
                          // SliverFixedExtentList. However, one could use any
                          // sliver widget here, e.g. SliverList or SliverGrid.
                          sliver: SliverFixedExtentList(
                            // The items in this example are fixed to 48 pixels
                            // high. This matches the Material Design spec for
                            // ListTile widgets.
                            itemExtent: 48.0,
                            delegate: SliverChildBuilderDelegate(
                              (BuildContext context, int index) {
                                // This builder is called for each child.
                                // In this example, we just number each list item.
                                return ListTile(
                                  tileColor: Colors.blue.shade100,
                                  title: Text('Item $index'),
                                );
                              },
                              // The childCount of the SliverChildBuilderDelegate
                              // specifies how many children this inner list
                              // has. In this example, each tab has a list of
                              // exactly 30 items, but this is arbitrary.
                              childCount: 30,
                            ),
                          ),
                        ),
                      ],
                    );
                  },
                ),
              );
            }).toList(),
          ),
        ),
      ),
    );
  }
}

Screenshots or Video

Screenshots / Video demonstration
23-10-09-15-06-48.mp4

Logs

Logs
[Paste your logs here]

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.13.6, on macOS 14.0 23A344 darwin-arm64, locale en-AU)
    • Flutter version 3.13.6 on channel stable at /Users/rorystephenson/sdk/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision ead455963c (13 days ago), 2023-09-26 18:28:17 -0700
    • Engine revision a794cf2681
    • Dart version 3.1.3
    • DevTools version 2.25.0

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    • Android SDK at /Users/rorystephenson/sdk/android
    • Platform android-34, build-tools 34.0.0
    • ANDROID_HOME = /Users/rorystephenson/sdk/android
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.0)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 15A240d
    • CocoaPods version 1.13.0

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2022.3)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)

[✓] VS Code (version 1.82.3)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension can be installed from:
      🔨 https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter

[✓] Connected device (4 available)
    • ONEPLUS A6000 (mobile)  • 2ed743fb                             • android-arm64  • Android 11 (API 30)
    • iPhone 15 Plus (mobile) • D3649EE4-B818-4DA5-A5BB-DA903E38636C • ios            • com.apple.CoreSimulator.SimRuntime.iOS-17-0 (simulator)
    • macOS (desktop)         • macos                                • darwin-arm64   • macOS 14.0 23A344 darwin-arm64
    • Chrome (web)            • chrome                               • web-javascript • Google Chrome 117.0.5938.149

[✓] Network resources
    • All expected network resources are available.

• No issues found!

Activity

  1. added
    in triagePresently being triaged by the triage team
    on Oct 10, 2023
  2. huycozy commented on Oct 10, 2023

    @huycozy
    Member

    Thanks for the report. Reproduced this on master channel as well.

    I think the actual behavior/result is: The outer view gets scrolled but gets "restored" to the old position when flicking the list.

    flutter doctor -v (stable and master)
    [✓] Flutter (Channel stable, 3.13.6, on macOS 14.0 23A344 darwin-x64, locale en-VN)
        • Flutter version 3.13.6 on channel stable at /Users/huynq/Documents/GitHub/flutter
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision ead455963c (5 days ago), 2023-09-26 18:28:17 -0700
        • Engine revision a794cf2681
        • Dart version 3.1.3
        • DevTools version 2.25.0
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 32.0.0)
        • Android SDK at /Users/huynq/Library/Android/sdk
        • Platform android-34, build-tools 32.0.0
        • ANDROID_HOME = /Users/huynq/Library/Android/sdk
        • Java binary at: /Applications/Android Studio Giraffe.app/Contents/jbr/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 15.0)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Build 15A240d
        • CocoaPods version 1.11.3
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] Android Studio (version 2022.3)
        • Android Studio at /Applications/Android Studio Giraffe.app/Contents
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
    
    [✓] Android Studio (version 2022.2)
        • Android Studio at /Applications/Android Studio.app/Contents
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694)
    
    [✓] VS Code (version 1.82.2)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.72.0
    
    [✓] Connected device (2 available)
        • macOS (desktop) • macos  • darwin-x64     • macOS 14.0 23A344 darwin-x64
        • Chrome (web)    • chrome • web-javascript • Google Chrome 117.0.5938.132
    
    [✓] Network resources
        • All expected network resources are available.
    
    • No issues found!
    [!] Flutter (Channel master, 3.16.0-8.0.pre.15, on macOS 14.0 23A344 darwin-x64, locale en-VN)
        • Flutter version 3.16.0-8.0.pre.15 on channel master at /Users/huynq/Documents/GitHub/flutter_master
        ! Warning: `flutter` on your path resolves to /Users/huynq/Documents/GitHub/flutter/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/huynq/Documents/GitHub/flutter_master. Consider adding /Users/huynq/Documents/GitHub/flutter_master/bin to the front of your path.
        ! Warning: `dart` on your path resolves to /Users/huynq/Documents/GitHub/flutter/bin/dart, which is not inside your current Flutter SDK checkout at /Users/huynq/Documents/GitHub/flutter_master. Consider adding /Users/huynq/Documents/GitHub/flutter_master/bin to the front of your path.
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision 0194d93665 (45 minutes ago), 2023-10-09 21:58:28 -0400
        • Engine revision 92880e9847
        • Dart version 3.3.0 (build 3.3.0-3.0.dev)
        • DevTools version 2.28.1
        • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades.
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 32.0.0)
        • Android SDK at /Users/huynq/Library/Android/sdk
        • Platform android-34, build-tools 32.0.0
        • ANDROID_HOME = /Users/huynq/Library/Android/sdk
        • Java binary at: /Applications/Android Studio Giraffe.app/Contents/jbr/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 15.0)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Build 15A240d
        • CocoaPods version 1.13.0
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] Android Studio (version 2022.3)
        • Android Studio at /Applications/Android Studio Giraffe.app/Contents
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
    
    [✓] Android Studio (version 2022.2)
        • Android Studio at /Applications/Android Studio.app/Contents
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694)
    
    [✓] VS Code (version 1.82.3)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.74.0
    
    [✓] Connected device (3 available)
        • RMX2001 (mobile) • EUYTFEUSQSRGDA6D • android-arm64  • Android 11 (API 30)
        • macOS (desktop)  • macos            • darwin-x64     • macOS 14.0 23A344 darwin-x64
        • Chrome (web)     • chrome           • web-javascript • Google Chrome 117.0.5938.149
    
    [✓] Network resources
        • All expected network resources are available.
    
    ! Doctor found issues in 1 category.
  3. added
    frameworkflutter/packages/flutter repository. See also f: labels.
    f: scrollingViewports, list views, slivers, etc.
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    and removed
    in triagePresently being triaged by the triage team
    on Oct 10, 2023
  4. rorystephenson commented on Oct 10, 2023

    @rorystephenson
    Author

    cc @Piinks as I noticed you have been aggregating the various NestedScrollView issues.

  5. added
    P2Important issues not at the top of the work list
    on Oct 10, 2023
  6. Michal-MK commented on Nov 11, 2023

    @Michal-MK
    Contributor

    Adding a +1 to this.

    I have another sample that reproduces this.

    When I remove the SliverOverlapInjector it makes it more difficult to reproduce, but it is still possible. And only with BouncingScrollPhysics.

    Edit: Minimizing the sample.

    class NestedScrollViewExampleApp extends StatelessWidget {
      const NestedScrollViewExampleApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: NestedScrollView(
            headerSliverBuilder: (context, innerBoxIsScrolled) => [
              SliverToBoxAdapter(
                child: Container(color: Colors.green, height: 300),
              ),
              SliverOverlapAbsorber(
                handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                sliver: SliverToBoxAdapter(
                  child: Container(
                    color: Colors.blue,
                    height: 64,
                  ),
                ),
              ),
            ],
            body: SingleChildScrollView(
              child: Container(
                decoration: gradient(),
                height: 800,
              ),
            ),
          ),
        );
      }
    
      BoxDecoration gradient() {
        return const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [Colors.black, Colors.blue],
            stops: [0, 1],
          ),
        );
      }
    }

    I have a video as well, I am repeating swipe up swipe down motion repeatedly my finger leaves the screen each time.

    swiping.mp4
  7. Michal-MK commented on Nov 11, 2023

    @Michal-MK
    Contributor

    I have never delved in the Flutter framework code so I'll track my investigation here hopefully someone with more experience will then find this useful and create a PR:
    So far it seems that the function _getMetrics is the culprit.
    image

  8. Michal-MK commented on Nov 11, 2023

    @Michal-MK
    Contributor

    Inside the extra variable, the value is suspicious when the "jump" happens.

    image
  9. Michal-MK commented on Nov 11, 2023

    @Michal-MK
    Contributor

    While testing that I also came across this behavior, after flinging, but not quite till the top of the outer scrollable, the inner scrollable still performs "bounce" The first fling is bad, ignore that, but the subsequent ones show the issue. The white background should not be visible imo. The second fling up presented the original issue.

    residual_scroll.mp4
  10. Michal-MK commented on Nov 11, 2023

    @Michal-MK
    Contributor

    I think I found it:

    We want to go to the underlined if statement, but the inner position is not fully setteled? yet; or there is a rounding error when the spring animation is finishing; or the spring animation finishes but does not properly set the inner scroll position to 0 but rather keeps the spring's animation final position, which may overshoot the 0? All these are guesses.

    image

    Edit: this turned out to be incorrect as the underlined condition is in an 'else' branch that would be unreachable if the value == 0.

  11. Michal-MK commented on Nov 11, 2023

    @Michal-MK
    Contributor

    So for anyone wanting a quick dirty fix, throw this at the beginning of _getMetrics()

        if (innerPosition.pixels <= precisionErrorTolerance && innerPosition.pixels >= -precisionErrorTolerance) {
          innerPosition.correctPixels(0.0);
        }

    It just hides the underlying issue, which I am now committed to finding and fixing, once I figure out how to set up a dev env for Flutter that is :)

  12. Michal-MK commented on Nov 12, 2023

    @Michal-MK
    Contributor

    So this is likely the proper fix:

    Either check the addition and if it is within the precisionErrorTolerance return proper 0.
    -OR-
    Instead of == on doubles, convert the check to an .abs() with the precisionErrorTolerance.

    image
  13. added a commit that references this issue on Jan 10, 2024
    7efed85
  14. added
    r: fixedIssue is closed as already fixed in a newer version
    on Jan 10, 2024
  15. github-actions commented on Jan 24, 2024

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  16. locked as resolved and limited conversation to collaborators on Jan 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listf: scrollingViewports, list views, slivers, etc.found in release: 3.13Found to occur in 3.13found in release: 3.16Found to occur in 3.16frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onr: fixedIssue is closed as already fixed in a newer versionteam-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions