quire-io / scroll-to-index

scroll to index with fixed/variable row height inside Flutter scrollable widget
MIT License
518 stars 105 forks source link

scrollToIndex not working when defining SmartRefresher as parent of SingleChildScrollView #59

Closed jcblancomartinez closed 3 years ago

jcblancomartinez commented 3 years ago

Describe the bug

scrollToIndex is not working when defining SmartRefresher as parent of SingleChildScrollView. scrollToIndexis working as expected wihout SmartRefresher as parent of SingleChildScrollView.

Reproduction code

Code working as expected when no SmartRefresher is defined as parent of SingleChildScrollView:


import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:scroll_to_index/scroll_to_index.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Scroll To Index Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Scroll To Index Demo'),
    );
  }
}

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 scrollDirection = Axis.vertical;

  late AutoScrollController controller;
  int count = 2;

  @override
  void initState() {
    super.initState();
    controller = AutoScrollController(
        viewportBoundaryGetter: () =>
            Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
        axis: scrollDirection);
    controller.scrollToIndex(1, preferPosition: AutoScrollPosition.begin);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        controller: controller,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisAlignment: MainAxisAlignment.end,
          children: _buildChildren(),
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    controller.dispose();
  }

  Widget _wrapScrollTag(
          {required int index,
          required AutoScrollController controller,
          required Widget child}) =>
      AutoScrollTag(
          key: ValueKey(index),
          controller: controller,
          index: index,
          child: child);

  List<Widget> _buildChildren() {
    List<Widget> widgets = [];
    for (int i = 0; i < count; i++) {
      widgets.add(_wrapScrollTag(
          index: i,
          controller: controller,
          child: Container(
              height: 520.0,
              width: 400.0,
              color: i.isEven ? Colors.yellow : Colors.green,
              padding:
                  new EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
              child: Text(i.toString()))));
    }
    return widgets;
  }
}

Code not working as expected when SmartRefresher is defined as parent of SingleChildScrollView:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:scroll_to_index/scroll_to_index.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Scroll To Index Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Scroll To Index Demo'),
    );
  }
}

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 scrollDirection = Axis.vertical;

  final RefreshController _refreshController = RefreshController();
  late AutoScrollController controller;
  int count = 2;

  @override
  void initState() {
    super.initState();
    controller = AutoScrollController(
        viewportBoundaryGetter: () =>
            Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
        axis: scrollDirection);
    controller.scrollToIndex(1, preferPosition: AutoScrollPosition.begin);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SmartRefresher(
          controller: _refreshController,
          enablePullDown: false,
          enablePullUp: true,
          onLoading: _onLoading,
          child: SingleChildScrollView(
            controller: controller,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisAlignment: MainAxisAlignment.end,
              children: _buildChildren(),
            ),
          )),
    );
  }

  @override
  void dispose() {
    super.dispose();
    controller.dispose();
    _refreshController.dispose();
  }

  Widget _wrapScrollTag(
          {required int index,
          required AutoScrollController controller,
          required Widget child}) =>
      AutoScrollTag(
          key: ValueKey(index),
          controller: controller,
          index: index,
          child: child);

  void _onLoading() {
    _refreshController.loadComplete();
    setState(() {
      count++;
    });
  }

  List<Widget> _buildChildren() {
    List<Widget> widgets = [];
    for (int i = 0; i < count; i++) {
      widgets.add(_wrapScrollTag(
          index: i,
          controller: controller,
          child: Container(
              height: 520.0,
              width: 400.0,
              color: i.isEven ? Colors.yellow : Colors.green,
              padding:
                  new EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
              child: Text(i.toString()))));
    }
    return widgets;
  }
}

To Reproduce Steps to reproduce the behavior:

  1. Load the page

Expected behavior

scrollToIndex should take us to the second container.

Flutter Version:

juancarlos@juancarlos-XPS-15-7590:~/AndroidStudioProjects/solon_flutter_app$ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 2.2.0, on Linux, locale en_US.UTF-8) [✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2) [✓] Chrome - develop for the web [✓] Android Studio (version 4.1) [✓] Connected device (2 available)

• No issues found!

Dependencies:

pull_to_refresh: ^2.0.0 scroll_to_index: ^2.0.0

Describe on which device you found the bug: Android 11 emulator

jcblancomartinez commented 3 years ago

@jerrywell When SmartRefresher is defined as parent of SingleChildScrollView, _RenderSingleChildViewport's _maxScrollExtent is 0.0. My understanding is that both SmartRefresher and SingleChildScrollView have the same height.

Could you please help me debug this issue?

Thanks.

jerrywell commented 3 years ago

try to use ListView with SingleChildScrollView ?

jcblancomartinez commented 3 years ago

@jerrywell could you please modify the code example where SmartRefresher is defined as parent of SingleChildScrollView with your proposal and paste it here?

Anyways, in the code example where SmartRefresher is not defined as parent of SingleChildScrollView, I'm not using ListView but SingleChildScrollView and it works fine.

Is there any fix that requires no ListView?

Thanks. Thank you.

jerrywell commented 3 years ago

unfortunately SmartRefresher is not official package. we are using the official RefreshIndicator without any issue, you can also check it. pull request is welcome. thanks : )

jcblancomartinez commented 3 years ago

Replacing SingleChildScrollView with ListView worked as suggested.

Thanks.