Just built up on @BananaMaster's answer, the little tweak adds an arrow to the tile and some shadows to make it all look more like the original ExpansionPanel.
class TestPage extends StatefulWidget {
const TestPage({Key? key}) : super(key: key);
@override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
bool isExpanded = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView(
children: [
Column(
children: [
ListTile(
onTap: () {
setState(() => isExpanded = !isExpanded);
},
leading: const Text(
'Title goes here',
style: TextStyle(fontSize: 17),
),
title: Align(
child: isExpanded == true
? const Icon(Icons.arrow_drop_up)
: const Icon(Icons.arrow_drop_down),
alignment: Alignment.centerRight,
),
margin: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(5),
topRight: Radius.circular(5),
bottomLeft: Radius.circular(5),
bottomRight: Radius.circular(5)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 1,
offset:
const Offset(0, 1),
),
],
),
),
AnimatedSize(
duration: Duration(milliseconds: 250),
curve: Curves.fastOutSlowIn,
child: AnimatedCrossFade(
firstChild: Container(height: 0.0),
secondChild: Column(
children: [
Text('widget 1'),
Text('widget 2'),
Text('widget 3'),
Text('widget 4'),
Text('widget 5'),
],
),
firstCurve:
const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve:
const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn,
crossFadeState: isExpanded
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: Duration(milliseconds: 250),
),
),
],
),
],
),
),
);
}
}