Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent scrolling behavior in nested ListViews #148024

Closed
darajava opened this issue May 8, 2024 · 5 comments
Closed

Inconsistent scrolling behavior in nested ListViews #148024

darajava opened this issue May 8, 2024 · 5 comments
Labels
in triage Presently being triaged by the triage team waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds

Comments

@darajava
Copy link

darajava commented May 8, 2024

Steps to reproduce

  1. Run the sample provided on Web
  2. Also run on MacOS
  3. Scroll down on both platforms until cursor is over the gray area
  4. Keep scrolling after you reach the end of the gray area

Expected results

Behaviour should be as on web on both:

  1. Other platforms with trackpad devices (i.e. MacOS)
  2. When using dragging to scroll the inner ListView

Actual results

(correct behavior) On desktop web, when scrolling using the trackpad, the scroll correctly continues and the parent scrolls down after the gray area is scrolled to the end.

(incorrect behavior) On MacOS, when scrolling using the trackpad, it is not possible to keep scrolling the parent when reached the end of the gray area.

(incorrect behavior) When dragging on Web/MacOS/Android/iOS, it is not possible to keep scrolling the parent after reaching the end of the gray area.

Code sample

Code sample
import 'dart:ui';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        scrollBehavior: MyCustomScrollBehavior(),
        home: Scaffold(
          body: ListView(
            children: [
              for (int i = 0; i < 20; i++) ListItem(),
              SizedBox(
                height: 220,
                child: ListView(
                  shrinkWrap: true,
                  children: [
                    for (int i = 0; i < 20; i++) ListItem2(),
                  ],
                ),
              ),
              for (int i = 0; i < 20; i++) ListItem(),
            ],
          ),
        ));
  }
}

class ListItem extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text('Item'),
      ),
    );
  }
}

class ListItem2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text('Item 2'),
      ),
    );
  }
}

class MyCustomScrollBehavior extends MaterialScrollBehavior {
  @override
  Set<PointerDeviceKind> get dragDevices => {
        PointerDeviceKind.touch,
        PointerDeviceKind.mouse,
        PointerDeviceKind.stylus,
        PointerDeviceKind.trackpad
      };
}

Screenshots or Video

Needs sound to make sense as I do a voiceover.

Screenshots / Video demonstration
Screen.Recording.2024-05-09.at.00.08.21.mov

Logs

Logs
[Paste your logs here]

Flutter Doctor output

Doctor output
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.19.4, on macOS 13.5 22G74 darwin-arm64, locale en-GB)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.0.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2022.3)
[✓] VS Code (version 1.88.1)
[✓] Connected device (3 available)
[✓] Network resources

• No issues found!```

</details>
@huycozy huycozy added the in triage Presently being triaged by the triage team label May 9, 2024
@huycozy
Copy link
Member

huycozy commented May 9, 2024

Hi @darajava, please take a look at this #99207 (comment). You should try NestedScrollView as suggested there to see if it can solve your case.

@huycozy huycozy added the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label May 9, 2024
@moffatman
Copy link
Contributor

So far we didn't do this, because trackpad/touch scroll has physics/curving that depends on the current state of the scrollable. Not sure how well a simple fix would work where we don't claim the drag if already at max scroll extent. And don't have bouncing scrolling.

I think in general this wasn't a priority, because nested scroll views are a very poor experience on mobile, where scrollable areas are implicit and assumed to be continuous and full-width.

@darajava
Copy link
Author

darajava commented May 9, 2024

@huycozy I have tried to implement NestedScrollView, but it results in the same effect. I might be using it wrong though:

import 'dart:ui';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        scrollBehavior: MyCustomScrollBehavior(),
        home: Scaffold(
          body: ListView(
            children: [
              for (int i = 0; i < 20; i++) ListItem(),
              SizedBox(
                height: 220,
                child: NestedScrollView(
                  reverse: false,
                  body: ListView(
                    children: [
                      for (int i = 0; i < 20; i++) ListItem2(),
                    ],
                  ),
                  headerSliverBuilder: (context, innerBoxIsScrolled) {
                    return [];
                  },
                ),
              ),
              for (int i = 0; i < 20; i++) ListItem(),
            ],
          ),
        ));
  }
}

class ListItem extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text('Item'),
      ),
    );
  }
}

class ListItem2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text('Item 2'),
      ),
    );
  }
}

class MyCustomScrollBehavior extends MaterialScrollBehavior {
  @override
  Set<PointerDeviceKind> get dragDevices => {
        PointerDeviceKind.touch,
        PointerDeviceKind.mouse,
        PointerDeviceKind.stylus,
        PointerDeviceKind.trackpad
      };
}

@moffatman I take your point as I can't find any examples of any nested scrollbars in any of the apps that I use. Perhaps my UI does not follow standard practice.

Not sure how well a simple fix would work where we don't claim the drag if already at max scroll extent. And don't have bouncing scrolling.

This is exactly what happens on web (html web, not Flutter)

@github-actions github-actions bot removed the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label May 9, 2024
@huycozy
Copy link
Member

huycozy commented May 10, 2024

@darajava Have you tried using NotificationListener for this? The idea I can think of is to check the scroll position of the inner listview to reaches the the end or not, something like this:

Sample code of NotificationListener idea
import 'dart:ui';

import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ScrollPhysics physics = AlwaysScrollableScrollPhysics();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        scrollBehavior: MyCustomScrollBehavior(),
        home: Scaffold(
          body: ListView(
            children: [
              for (int i = 0; i < 20; i++) ListItem(),
              SizedBox(
                height: 220,
                child: NotificationListener(
                  onNotification: (notification) {
                    // check if reaches the end of the list
                    if (notification is ScrollEndNotification) {
                      if (notification.metrics.extentAfter == 0) {
                        print('End of list');
                        setState(() {
                          physics = NeverScrollableScrollPhysics();
                        });
                      }
                    }
                    return true;
                  },
                  child: ListView(
                    shrinkWrap: true,
                    physics: physics,
                    children: [
                      for (int i = 0; i < 20; i++) ListItem2(),
                    ],
                  ),
                ),
              ),
              for (int i = 0; i < 20; i++) ListItem(),
            ],
          ),
        ));
  }
}

class ListItem extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text('Item'),
      ),
    );
  }
}

class ListItem2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text('Item 2'),
      ),
    );
  }
}

class MyCustomScrollBehavior extends MaterialScrollBehavior {
  @override
  Set<PointerDeviceKind> get dragDevices => {PointerDeviceKind.touch, PointerDeviceKind.mouse, PointerDeviceKind.stylus, PointerDeviceKind.trackpad};
}

This sample is not complete because it's only for scrolling down to bottom; you also need to calculate for the case when scrolling up back to the top (depending on your desire).

And I also see this will be a complex behavior that you need to spend much effort on this UX. I'm not sure which app/design you refer to, but if you can share the use case here on other apps, it would be helpful.

@huycozy huycozy added the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label May 10, 2024
Copy link

Without additional information, we are unfortunately not sure how to resolve this issue. We are therefore reluctantly going to close this bug for now.
If you find this problem please file a new issue with the same description, what happens, logs and the output of 'flutter doctor -v'. All system setups can be slightly different so it's always better to open new issues and reference the related ones.
Thanks for your contribution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in triage Presently being triaged by the triage team waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds
Projects
None yet
Development

No branches or pull requests

3 participants