Flutter listview at the end of its content scrolls screen

2.2k views Asked by At

I'm wondering if it is possible to have a vertical ListView with fixed height in a SingleChildScrollView and when there are no more contents in the vertical ListView the scrolling is applied to the whole screen (SingleChildScrollView)

something like this:

SingleChildScrollView(
      child: Column(
          children: [
            // some widgets ....
            
            Container(
              constraints: BoxConstraints(maxHeight: 200),
              child: ListView.builder(
                // other settings
                scrollDirection: Axis.vertical,
              ),
            )
          ]
      )
  )
4

There are 4 answers

1
cyberail On BEST ANSWER

I am using Listview insted of SingleChildScrollView but it is the same:

import 'dart:async';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(title: 'Flutter Demo', home: MyListView());
  }
}

class MyListView extends StatelessWidget {
  ScrollController _mainScrollController = ScrollController();
  double listHeight = 370;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AppBar'),
      ),
      body: Container(
        height: MediaQuery.of(context).size.height,
        child: ListView(
            controller: _mainScrollController,
            children: <Widget>[

              Container(height: listHeight,child: RapportList(parentScrollController: _mainScrollController)),
              OtherElement(text: "Other element 1 which will be scrolled",),
              OtherElement(text: "Other element 2 which will be scrolled",),
              OtherElement(text: "Other element 3 which will be scrolled",),
              OtherElement(text: "Other element 4 which will be scrolled",),
              OtherElement(text: "Other element 5 which will be scrolled",),
            ],
        ),
      ),
    );
  }
}

class RapportList extends StatefulWidget {
  final ScrollController parentScrollController;
  RapportList({@required this.parentScrollController});
  @override
  _RapportListState createState() => _RapportListState();
}

class _RapportListState extends State<RapportList> {
  ScrollPhysics physics = ScrollPhysics();
  // NeverScrollableScrollPhysics()
  ScrollController _listViewScrollController;


  void listViewScrollListener(){
     print("smth");
    if(_listViewScrollController.offset >= _listViewScrollController.position.maxScrollExtent &&
        !_listViewScrollController.position.outOfRange){
      if(widget.parentScrollController.offset==0){
        widget.parentScrollController.animateTo(50,duration: Duration(milliseconds: 200),curve: Curves.linear);
      }
      setState((){
        physics = NeverScrollableScrollPhysics();
      });
      print("bottom");
    }
  }

  void mainScrollListener(){
    if(widget.parentScrollController.offset <= widget.parentScrollController.position.minScrollExtent &&
        !widget.parentScrollController.position.outOfRange){
      setState((){
        if(physics is NeverScrollableScrollPhysics){
          physics = ScrollPhysics();
          _listViewScrollController.animateTo(_listViewScrollController.position.maxScrollExtent-50,duration: Duration(milliseconds: 200),curve: Curves.linear);
        }
      });
      print("top");
    }
  }

  @override
  void setState(fn) {
    super.setState(fn);
  }
  @override
  void initState() {
    _listViewScrollController = ScrollController();
    _listViewScrollController.addListener(listViewScrollListener);

    // TODO: implement initState
    super.initState();
  }
  @override
  Widget build(BuildContext context) {

    widget.parentScrollController.addListener(mainScrollListener);
    return ListView.builder(
      controller: _listViewScrollController,
      physics: physics,
      shrinkWrap: true,
      itemCount: 50,
      itemBuilder: (context, index) {
        return ListTile(
          title: GestureDetector(
            child: Row(
              children: <Widget>[
                Container(child: Text("text $index")),
              ],
            ),
          ),
        );
      },
    );
  }
}


class OtherElement extends StatelessWidget {
  final String text;
  OtherElement({this.text});
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100,
      child: Center(child: Padding(
        padding: const EdgeInsets.symmetric(horizontal:40.0),
        child: Text(this.text,style:TextStyle(fontSize: 30)),
      )),
    );
  }
}

github gist.

demo gif:

enter image description here

0
Fabio Dias On

I guess you could try and use the Nested ScrollView Widget.

This widget enables you to accomplish what you need.

0
Bensal On

I think your updated code has a bug. Because if you have a Scrollable ListView inside a SingleChildScrollView, you cannot scroll it. Make it to you have to add this to your listView:

            physics: const NeverScrollableScrollPhysics(),

Now it should be working. You can also try adding column instead of a listView in most of the circumstances.

0
black_pearl On

update @man of knowledge's code,

( ListView in ListView ),

to Dart 2.15.1

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(title: 'Flutter Demo', home: MyListView());
  }
}

class MyListView extends StatelessWidget {
  ScrollController _mainScrollController = ScrollController();

  double listHeight = 370;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AppBar'),
      ),
      body: Container(
        height: MediaQuery.of(context).size.height,
        child: ListView(
          controller: _mainScrollController,
          children: <Widget>[
            Container(
                height: listHeight, child: RapportList(_mainScrollController)),
            OtherElement(
              "Other element 1 which will be scrolled",
            ),
            OtherElement(
              "Other element 2 which will be scrolled",
            ),
            OtherElement(
              "Other element 3 which will be scrolled",
            ),
            OtherElement(
              "Other element 4 which will be scrolled",
            ),
            OtherElement(
              "Other element 5 which will be scrolled",
            ),
          ],
        ),
      ),
    );
  }
}

class RapportList extends StatefulWidget {
  final ScrollController parentScrollController;
  RapportList(this.parentScrollController);
  @override
  _RapportListState createState() => _RapportListState();
}

class _RapportListState extends State<RapportList> {
  late ScrollController _listViewScrollController = ScrollController()
    ..addListener(listViewScrollListener);
  ScrollPhysics _physics = ScrollPhysics();
  // NeverScrollableScrollPhysics()

  void listViewScrollListener() {
    print("smth");
    if (_listViewScrollController.offset >=
            _listViewScrollController.position.maxScrollExtent &&
        !_listViewScrollController.position.outOfRange) {
      if (widget.parentScrollController.offset == 0) {
        widget.parentScrollController.animateTo(50,
            duration: Duration(milliseconds: 200), curve: Curves.linear);
      }
      setState(() {
        _physics = NeverScrollableScrollPhysics();
      });
      print("bottom");
    }
  }

  void mainScrollListener() {
    if (widget.parentScrollController.offset <=
            widget.parentScrollController.position.minScrollExtent &&
        !widget.parentScrollController.position.outOfRange) {
      setState(() {
        if (_physics is NeverScrollableScrollPhysics) {
          _physics = ScrollPhysics();

          _listViewScrollController.animateTo(
              _listViewScrollController.position.maxScrollExtent - 50,
              duration: Duration(milliseconds: 200),
              curve: Curves.linear);
        }
      });
      print("top");
    }
  }

  @override
  Widget build(BuildContext context) {
    widget.parentScrollController.addListener(mainScrollListener);
    return ListView.builder(
      controller: _listViewScrollController,
      physics: _physics,
      shrinkWrap: true,
      itemCount: 50,
      itemBuilder: (context, index) {
        return ListTile(
          title: GestureDetector(
            child: Row(
              children: <Widget>[
                Container(child: Text("text $index")),
              ],
            ),
          ),
        );
      },
    );
  }
}

class OtherElement extends StatelessWidget {
  final String text;
  OtherElement(this.text);
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100,
      child: Center(
          child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 40.0),
        child: Text(this.text, style: TextStyle(fontSize: 30)),
      )),
    );
  }
}