If I got right what your asking for, is basically a way to scroll automatically your ExpansionTile
up so the user doesn't have to do it manually to see its content, right?
Then, you can actually use a ScrollController
in your ListView
and take advantage of the onExpansionChanged
callback of the ExpansionTile
to scroll it up/down accordingly to the size of the tile vs. its current position.
But now you are wondering: how do I know how much to scroll?
For that, either you know previously how exactly is the height of your widget when constructing it by giving it some constraints (like building a Container(heigh: 100.0))
or, in this particular scenario, you don't know exactly how many pixels the ExpansionTile
height will take. So, we can use a GlobalKey
and then check its rendering height by checking its RenderBox
at runtime to know exactly how many pixels it's taking when collapsed and multiply it by its index.
ExpansionTile _buildExpansionTile(int index) {
final GlobalKey expansionTileKey = GlobalKey();
double previousOffset;
return ExpansionTile(
key: expansionTileKey,
onExpansionChanged: (isExpanded) {
if (isExpanded) previousOffset = _scrollController.offset;
_scrollToSelectedContent(isExpanded, previousOffset, index, expansionTileKey);
},
title: Text('My expansion tile $index'),
children: _buildExpansionTileChildren(),
);
}
Ok, now we have a ListView
that will use this _buildExpansionTile
in its generator with a GlobalKey
. We are also saving the scroll offset of the ListView
when we expand the tile. But, how do we actually scroll up to show its content? Let's see the _scrollToSelectedContent
method.
void _scrollToSelectedContent(bool isExpanded, double previousOffset, int index, GlobalKey myKey) {
final keyContext = myKey.currentContext;
if (keyContext != null) {
// make sure that your widget is visible
final box = keyContext.findRenderObject() as RenderBox;
_scrollController.animateTo(isExpanded ? (box.size.height * index) : previousOffset,
duration: Duration(milliseconds: 500), curve: Curves.linear);
}
}
So, we are accessing the render object by its key and then use the ListView
scroll controller to scroll up when expanded and down back to its initial position when collapsed by multiplying its height with its index. You don't have to scroll back, it was just my personal preference for this example. This will result in something like this:
And here you have the full example in a StatefulWidget
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final ScrollController _scrollController = ScrollController();
void _scrollToSelectedContent(bool isExpanded, double previousOffset, int index, GlobalKey myKey) {
final keyContext = myKey.currentContext;
if (keyContext != null) {
// make sure that your widget is visible
final box = keyContext.findRenderObject() as RenderBox;
_scrollController.animateTo(isExpanded ? (box.size.height * index) : previousOffset,
duration: Duration(milliseconds: 500), curve: Curves.linear);
}
}
List<Widget> _buildExpansionTileChildren() => [
FlutterLogo(
size: 50.0,
),
Text(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse vulputate arcu interdum lacus pulvinar aliquam. Donec ut nunc eleifend, volutpat tellus vel, volutpat libero. Vestibulum et eros lorem. Nam ut lacus sagittis, varius risus faucibus, lobortis arcu. Nullam tempor vehicula nibh et ornare. Etiam interdum tellus ut metus faucibus semper. Aliquam quis ullamcorper urna, non semper purus. Mauris luctus quam enim, ut ornare magna vestibulum vel. Donec consectetur, quam a mattis tincidunt, augue nisi bibendum est, quis viverra risus odio ac ligula. Nullam vitae urna malesuada magna imperdiet faucibus non et nunc. Integer magna nisi, dictum a tempus in, bibendum quis nisi. Aliquam imperdiet metus id metus rutrum scelerisque. Morbi at nisi nec risus accumsan tempus. Curabitur non sem sit amet tellus eleifend tincidunt. Pellentesque sed lacus orci.',
textAlign: TextAlign.justify,
),
];
ExpansionTile _buildExpansionTile(int index) {
final GlobalKey expansionTileKey = GlobalKey();
double previousOffset;
return ExpansionTile(
key: expansionTileKey,
onExpansionChanged: (isExpanded) {
if (isExpanded) previousOffset = _scrollController.offset;
_scrollToSelectedContent(isExpanded, previousOffset, index, expansionTileKey);
},
title: Text('My expansion tile $index'),
children: _buildExpansionTileChildren(),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('MyScrollView'),
),
body: ListView.builder(
controller: _scrollController,
itemCount: 100,
itemBuilder: (BuildContext context, int index) => _buildExpansionTile(index),
),
);
}
}