akshathjain / sliding_up_panel

A draggable Flutter widget that makes implementing a SlidingUpPanel much easier!
https://pub.dartlang.org/packages/sliding_up_panel
Other
1.36k stars 378 forks source link

ListView inside SlidingUpPanel cannot be scrolled using keyboard or programmatically #239

Open MarcusWichelmann opened 3 years ago

MarcusWichelmann commented 3 years ago

Describe the bug I have used panelBuilder to add a ListView to the panel as described in the readme. Because this is a TV app, I'm now trying to scroll this list only with the up/down keys of the keyboard. Normally, the ListView widget automatically ensures, that the focused item is always visible by implicitly scrolling to it. But this mechanism doesn't seem to work when the ListView is inside a SlidingUpPanel. Instead, the scrollable list is stuck at the top and can only be scrolled with the keyboard after once moving it with the mouse (emulator). After having it once scrolled, the implicit scrolling does work flawlessly until I close and re-open the panel, then it's stuck again.

I also experimented with scrollController.position.ensureVisible() and scrollController.position.animateTo(), but these methods don't have any effect until having the list once scrolled manually, too.

To Reproduce

Expected behavior When moving the focus far enough down so that the focused item is no longer visible, the list inside the opened SlidingUpPanel should automatically scroll down a bit to make the focused item visible. Also, the ensureVisible and animateTo methods should always work (at least when the panel is open).

Screenshots If applicable, add screenshots to help explain your problem.

Device

Sample main.dart

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

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

class MyApp extends StatelessWidget {
  final PanelController _panelController = PanelController();

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Test'),
        ),
        body: SlidingUpPanel(
          controller: _panelController,
          panelBuilder: _buildListView,
        ),
      ),
    );
  }

  Widget _buildListView(ScrollController scrollController) {
    return ListView.builder(
      controller: scrollController,
      itemCount: 100,
      itemBuilder: (context, index) {
        return Builder(
          builder: (context) {
            return Focus(
              onFocusChange: (value) {
                if (value) {
                  if (!_panelController.isPanelOpen) {
                    _panelController.open();
                  }

                  // Experimented with programmatic scrolling:
                  //
                  //     // Scroll, so the focused widget is always vertically centered
                  //     scrollController.position.ensureVisible(
                  //       context.findRenderObject()!,
                  //       alignmentPolicy: ScrollPositionAlignmentPolicy.explicit,
                  //       alignment: 0.5,
                  //       curve: Curves.ease,
                  //       duration: const Duration(milliseconds: 300),
                  //     );
                  //
                }
              },
              child: Container(
                margin: const EdgeInsets.all(10),
                child: Builder(
                  builder: (context) {
                    return Text(
                      (Focus.of(context).hasFocus ? 'Focused' : 'Unfocused') +
                          ' - $index',
                    );
                  },
                ),
              ),
            );
          },
        );
      },
    );
  }
}

Thanks for building this great package!

MarcusWichelmann commented 3 years ago

Does anyone have an idea? If something is unclear with my issue description, please let me know.

naamapps commented 3 years ago

The package prevents the list view from scrolling when the panel is not expended and scrolled already. I think it requires a change in the code for it to scroll programmatically before the user interaction with the panel.

hauketoenjes commented 3 years ago

Hi, I just looked over the code and the problem is the use of buildPanel(ScrollController sc). This ScrollController is not the one that is responsible for any ListView inside of the panel.

Nonetheless, in the code above the ScrollController of the panel is given to the ListView inside the panel. Hence, the ListView Widget does not automatically keep the focused item in view because it doesn't know how with the panel's ScrollController.

The better way of achieving what you are trying to do would probably to just give the ListView widget to the panel via the panel parameter. Then the above example works as expected (look at the code below). If you need to access the ScrollController of the ListView inside the panel you can just give it a new one and do it the "normal" way.

Here's the above example (working as intended):

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

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

class MyApp extends StatelessWidget {
  final PanelController _panelController = PanelController();

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Test'),
        ),
        body: SlidingUpPanel(
          controller: _panelController,
          panel: _buildListView(), // Use panel instead of panelBuilder
        ),
      ),
    );
  }

  Widget _buildListView() {
    return ListView.builder(
      // controller: scrollController, // Don't give the scrollController of the panel to the ListView
      itemCount: 100,
      itemBuilder: (context, index) {
        return Builder(
          builder: (context) {
            return Focus(
              onFocusChange: (value) {
                if (value) {
                  if (!_panelController.isPanelOpen) {
                    _panelController.open();
                  }

                  // Experimented with programmatic scrolling:
                  //
                  //     // Scroll, so the focused widget is always vertically centered
                  //     scrollController.position.ensureVisible(
                  //       context.findRenderObject()!,
                  //       alignmentPolicy: ScrollPositionAlignmentPolicy.explicit,
                  //       alignment: 0.5,
                  //       curve: Curves.ease,
                  //       duration: const Duration(milliseconds: 300),
                  //     );
                  //
                }
              },
              child: Container(
                margin: const EdgeInsets.all(10),
                child: Builder(
                  builder: (context) {
                    return Text(
                      (Focus.of(context).hasFocus ? 'Focused' : 'Unfocused') +
                          ' - $index',
                    );
                  },
                ),
              ),
            );
          },
        );
      },
    );
  }
}
MarcusWichelmann commented 3 years ago

@hauketoenjes Thank you very much, this solved my issue and keyboard scrolling works fine now. Only the the touch input is a bit broken now because scrolling doesn't open the panel anymore. But this is easily fixable by opening the panel programmatically as soon as scrolling begins.

I'll leave this bug report open, because a fix that doesn't require breaking touch input would still be useful.

hauketoenjes commented 3 years ago

@MarcusWichelmann Ok this particular problem can be solved for "only" keyboard scrolling like you said with my example above.

I looked a bit deeper in this package and the main problem is, that the SlidingUpPanel works in a way, that it automatically scrolls / jumps to the top of the ScrollView attached to the ScrollController given by panelBuilder(ScrollController sc). This functionality is used to prevent the ScrollView inside from scrolling while the panel itself is being slid up. This mechanism is then disabled when the user scrolls via touch input:

The "jumping" can be found here:

https://github.com/akshathjain/sliding_up_panel/blob/466d2f5d0d4327815af1dc596c997eeea10deb8a/lib/src/panel.dart#L241-L244

And the disabling / enabling here:

https://github.com/akshathjain/sliding_up_panel/blob/466d2f5d0d4327815af1dc596c997eeea10deb8a/lib/src/panel.dart#L481-L489

Since the boolean is only set on touch / pointer input, the panel is not scrollable (or at least is prevented from scrolling through the jumpTo(0)) when opened programmatically.

Nonetheless to enable the functionality of scrolling when opening the panel programmatically we could just set _scrollingEnabled = true; on open.

I will make a pull request adding this feature. I think it does not break anything else when setting the boolean in the open() function here:

https://github.com/akshathjain/sliding_up_panel/blob/466d2f5d0d4327815af1dc596c997eeea10deb8a/lib/src/panel.dart#L668-L671

This would still allow the normal behavior for touch input, but also allow navigating via keyboard only.

Also this is in a way related to #236 and should be added together.