I am trying to create a draggable bottom sheet that can be expanded or collapsed by the user. Additionally, the user should be able to temporarily collapse the sheet by holding and swiping it.
The code I have so far works well, except for one issue: I am unable to make the height of the bottom sheet the height of its contents. In the following screenshot the bottom of the widget is exactly below "Line 6":
This is the code I come up with so far:
library draggable_bottom_sheet;
import 'package:flutter/material.dart';
class DraggableTest extends StatefulWidget {
const DraggableTest({super.key});
@override
State<DraggableTest> createState() => _DraggableTestState();
}
class _DraggableTestState extends State<DraggableTest> {
bool _collapsed = false;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Bottom Sheet')),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
OutlinedButton(
onPressed: () {
setState(() {
_collapsed = !_collapsed;
});
},
child: Text(_collapsed ? 'Expand' : 'Collapse')),
DraggableBottomSheet(
expandedWidget: _expandedWidget(),
backgroundColor: Colors.yellow,
collapsed: _collapsed,
maxExtent: 250,
),
],
),
),
);
}
Widget _expandedWidget() {
return ListView(
physics: const NeverScrollableScrollPhysics(),
children: const [
Text('Title', style: TextStyle(fontSize: 22)),
Text('Line 1', style: TextStyle(fontSize: 18)),
Text('Line 2', style: TextStyle(fontSize: 18)),
Text('Line 3', style: TextStyle(fontSize: 18)),
Text('Line 4', style: TextStyle(fontSize: 18)),
Text('Line 5', style: TextStyle(fontSize: 18)),
Text('Line 6', style: TextStyle(fontSize: 18)),
],
);
}
}
class DraggableBottomSheet extends StatefulWidget {
final Widget expandedWidget;
final Color backgroundColor;
final bool collapsed;
final double maxExtent;
const DraggableBottomSheet({
required this.expandedWidget,
required this.backgroundColor,
required this.collapsed,
required this.maxExtent,
super.key,
});
@override
State<DraggableBottomSheet> createState() => _DraggableBottomSheetState();
}
class _DraggableBottomSheetState extends State<DraggableBottomSheet> {
double _currentExtent = 0.0;
final _minExtent = 50.0;
bool _isDragging = false;
@override
void initState() {
_currentExtent =
widget.collapsed ? _minExtent : widget.maxExtent; // <<< This is the line that is causing the problem
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
color: widget.backgroundColor,
child: Align(alignment: Alignment.bottomCenter, child: _sheet()),
);
}
Widget _sheet() {
return GestureDetector(
onVerticalDragUpdate: _onVerticalDragUpdate,
onVerticalDragStart: _onVerticalDragStart,
onVerticalDragEnd: _onVerticalDragEnd,
child: AnimatedContainer(
curve: Curves.bounceOut,
duration: _isDragging ? Duration.zero : const Duration(milliseconds: 500),
height: widget.collapsed ? _minExtent : _currentExtent,
child: widget.collapsed ? null : widget.expandedWidget,
),
);
}
void _onVerticalDragEnd(DragEndDetails details) {
setState(() {
_isDragging = false;
_currentExtent = widget.maxExtent;
});
}
void _onVerticalDragStart(DragStartDetails details) {
setState(() {
_isDragging = true;
});
}
void _onVerticalDragUpdate(DragUpdateDetails details) {
if (widget.collapsed) return;
final newExtent = (_currentExtent - details.delta.dy).roundToDouble();
if (newExtent >= _minExtent && newExtent <= widget.maxExtent) {
setState(() => _currentExtent = newExtent);
}
}
}