Search code examples
flutterflutter-layout

Flutter : ListView : Scroll parent ListView when child ListView reach bottom - ClampingScrollPhysics not working in sized container


I'm using Flutter version 1.12.13+hotfix.

I'm looking for a solution to be able to scroll inside a ListView and when reached the bottom, automatically give scroll lead to the parent ListView.

enter image description here

The first solution to achieve that is to use "physics: ClampingScrollPhysics()" with "shrinkWrap: true". So I apply this solution to all sub Listview except first one (the red) because I need to wrap it inside a sized Container().

The problem come from the first one... ClampingScrollPhysics() didn't work with sized Container() !

So, when I scroll the red Listview and reach its bottom, scroll stoping... I need to put my finger outside this ListView to be able again to scroll.

@override
  Widget build(BuildContext context) {
    super.build(context);

    print("build MySongs");

    return ListView(
      children: <Widget>[
        Container(
          height: 170,
          margin: EdgeInsets.all(16),
          child: ListView(
            children: <Widget>[
              Container(color: Colors.red, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.red, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.red, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
            ],
          ),
        ),
        Container(
          margin: EdgeInsets.all(16),
          child: ListView(
            physics: ClampingScrollPhysics(),
            shrinkWrap: true,
            children: <Widget>[
              Container(color: Colors.orange, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.orange, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.orange, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
            ],
          ),
        ),
        Container(
          margin: EdgeInsets.all(16),
          child: ListView(
            shrinkWrap: true,
            physics: ClampingScrollPhysics(),
            children: <Widget>[
              Container(color: Colors.blue, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.blue, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.blue, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
            ],
          ),
        ),
        Container(
          margin: EdgeInsets.all(16),
          child: ListView(
            physics: ClampingScrollPhysics(),
            shrinkWrap: true,
            children: <Widget>[
              Container(color: Colors.green, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.green, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.green, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
            ],
          ),
        ),
      ],
    );
  }

Maybe in need post this question on Flutter github issue :/


Solution

  • Thanks for Hamed Hamedi solution :) ! I made a better solution, I think, based on NotificationListener ! (I discovered this functionnality thanks to him).

    @override
      Widget build(BuildContext context) {
        return Container(
          padding: EdgeInsets.all(8),
          color: Colors.yellow,
          child: ListView.builder(
            controller: controller,
            itemBuilder: (c, i) =>
            i == 10
              ? Container(
              height: 150,
              color: Colors.red,
              child: NotificationListener<OverscrollNotification>(
                onNotification: (OverscrollNotification value) {
                  if (value.overscroll < 0 && controller.offset + value.overscroll <= 0) {
                    if (controller.offset != 0) controller.jumpTo(0);
                    return true;
                  }
                  if (controller.offset + value.overscroll >= controller.position.maxScrollExtent) {
                    if (controller.offset != controller.position.maxScrollExtent) controller.jumpTo(controller.position.maxScrollExtent);
                    return true;
                  }
                  controller.jumpTo(controller.offset + value.overscroll);
                  return true;
                },
                child: ListView.builder(
                  itemBuilder: (c, ii) => Text('-->' + ii.toString()),
                  itemCount: 20,
                ),
              ),
            )
              : Text(i.toString()),
            itemCount: 45,
          ),
        );
      }
    

    The solution wrapped into StatelessWidget :

    import 'package:flutter/material.dart';
    
    class ScrollParent extends StatelessWidget {
      final ScrollController controller;
      final Widget child;
    
      ScrollParent({this.controller, this.child});
    
      @override
      Widget build(BuildContext context) {
        return NotificationListener<OverscrollNotification>(
          onNotification: (OverscrollNotification value) {
            if (value.overscroll < 0 && controller.offset + value.overscroll <= 0) {
              if (controller.offset != 0) controller.jumpTo(0);
              return true;
            }
            if (controller.offset + value.overscroll >= controller.position.maxScrollExtent) {
              if (controller.offset != controller.position.maxScrollExtent) controller.jumpTo(controller.position.maxScrollExtent);
              return true;
            }
            controller.jumpTo(controller.offset + value.overscroll);
            return true;
          },
          child: child,
        );
      }
    }
    

    To go further, take a look of other implementation of NotificationListener which can be useful for pagination :). You can try also this :

    NotificationListener<ScrollStartNotification>(
      onNotification: (ScrollStartNotification value) {
        final ScrollMetrics metrics = value.metrics;
        if (!metrics.atEdge || metrics.pixels != 0) return true;
        print("Your callback here");
        return true;
      },
      child: child,
    )
    
    

    Or this :

    NotificationListener<ScrollEndNotification>(
      onNotification: (ScrollEndNotification value) {
        final ScrollMetrics metrics = value.metrics;
        if (!metrics.atEdge || metrics.pixels == 0) return true;
        print("Your callback here");
        return true;
      },
      child: child,
    )
    
    

    if you face issue in ios check this solution. https://github.com/gsioteam/kinoko/issues/12

    in List view builder put

    physics: Platform.isIOS ? const ClampingScrollPhysics(): const AlwaysScrollableScrollPhysics(),