Expand widget to bottom within CustomScrollView with center key set

266 views Asked by At

In my app I want to display a comment section below an expandable widget. The comment section should be expandable to the top and to the bottom, without changing the position of the scrollview.

The center key is not needed to center the page initially, just to keep the view in place when expanding the lists to the bottom and the top.

So far, this is the code I have come up with (simplified example) using these resources:

Flutter, ListView, How to add several items on top of the ListView and make it not scroll to the top

respectively

https://github.com/flutter/flutter/issues/21541#issuecomment-629121578

import 'package:flutter/material.dart';

class TestPage extends StatefulWidget {
  const TestPage({super.key});

  @override
  State<TestPage> createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  List<Widget> newList = List.generate(
    20,
    (index) => Text('Upper ${index.toString()}'),
  );
  List<Widget> myList = List.generate(
    20,
    (index) => Text('Lower ${index.toString()}'),
  );

  bool showMore = false;

  final Key centerKey = const ValueKey('second-sliver-list');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton.extended(
            onPressed: () {
              setState(() {
                newList.add(Text('Upper ${newList.length}'));
              });
            },
            label: const Text('Add to Upper'),
          ),
          const SizedBox(height: 10),
          FloatingActionButton.extended(
            onPressed: () {
              setState(() {
                myList.add(Text('Lower ${newList.length}'));
              });
            },
            label: const Text('Add to Lower'),
          ),
        ],
      ),
      appBar: AppBar(),
      body: CustomScrollView(
        center: centerKey,
        slivers: [
          SliverToBoxAdapter(
            child: Column(
              children: [
                SizedBox(
                  height: showMore ? null : 200,
                  child: const Text(
                    'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis ac risus quis nisl blandit tristique at nec velit. In placerat ultrices ante a finibus. Nullam feugiat sapien eget neque vulputate vulputate. Quisque et lobortis odio, non condimentum sapien. Nam tristique nisi faucibus metus semper dictum. Nunc tristique, lorem vulputate interdum tempus, eros magna fringilla diam, vitae euismod augue ipsum laoreet tellus. Maecenas faucibus ante sagittis arcu imperdiet ornare. Vestibulum a malesuada dui. Integer interdum, leo ut tincidunt accumsan, justo dolor venenatis erat, quis pellentesque felis odio at orci. Sed eget neque mi. Etiam cursus nisl eget dolor porttitor, non aliquet sapien sollicitudin. Suspendisse sodales tellus purus, quis ultricies eros congue in. Nam eget odio at orci rutrum porta quis nec turpis. Cras non mattis metus. Duis eu auctor metus. Curabitur ac vestibulum urna. Nunc turpis augue, condimentum id fringilla at, tincidunt et libero. Curabitur facilisis, justo eu lobortis lobortis, magna diam fermentum quam, et fermentum ante leo eu tellus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed in gravida ex. Aenean non lacinia lacus, at viverra nulla. Nullam sit amet velit varius, efficitur quam vitae, ornare justo. Pellentesque imperdiet nisi quis fringilla varius. Vivamus ut neque vitae arcu commodo faucibus. Donec tincidunt quam vitae eleifend tempor. Phasellus convallis, sapien vel dapibus suscipit, libero risus hendrerit lacus, ut tincidunt nunc justo at quam. Duis sagittis viverra augue vitae eleifend. Suspendisse pellentesque metus lectus, quis iaculis felis sollicitudin quis. Ut tincidunt congue euismod. Mauris fermentum finibus rutrum. Morbi eget mauris in nulla volutpat volutpat in dictum odio. Nulla pretium nisl augue, eget efficitur odio ultrices id. Donec varius sed metus a efficitur. Etiam lacinia magna non felis auctor, eu lobortis sem sagittis. Nunc molestie consectetur consequat. Cras nec sem a turpis pulvinar iaculis eget at nisl. Praesent tincidunt interdum metus ac feugiat. Vestibulum dui neque, rutrum eu aliquam ultricies, dignissim ac purus. Phasellus eu posuere ligula, in tempus arcu. Etiam eu lorem non ipsum tempor tempor a quis ligula. Quisque porta finibus neque. Cras placerat a lacus sed laoreet. Quisque eget rhoncus lacus, sed bibendum ex. Praesent egestas lacus vitae felis tempor, eget placerat enim lobortis. Mauris tempus bibendum mauris. Fusce tempor, enim sit amet blandit convallis, lectus dui maximus nibh, id sollicitudin lectus urna non metus. Donec sed posuere augue. Morbi sodales pulvinar dolor a aliquam. Etiam gravida magna dui, ac porta tellus tincidunt vitae. Nulla facilisi. Donec eget ex vel tortor fermentum congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer eu ligula et felis luctus rutrum id quis tellus. Pellentesque ut diam non orci eleifend consectetur. Duis lacinia mollis lorem at maximus. Nulla facilisi. Nam sollicitudin ut sapien non molestie. Morbi sit amet sagittis mauris. Proin vel tincidunt elit. Morbi ut est et purus vehicula posuere. Nulla volutpat tincidunt turpis at iaculis. Sed bibendum magna lectus, in semper sapien congue auctor. Sed auctor mi eu dui ornare placerat. Mauris ullamcorper egestas leo, vel dignissim elit iaculis et. Pellentesque ultrices eget felis vel tristique. Cras aliquam lacinia tortor quis ullamcorper. Fusce dapibus tincidunt dui quis lobortis. Ut eget nunc elit. Proin elementum sagittis congue. Suspendisse euismod risus diam, eu dictum erat egestas sed. Aenean sodales nec leo ut pellentesque. Donec rhoncus ante id turpis pretium vulputate. Sed et blandit nisl, ut condimentum purus. Nunc placerat, diam vitae elementum posuere, nibh quam aliquam nibh, vel commodo enim tellus vel felis. Pellentesque ut lorem sit amet neque imperdiet fringilla. Quisque malesuada sem ut odio eleifend hendrerit. Quisque convallis semper justo a tincidunt.',
                  ),
                ),
                TextButton(
                  onPressed: () {
                    setState(() {
                      showMore = !showMore;
                    });
                  },
                  child: showMore
                      ? const Text('Show less')
                      : const Text('Show more'),
                ),
              ],
            ),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return ListTile(
                  title: newList[index],
                );
              },
              childCount: newList.length,
            ),
          ),
          SliverList(
            key: centerKey,
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return ListTile(title: myList[index]);
              },
              childCount: myList.length,
            ),
          ),
        ],
      ),
    );
  }
}

The comment section works like I want it to work, but when expanding the upper text widget, the widget is expanded to the top, rather than to the bottom.

How could I adjust my code to change this behaviour?

5

There are 5 answers

2
NIMA Shahahmadian On

well I don't know if it's exactly what you want or not but this is a way of doing so and I will look up for more and better solutions:

import 'package:flutter/material.dart';

class TestPage extends StatefulWidget {
  const TestPage({super.key});

  @override
  State<TestPage> createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  List<Widget> newList = List.generate(
    20,
    (index) => Text('Upper ${index.toString()}'),
  );
  List<Widget> myList = List.generate(
    20,
    (index) => Text('Lower ${index.toString()}'),
  );

  bool showMore = false;

  final Key centerKey = const ValueKey('second-sliver-list');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton.extended(
            onPressed: () {
              setState(() {
                newList.add(Text('Upper ${newList.length}'));
              });
            },
            label: const Text('Add to Upper'),
          ),
          const SizedBox(height: 10),
          FloatingActionButton.extended(
            onPressed: () {
              setState(() {
                myList.add(Text('Lower ${newList.length}'));
              });
            },
            label: const Text('Add to Lower'),
          ),
        ],
      ),
      appBar: AppBar(),
      body: SingleChildScrollView(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            SizedBox(
              height: showMore ? null : 200,
              child: const Text(
                'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis ac risus quis nisl blandit tristique at nec velit. In placerat ultrices ante a finibus. Nullam feugiat sapien eget neque vulputate vulputate. Quisque et lobortis odio, non condimentum sapien. Nam tristique nisi faucibus metus semper dictum. Nunc tristique, lorem vulputate interdum tempus, eros magna fringilla diam, vitae euismod augue ipsum laoreet tellus. Maecenas faucibus ante sagittis arcu imperdiet ornare. Vestibulum a malesuada dui. Integer interdum, leo ut tincidunt accumsan, justo dolor venenatis erat, quis pellentesque felis odio at orci. Sed eget neque mi. Etiam cursus nisl eget dolor porttitor, non aliquet sapien sollicitudin. Suspendisse sodales tellus purus, quis ultricies eros congue in. Nam eget odio at orci rutrum porta quis nec turpis. Cras non mattis metus. Duis eu auctor metus. Curabitur ac vestibulum urna. Nunc turpis augue, condimentum id fringilla at, tincidunt et libero. Curabitur facilisis, justo eu lobortis lobortis, magna diam fermentum quam, et fermentum ante leo eu tellus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed in gravida ex. Aenean non lacinia lacus, at viverra nulla. Nullam sit amet velit varius, efficitur quam vitae, ornare justo. Pellentesque imperdiet nisi quis fringilla varius. Vivamus ut neque vitae arcu commodo faucibus. Donec tincidunt quam vitae eleifend tempor. Phasellus convallis, sapien vel dapibus suscipit, libero risus hendrerit lacus, ut tincidunt nunc justo at quam. Duis sagittis viverra augue vitae eleifend. Suspendisse pellentesque metus lectus, quis iaculis felis sollicitudin quis. Ut tincidunt congue euismod. Mauris fermentum finibus rutrum. Morbi eget mauris in nulla volutpat volutpat in dictum odio. Nulla pretium nisl augue, eget efficitur odio ultrices id. Donec varius sed metus a efficitur. Etiam lacinia magna non felis auctor, eu lobortis sem sagittis. Nunc molestie consectetur consequat. Cras nec sem a turpis pulvinar iaculis eget at nisl. Praesent tincidunt interdum metus ac feugiat. Vestibulum dui neque, rutrum eu aliquam ultricies, dignissim ac purus. Phasellus eu posuere ligula, in tempus arcu. Etiam eu lorem non ipsum tempor tempor a quis ligula. Quisque porta finibus neque. Cras placerat a lacus sed laoreet. Quisque eget rhoncus lacus, sed bibendum ex. Praesent egestas lacus vitae felis tempor, eget placerat enim lobortis. Mauris tempus bibendum mauris. Fusce tempor, enim sit amet blandit convallis, lectus dui maximus nibh, id sollicitudin lectus urna non metus. Donec sed posuere augue. Morbi sodales pulvinar dolor a aliquam. Etiam gravida magna dui, ac porta tellus tincidunt vitae. Nulla facilisi. Donec eget ex vel tortor fermentum congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer eu ligula et felis luctus rutrum id quis tellus. Pellentesque ut diam non orci eleifend consectetur. Duis lacinia mollis lorem at maximus. Nulla facilisi. Nam sollicitudin ut sapien non molestie. Morbi sit amet sagittis mauris. Proin vel tincidunt elit. Morbi ut est et purus vehicula posuere. Nulla volutpat tincidunt turpis at iaculis. Sed bibendum magna lectus, in semper sapien congue auctor. Sed auctor mi eu dui ornare placerat. Mauris ullamcorper egestas leo, vel dignissim elit iaculis et. Pellentesque ultrices eget felis vel tristique. Cras aliquam lacinia tortor quis ullamcorper. Fusce dapibus tincidunt dui quis lobortis. Ut eget nunc elit. Proin elementum sagittis congue. Suspendisse euismod risus diam, eu dictum erat egestas sed. Aenean sodales nec leo ut pellentesque. Donec rhoncus ante id turpis pretium vulputate. Sed et blandit nisl, ut condimentum purus. Nunc placerat, diam vitae elementum posuere, nibh quam aliquam nibh, vel commodo enim tellus vel felis. Pellentesque ut lorem sit amet neque imperdiet fringilla. Quisque malesuada sem ut odio eleifend hendrerit. Quisque convallis semper justo a tincidunt.',
              ),
            ),
            TextButton(
              onPressed: () {
                setState(() {
                  showMore = !showMore;
                });
              },
              child: showMore ? const Text('Show less') : const Text('Show more'),
            ),
            SizedBox(
              height: 500,
              child: CustomScrollView(
                center: centerKey,
                slivers: [
                  SliverToBoxAdapter(
                    child: Column(
                      children: [],
                    ),
                  ),
                  SliverList(
                    delegate: SliverChildBuilderDelegate(
                      (context, index) {
                        return ListTile(
                          title: newList[index],
                        );
                      },
                      childCount: newList.length,
                    ),
                  ),
                  SliverList(
                    key: centerKey,
                    delegate: SliverChildBuilderDelegate(
                      (context, index) {
                        return ListTile(title: myList[index]);
                      },
                      childCount: myList.length,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

what you have to change is to take your text out of CustomScrollView you can try doing it in a suitable manner.

3
Ashish On

I am not totally sure about your requirement but here I have made 1 tweak to your code. Is it what you are looking for?

import 'package:flutter/material.dart';

class TestPage extends StatefulWidget {
  const TestPage({super.key});

  @override
  State<TestPage> createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  List<Widget> newList = List.generate(
    20,
    (index) => Text('Upper ${index.toString()}'),
  );
  List<Widget> myList = List.generate(
    20,
    (index) => Text('Lower ${index.toString()}'),
  );

  bool showMore = false;
  ScrollController _scrollController = ScrollController();
  final Key centerKey = const ValueKey('second-sliver-list');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton.extended(
            onPressed: () {
              setState(() {
                newList.add(Text('Upper ${newList.length}'));
              });
            },
            label: const Text('Add to Upper'),
          ),
          const SizedBox(height: 10),
          FloatingActionButton.extended(
            onPressed: () {
              setState(() {
                myList.add(Text('Lower ${newList.length}'));
              });
            },
            label: const Text('Add to Lower'),
          ),
        ],
      ),
      appBar: AppBar(),
      body: CustomScrollView(
        controller: _scrollController,
        slivers: [
          SliverToBoxAdapter(
            child: Column(
              children: [
                SizedBox(
                  height: showMore ? null : 200,
                  child: const Text(
                    'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis ac risus quis nisl blandit tristique at nec velit. In placerat ultrices ante a finibus. Nullam feugiat sapien eget neque vulputate vulputate. Quisque et lobortis odio, non condimentum sapien. Nam tristique nisi faucibus metus semper dictum. Nunc tristique, lorem vulputate interdum tempus, eros magna fringilla diam, vitae euismod augue ipsum laoreet tellus. Maecenas faucibus ante sagittis arcu imperdiet ornare. Vestibulum a malesuada dui. Integer interdum, leo ut tincidunt accumsan, justo dolor venenatis erat, quis pellentesque felis odio at orci. Sed eget neque mi. Etiam cursus nisl eget dolor porttitor, non aliquet sapien sollicitudin. Suspendisse sodales tellus purus, quis ultricies eros congue in. Nam eget odio at orci rutrum porta quis nec turpis. Cras non mattis metus. Duis eu auctor metus. Curabitur ac vestibulum urna. Nunc turpis augue, condimentum id fringilla at, tincidunt et libero. Curabitur facilisis, justo eu lobortis lobortis, magna diam fermentum quam, et fermentum ante leo eu tellus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed in gravida ex. Aenean non lacinia lacus, at viverra nulla. Nullam sit amet velit varius, efficitur quam vitae, ornare justo. Pellentesque imperdiet nisi quis fringilla varius. Vivamus ut neque vitae arcu commodo faucibus. Donec tincidunt quam vitae eleifend tempor. Phasellus convallis, sapien vel dapibus suscipit, libero risus hendrerit lacus, ut tincidunt nunc justo at quam. Duis sagittis viverra augue vitae eleifend. Suspendisse pellentesque metus lectus, quis iaculis felis sollicitudin quis. Ut tincidunt congue euismod. Mauris fermentum finibus rutrum. Morbi eget mauris in nulla volutpat volutpat in dictum odio. Nulla pretium nisl augue, eget efficitur odio ultrices id. Donec varius sed metus a efficitur. Etiam lacinia magna non felis auctor, eu lobortis sem sagittis. Nunc molestie consectetur consequat. Cras nec sem a turpis pulvinar iaculis eget at nisl. Praesent tincidunt interdum metus ac feugiat. Vestibulum dui neque, rutrum eu aliquam ultricies, dignissim ac purus. Phasellus eu posuere ligula, in tempus arcu. Etiam eu lorem non ipsum tempor tempor a quis ligula. Quisque porta finibus neque. Cras placerat a lacus sed laoreet. Quisque eget rhoncus lacus, sed bibendum ex. Praesent egestas lacus vitae felis tempor, eget placerat enim lobortis. Mauris tempus bibendum mauris. Fusce tempor, enim sit amet blandit convallis, lectus dui maximus nibh, id sollicitudin lectus urna non metus. Donec sed posuere augue. Morbi sodales pulvinar dolor a aliquam. Etiam gravida magna dui, ac porta tellus tincidunt vitae. Nulla facilisi. Donec eget ex vel tortor fermentum congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer eu ligula et felis luctus rutrum id quis tellus. Pellentesque ut diam non orci eleifend consectetur. Duis lacinia mollis lorem at maximus. Nulla facilisi. Nam sollicitudin ut sapien non molestie. Morbi sit amet sagittis mauris. Proin vel tincidunt elit. Morbi ut est et purus vehicula posuere. Nulla volutpat tincidunt turpis at iaculis. Sed bibendum magna lectus, in semper sapien congue auctor. Sed auctor mi eu dui ornare placerat. Mauris ullamcorper egestas leo, vel dignissim elit iaculis et. Pellentesque ultrices eget felis vel tristique. Cras aliquam lacinia tortor quis ullamcorper. Fusce dapibus tincidunt dui quis lobortis. Ut eget nunc elit. Proin elementum sagittis congue. Suspendisse euismod risus diam, eu dictum erat egestas sed. Aenean sodales nec leo ut pellentesque. Donec rhoncus ante id turpis pretium vulputate. Sed et blandit nisl, ut condimentum purus. Nunc placerat, diam vitae elementum posuere, nibh quam aliquam nibh, vel commodo enim tellus vel felis. Pellentesque ut lorem sit amet neque imperdiet fringilla. Quisque malesuada sem ut odio eleifend hendrerit. Quisque convallis semper justo a tincidunt.',
                  ),
                ),
                TextButton(
                  onPressed: () {
                    setState(() {
                      showMore = !showMore;
                      _scrollController.animateTo(
                        0.0,
                        duration:const Duration(milliseconds: 600),
                        curve: Curves.easeInOut,
                      );
                    });
                  },
                  child: showMore
                      ? const Text('Show less')
                      : const Text('Show more'),
                ),
              ],
            ),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return ListTile(
                  title: newList[index],
                );
              },
              childCount: newList.length,
            ),
          ),
          SliverList(
            key: centerKey,
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return ListTile(title: myList[index]);
              },
              childCount: myList.length,
            ),
          ),
        ],
      ),
    );
  }
}
5
k.s poyraz On

I think you want the scroll position to be initially in the second SliverList. That's why you've used the 'center' attribute inside the CustomScrollView. However, you've experienced side effects of using the 'center' attribute for this scenario.

Docs says about CustomScrollView' center attribute; "Children after center will be placed in the AxisDirection determined by scrollDirection and reverse relative to the center. Children before the center will be placed in the opposite of the axis direction relative to the center. This makes the center the inflection point of the growth direction."

I believe you can achieve your requests as follows;

  1. Let's remove the 'center' attribute from CustomScrollView.
  2. Use a method that will provide us with the initial scroll position. Here it is: WidgetsBinding.instance.addPostFrameCallback((_) => Scrollable.ensureVisible(dataKey.currentContext!));
  3. Lastly, we should reverse your first sliverlist as your desired scenario.

Here, full codes that you need;

import 'package:flutter/material.dart';

class TestPage extends StatefulWidget {
  const TestPage({super.key});

  @override
  State<TestPage> createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  ScrollController scrollController = ScrollController();
  List<Widget> newList = List.generate(
    20,
    (index) => Text('Upper ${index.toString()}'),
  );
  List<Widget> myList = List.generate(
    20,
    (index) => Text('Lower ${index.toString()}'),
  );

  bool showMore = false;

  final Key centerKey = const ValueKey('second-sliver-list');

  final dataKey = new GlobalKey(); // we created GlobalKey rather than Key

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback(
      (_) => Scrollable.ensureVisible(dataKey.currentContext!),
    ); // this will scroll to desired position after build completes
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton.extended(
            onPressed: () {
              setState(() {
                newList.add(Text('Upper ${newList.length}'));
              });
            },
            label: const Text('Add to Upper'),
          ),
          const SizedBox(height: 10),
          FloatingActionButton.extended(
            onPressed: () {
              setState(() {
                myList.add(Text('Lower ${newList.length}'));
              });
            },
            label: const Text('Add to Lower'),
          ),
        ],
      ),
      appBar: AppBar(),
      body: CustomScrollView(
        // removed center
        slivers: [
          SliverToBoxAdapter(
            child: Column(
              children: [
                SizedBox(
                  height: showMore ? null : 200,
                  child: const Text(
                    'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis ac risus quis nisl blandit tristique at nec velit. In placerat ultrices ante a finibus. Nullam feugiat sapien eget neque vulputate vulputate. Quisque et lobortis odio, non condimentum sapien. Nam tristique nisi faucibus metus semper dictum. Nunc tristique, lorem vulputate interdum tempus, eros magna fringilla diam, vitae euismod augue ipsum laoreet tellus. Maecenas faucibus ante sagittis arcu imperdiet ornare. Vestibulum a malesuada dui. Integer interdum, leo ut tincidunt accumsan, justo dolor venenatis erat, quis pellentesque felis odio at orci. Sed eget neque mi. Etiam cursus nisl eget dolor porttitor, non aliquet sapien sollicitudin. Suspendisse sodales tellus purus, quis ultricies eros congue in. Nam eget odio at orci rutrum porta quis nec turpis. Cras non mattis metus. Duis eu auctor metus. Curabitur ac vestibulum urna. Nunc turpis augue, condimentum id fringilla at, tincidunt et libero. Curabitur facilisis, justo eu lobortis lobortis, magna diam fermentum quam, et fermentum ante leo eu tellus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed in gravida ex. Aenean non lacinia lacus, at viverra nulla. Nullam sit amet velit varius, efficitur quam vitae, ornare justo. Pellentesque imperdiet nisi quis fringilla varius. Vivamus ut neque vitae arcu commodo faucibus. Donec tincidunt quam vitae eleifend tempor. Phasellus convallis, sapien vel dapibus suscipit, libero risus hendrerit lacus, ut tincidunt nunc justo at quam. Duis sagittis viverra augue vitae eleifend. Suspendisse pellentesque metus lectus, quis iaculis felis sollicitudin quis. Ut tincidunt congue euismod. Mauris fermentum finibus rutrum. Morbi eget mauris in nulla volutpat volutpat in dictum odio. Nulla pretium nisl augue, eget efficitur odio ultrices id. Donec varius sed metus a efficitur. Etiam lacinia magna non felis auctor, eu lobortis sem sagittis. Nunc molestie consectetur consequat. Cras nec sem a turpis pulvinar iaculis eget at nisl. Praesent tincidunt interdum metus ac feugiat. Vestibulum dui neque, rutrum eu aliquam ultricies, dignissim ac purus. Phasellus eu posuere ligula, in tempus arcu. Etiam eu lorem non ipsum tempor tempor a quis ligula. Quisque porta finibus neque. Cras placerat a lacus sed laoreet. Quisque eget rhoncus lacus, sed bibendum ex. Praesent egestas lacus vitae felis tempor, eget placerat enim lobortis. Mauris tempus bibendum mauris. Fusce tempor, enim sit amet blandit convallis, lectus dui maximus nibh, id sollicitudin lectus urna non metus. Donec sed posuere augue. Morbi sodales pulvinar dolor a aliquam. Etiam gravida magna dui, ac porta tellus tincidunt vitae. Nulla facilisi. Donec eget ex vel tortor fermentum congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer eu ligula et felis luctus rutrum id quis tellus. Pellentesque ut diam non orci eleifend consectetur. Duis lacinia mollis lorem at maximus. Nulla facilisi. Nam sollicitudin ut sapien non molestie. Morbi sit amet sagittis mauris. Proin vel tincidunt elit. Morbi ut est et purus vehicula posuere. Nulla volutpat tincidunt turpis at iaculis. Sed bibendum magna lectus, in semper sapien congue auctor. Sed auctor mi eu dui ornare placerat. Mauris ullamcorper egestas leo, vel dignissim elit iaculis et. Pellentesque ultrices eget felis vel tristique. Cras aliquam lacinia tortor quis ullamcorper. Fusce dapibus tincidunt dui quis lobortis. Ut eget nunc elit. Proin elementum sagittis congue. Suspendisse euismod risus diam, eu dictum erat egestas sed. Aenean sodales nec leo ut pellentesque. Donec rhoncus ante id turpis pretium vulputate. Sed et blandit nisl, ut condimentum purus. Nunc placerat, diam vitae elementum posuere, nibh quam aliquam nibh, vel commodo enim tellus vel felis. Pellentesque ut lorem sit amet neque imperdiet fringilla. Quisque malesuada sem ut odio eleifend hendrerit. Quisque convallis semper justo a tincidunt.',
                  ),
                ),
                TextButton(
                  onPressed: () {
                    setState(() {
                      showMore = !showMore;
                    });
                  },
                  child: showMore
                      ? const Text('Show less')
                      : const Text('Show more'),
                ),
              ],
            ),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                // reversed list can be achieved alike
                final reversedIndex = newList.length - index - 1;
                return ListTile(
                  title: newList[reversedIndex],
                );
              },
              childCount: newList.length,
            ),
          ),
          SliverList(
            key: dataKey, // Global key
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return ListTile(title: myList[index]);
              },
              childCount: myList.length,
            ),
          ),
        ],
      ),
    );
  }
}

After all, your expandable algorithm will expand to downward. Good luck

0
NIMA Shahahmadian On
import 'package:flutter/material.dart';

class TestPage extends StatefulWidget {
  const TestPage({super.key});

  @override
  State<TestPage> createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  List<Widget> newList = List.generate(
    20,
    (index) => Text('Upper ${index.toString()}'),
  );
  List<Widget> myList = List.generate(
    20,
    (index) => Text('Lower ${index.toString()}'),
  );

  bool showMore = false;

  final Key centerKey = const ValueKey('second-sliver-list');
  double widgetSize = 50;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton.extended(
            onPressed: () {
              setState(() {
                newList.add(Text('Upper ${newList.length}'));
              });
            },
            label: const Text('Add to Upper'),
          ),
          const SizedBox(height: 10),
          FloatingActionButton.extended(
            onPressed: () {
              setState(() {
                myList.add(Text('Lower ${newList.length}'));
              });
            },
            label: const Text('Add to Lower'),
          ),
        ],
      ),
      appBar: AppBar(),
      body: SingleChildScrollView(
        child: Column(
          children: [
            SizedBox(
              height: showMore ? null : 200,
              child: const Text(
                'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis ac risus quis nisl blandit tristique at nec velit. In placerat ultrices ante a finibus. Nullam feugiat sapien eget neque vulputate vulputate. Quisque et lobortis odio, non condimentum sapien. Nam tristique nisi faucibus metus semper dictum. Nunc tristique, lorem vulputate interdum tempus, eros magna fringilla diam, vitae euismod augue ipsum laoreet tellus. Maecenas faucibus ante sagittis arcu imperdiet ornare. Vestibulum a malesuada dui. Integer interdum, leo ut tincidunt accumsan, justo dolor venenatis erat, quis pellentesque felis odio at orci. Sed eget neque mi. Etiam cursus nisl eget dolor porttitor, non aliquet sapien sollicitudin. Suspendisse sodales tellus purus, quis ultricies eros congue in. Nam eget odio at orci rutrum porta quis nec turpis. Cras non mattis metus. Duis eu auctor metus. Curabitur ac vestibulum urna. Nunc turpis augue, condimentum id fringilla at, tincidunt et libero. Curabitur facilisis, justo eu lobortis lobortis, magna diam fermentum quam, et fermentum ante leo eu tellus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed in gravida ex. Aenean non lacinia lacus, at viverra nulla. Nullam sit amet velit varius, efficitur quam vitae, ornare justo. Pellentesque imperdiet nisi quis fringilla varius. Vivamus ut neque vitae arcu commodo faucibus. Donec tincidunt quam vitae eleifend tempor. Phasellus convallis, sapien vel dapibus suscipit, libero risus hendrerit lacus, ut tincidunt nunc justo at quam. Duis sagittis viverra augue vitae eleifend. Suspendisse pellentesque metus lectus, quis iaculis felis sollicitudin quis. Ut tincidunt congue euismod. Mauris fermentum finibus rutrum. Morbi eget mauris in nulla volutpat volutpat in dictum odio. Nulla pretium nisl augue, eget efficitur odio ultrices id. Donec varius sed metus a efficitur. Etiam lacinia magna non felis auctor, eu lobortis sem sagittis. Nunc molestie consectetur consequat. Cras nec sem a turpis pulvinar iaculis eget at nisl. Praesent tincidunt interdum metus ac feugiat. Vestibulum dui neque, rutrum eu aliquam ultricies, dignissim ac purus. Phasellus eu posuere ligula, in tempus arcu. Etiam eu lorem non ipsum tempor tempor a quis ligula. Quisque porta finibus neque. Cras placerat a lacus sed laoreet. Quisque eget rhoncus lacus, sed bibendum ex. Praesent egestas lacus vitae felis tempor, eget placerat enim lobortis. Mauris tempus bibendum mauris. Fusce tempor, enim sit amet blandit convallis, lectus dui maximus nibh, id sollicitudin lectus urna non metus. Donec sed posuere augue. Morbi sodales pulvinar dolor a aliquam. Etiam gravida magna dui, ac porta tellus tincidunt vitae. Nulla facilisi. Donec eget ex vel tortor fermentum congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer eu ligula et felis luctus rutrum id quis tellus. Pellentesque ut diam non orci eleifend consectetur. Duis lacinia mollis lorem at maximus. Nulla facilisi. Nam sollicitudin ut sapien non molestie. Morbi sit amet sagittis mauris. Proin vel tincidunt elit. Morbi ut est et purus vehicula posuere. Nulla volutpat tincidunt turpis at iaculis. Sed bibendum magna lectus, in semper sapien congue auctor. Sed auctor mi eu dui ornare placerat. Mauris ullamcorper egestas leo, vel dignissim elit iaculis et. Pellentesque ultrices eget felis vel tristique. Cras aliquam lacinia tortor quis ullamcorper. Fusce dapibus tincidunt dui quis lobortis. Ut eget nunc elit. Proin elementum sagittis congue. Suspendisse euismod risus diam, eu dictum erat egestas sed. Aenean sodales nec leo ut pellentesque. Donec rhoncus ante id turpis pretium vulputate. Sed et blandit nisl, ut condimentum purus. Nunc placerat, diam vitae elementum posuere, nibh quam aliquam nibh, vel commodo enim tellus vel felis. Pellentesque ut lorem sit amet neque imperdiet fringilla. Quisque malesuada sem ut odio eleifend hendrerit. Quisque convallis semper justo a tincidunt.',
              ),
            ),
            TextButton(
              onPressed: () {
                setState(() {
                  showMore = !showMore;
                });
              },
              child: showMore ? const Text('Show less') : const Text('Show more'),
            ),
            SizedBox(
              height: (myList.length + newList.length) * widgetSize,
              child: CustomScrollView(
                physics: NeverScrollableScrollPhysics(),
                center: centerKey,
                slivers: [
                  SliverToBoxAdapter(
                    child: Column(
                      children: [],
                    ),
                  ),
                  SliverList(
                    key: centerKey,
                    delegate: SliverChildBuilderDelegate(
                      (context, index) {
                        return ListTile(
                          title: newList[index],
                        );
                      },
                      childCount: newList.length,
                    ),
                  ),
                  SliverList(
                    // key: centerKey,
                    delegate: SliverChildBuilderDelegate(
                      (context, index) {
                        return ListTile(title: myList[index]);
                      },
                      childCount: myList.length,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

this is ultimate solution that I can provide and you need to change widgetSize in your case and try to sort your lists as you wish

3
diegoveloper On

I implemented a custom solution based on the size of the widgets above your Expandable content.

Using an animation when the expandable content is shown

enter image description here

          scrollController.animateTo(
            scrollController.position.minScrollExtent +
                (box?.paintBounds.height ?? 0.0),
            duration: const Duration(milliseconds: 500),
            curve: Curves.easeOut,
          );

Without animation (the flicker is because the dropping frames on this gif)

enter image description here

          scrollController.jumpTo(
            scrollController.position.minScrollExtent +
                (box?.paintBounds.height ?? 0.0),
          );

I added a custom header to simulate the content above your expandable item, this is the full code based on your example:

class TestPage extends StatefulWidget {
  const TestPage({super.key});

  @override
  State<TestPage> createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> {
  List<Widget> newList = List.generate(
    20,
    (index) => Text('Upper ${index.toString()}'),
  );
  List<Widget> myList = List.generate(
    20,
    (index) => Text('Lower ${index.toString()}'),
  );

  bool showMore = false;

  final Key centerKey = const ValueKey('second-sliver-list');
  final scrollController = ScrollController();
  final keyTop = GlobalKey();

  void onShowMore() {
    setState(() {
      showMore = !showMore;
    });
    if (showMore) {
      WidgetsBinding.instance.addPostFrameCallback(
        (_) {
          final box = keyTop.currentContext!.findRenderObject();
          scrollController.jumpTo(
            scrollController.position.minScrollExtent +
                (box?.paintBounds.height ?? 0.0),
          );

          // scrollController.animateTo(
          //   scrollController.position.minScrollExtent +
          //       (box?.paintBounds.height ?? 0.0),
          //   duration: const Duration(milliseconds: 500),
          //   curve: Curves.easeOut,
          // );
        },
      );
    }
  }

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

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton.extended(
            onPressed: () {
              setState(() {
                newList.add(Text('Upper ${newList.length}'));
              });
            },
            label: const Text('Add to Upper'),
          ),
          const SizedBox(height: 10),
          FloatingActionButton.extended(
            onPressed: () {
              setState(() {
                myList.add(Text('Lower ${newList.length}'));
              });
            },
            label: const Text('Add to Lower'),
          ),
        ],
      ),
      appBar: AppBar(),
      body: CustomScrollView(
        center: centerKey,
        controller: scrollController,
        slivers: [
          SliverToBoxAdapter(
            key: keyTop,
            child: Column(
              children: List.generate(
                4,
                (index) => Text(
                  'Header text $index',
                  style: const TextStyle(
                      fontSize: 40, fontWeight: FontWeight.bold),
                ),
              ),
            ),
          ),
          SliverToBoxAdapter(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                SizedBox(
                  height: showMore ? null : 200,
                  child: const Text(
                    'Lorem ipsum dolor sit ame.... your long text here',
                  ),
                ),
                TextButton(
                  onPressed: onShowMore,
                  child: showMore
                      ? const Text('Show less')
                      : const Text('Show more'),
                ),
              ],
            ),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return ListTile(
                  title: newList[index],
                );
              },
              childCount: newList.length,
            ),
          ),
          SliverList(
            key: centerKey,
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return ListTile(title: myList[index]);
              },
              childCount: myList.length,
            ),
          ),
        ],
      ),
    );
  }
}