0

In the project I'm currently working on, I have a Scaffold that contains a SinlgeChildScrollView. Within this SingleChildScrollView the actual content is being displayed, allowing for the possibility of scrolling if the content leaves the screen.

While this makes sense for ~90% of my screens, however I have one screen in which I display 2 ExpansionTiles. Both of these could possibly contain many entries, making them very big when expanded.

The problem right now is, that I'd like the ExpansionTile to stop expanding at latest when it reaches the bottom of the screen and make the content within the ExpansionTile (i.e. the ListTiles) scrollable.

Currently the screen looks like this when there are too many entries:

enter image description here

As you can clearly see, the ExpansionTile leaves the screen, forcing the user to scroll the actual screen, which would lead to the headers of both ExpansionTiles disappearing out of the screen given there are enought entries in the list. Even removing the SingleChildScrollView from the Scaffold doesn't solve the problem but just leads to a RenderOverflow.

enter image description here

The code used for generating the Scaffold and its contents is the following:

class MembershipScreen extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _MembershipScreenState();

}

class _MembershipScreenState extends State<MembershipScreen> {

  String _fontFamily = 'OpenSans';

  Widget _buildMyClubs() {
    return Container(
      decoration: BoxDecoration(
          color: Colors.white,
          border: Border.all(
              color: Color(0xFFD2D2D2),
              width: 2
          ),
          borderRadius: BorderRadius.circular(25)
      ),
      child: Theme(
        data: ThemeData().copyWith(dividerColor: Colors.transparent),
        child: ExpansionTile(
            title: Text("My Clubs"),
            trailing: Icon(Icons.add),
            children: getSearchResults(),
        ),
      )
    );
  }

  Widget _buildAllClubs() {
    return Container(
      decoration: BoxDecoration(
          color: Colors.white,
          border: Border.all(
              color: Color(0xFFD2D2D2),
              width: 2
          ),
          borderRadius: BorderRadius.circular(25)
      ),
      child: Theme(
        data: ThemeData().copyWith(dividerColor: Colors.transparent),
        child: SingleChildScrollView(
          child: ExpansionTile(
            title: Text("All Clubs"),
            trailing: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                Icon(Icons.add)
              ],
            ),
            children: getSearchResults(),
          ),
        )
      )
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBody: true,
      body: AnnotatedRegion<SystemUiOverlayStyle>(
          value: SystemUiOverlayStyle.light,
          child: GestureDetector(
            onTap: () => FocusScope.of(context).unfocus(),
            child: Stack(
              children: <Widget>[
                Container(
                  height: double.infinity,
                  width: double.infinity,
                  decoration: BoxDecoration(
                      gradient: kGradient //just some gradient
                  ),
                ),
                Center(
                  child: Container(
                  height: double.infinity,
                  constraints: BoxConstraints(maxWidth: 500),
                  child: SingleChildScrollView(
                      physics: AlwaysScrollableScrollPhysics(),
                      padding: EdgeInsets.symmetric(horizontal: 40.0, vertical: 20.0),
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          Text(
                            'Clubs',
                            style: TextStyle(
                                fontSize: 30.0,
                                color: Colors.white,
                                fontFamily: _fontFamily,
                                fontWeight: FontWeight.bold),
                          ),
                          SizedBox(
                            height: 20,
                          ),
                          _buildMyClubs(),
                          SizedBox(height: 20,),
                          _buildAllClubs()
                        ],
                      ),
                    ),
                  ),
                ),
              ],
            ),
          )
      ),
    );
  }

  List<Widget> getSearchResults() {
    return [
      ListTile(
        title: Text("Test1"),
        onTap: () => print("Test1"),
      ),
      ListTile(
        title: Text("Test2"),
        onTap: () => print("Test2"),
      ), //etc..
    ];
  }

}

I hope I didn't break the code by removing irrelevant parts of it in order to reduce size before posting it here. Hopefully, there is someone who knows how to achieve what I intend to do here and who can help me with the solution for this.

EDIT

As it might not be easy to understand what I try to achieve, I tried to come up with a visualization for the desired behaviour:

enter image description here

Thereby, the items that are surrounded with dashed lines are contained with the list, however cannot be displayed because they would exceed the viewport's boundaries. Hence the ExpansionTile that is containing the item needs to provide a scroll bar for the user to scroll down WITHIN the list. Thereby, both ExpansionTiles are visible at all times.

Samaranth
  • 385
  • 3
  • 16

1 Answers1

1

Try below code hope its help to you. Add your ExpansionTile() Widget inside Column() and Column() wrap in SingleChildScrollView()

  1. Refer SingleChildScrollView here
  2. Refer Column here
  3. You can refer my answer here also for ExpansionPanel
  4. Refer Lists here
  5. Refer ListView.builder() here

your List:

  List<Widget> getSearchResults = [
    ListTile(
      title: Text("Test1"),
      onTap: () => print("Test1"),
    ),
    ListTile(
      title: Text("Test2"),
      onTap: () => print("Test2"),
    ), //etc..
  ];

Your Widget using ListView.builder():

SingleChildScrollView(
    padding: EdgeInsets.all(20),
    child: Column(
      children: [
        Card(
          child: ExpansionTile(
            title: Text(
              "My Clubs",
            ),
            trailing: Icon(
              Icons.add,
            ),
            children: [
              ListView.builder(
                shrinkWrap: true,
                itemBuilder: (BuildContext context, int index) {
                  return Column(
                    children: getSearchResults,
                  );
                },
                itemCount: getSearchResults.length, // try 50 length just testing
              ),
            ],
          ),
        ),
      ],
    ),
  ),

Your Simple Widget :

 SingleChildScrollView(
    padding: EdgeInsets.all(20),
    child: Column(
      children: [
        Card(
          child: ExpansionTile(
            title: Text(
              "My Clubs",
            ),
            trailing: Icon(
              Icons.add,
            ),
            children:getSearchResults
             
          ),
        ),
      ],
    ),
  ),

Your result screen -> image

Ravindra S. Patil
  • 11,757
  • 3
  • 13
  • 40
  • Thanks for your suggestions, but it seems like it doesn't entirely solve the actual problem. If I implement it like this I get the list as before, this time wrapped within a `Card` but the list itself still is not scrollable for me and the list containing the results still extends beyond the screen border. I used your suggested solution using the `ListView.builder` by the way. Also in any case, is there a possible solution that's making use of a `Container` instead of a `Card` Widget to preserve the LnF of the application? – Samaranth Dec 05 '21 at 13:13
  • @Samaranth yes you can used `Container` instead of `Card` and you used `ListView.Builder` and list is not scrollable just add `physics: NeverScrollableScrollPhysics(),` inside `Listview.builder` refer `NeverScrollableScrollPhysics` [here](https://api.flutter.dev/flutter/widgets/NeverScrollableScrollPhysics-class.html) hope its helpful to you. – Ravindra S. Patil Dec 05 '21 at 16:52
  • I actually tried how using `NeverScrollableScrollPhysics()` and `AlwaysScrollableScrollPhysics()` changed the interaction with the widget but in any case it didn't change the fact that the Widget itself is not scrollable but only the Scaffold surrounding it. But it's good to know that your suggestion of a `Card` is easily replaceable with a `Container`. – Samaranth Dec 06 '21 at 11:10
  • @Samaranth your problem is solved or not? – Ravindra S. Patil Dec 06 '21 at 11:12
  • No, not really. The main problem is that the `ExpansionTile` itself is not scrollable and expands beyond the screen border when it contains enough entries. The only possibility currently is to croll the main view down, which is not how it should be. I search for a solution that the `ExpansionTile` stops slightly above the `BottomNavBar` and that I can scroll all elements that are contained within the `ExpansionTile` as a scrollable list. I.e. the upper and the lower ExpansionTiles are always both visible as well as their headers "My Clubs" and "All Clubs". – Samaranth Dec 06 '21 at 11:18
  • I edited the original post by providing a visualization of the behaviour I want to achieve. Perhaps now my intention will be easier to understand. – Samaranth Dec 06 '21 at 11:42