Last active
February 8, 2024 10:53
-
-
Save andrzejchm/02c1728b6f31a69fde2fb4e10b636060 to your computer and use it in GitHub Desktop.
ExpandablePageView is a PageView that will adapt its height to the currently displayed child. with animation!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
class ExpandablePageView extends StatefulWidget { | |
final List<Widget> children; | |
const ExpandablePageView({ | |
Key key, | |
@required this.children, | |
}) : super(key: key); | |
@override | |
_ExpandablePageViewState createState() => _ExpandablePageViewState(); | |
} | |
class _ExpandablePageViewState extends State<ExpandablePageView> with TickerProviderStateMixin { | |
PageController _pageController; | |
List<double> _heights; | |
int _currentPage = 0; | |
double get _currentHeight => _heights[_currentPage]; | |
@override | |
void initState() { | |
_heights = widget.children.map((e) => 0.0).toList(); | |
super.initState(); | |
_pageController = PageController() // | |
..addListener(() { | |
final _newPage = _pageController.page.round(); | |
if (_currentPage != _newPage) { | |
setState(() => _currentPage = _newPage); | |
} | |
}); | |
} | |
@override | |
void dispose() { | |
_pageController.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return TweenAnimationBuilder<double>( | |
curve: Curves.easeInOutCubic, | |
duration: const Duration(milliseconds: 100), | |
tween: Tween<double>(begin: _heights[0], end: _currentHeight), | |
builder: (context, value, child) => SizedBox(height: value, child: child), | |
child: PageView( | |
controller: _pageController, | |
children: _sizeReportingChildren, | |
), | |
); | |
} | |
List<Widget> get _sizeReportingChildren => widget.children | |
.asMap() // | |
.map( | |
(index, child) => MapEntry( | |
index, | |
OverflowBox( | |
//needed, so that parent won't impose its constraints on the children, thus skewing the measurement results. | |
minHeight: 0, | |
maxHeight: double.infinity, | |
alignment: Alignment.topCenter, | |
child: SizeReportingWidget( | |
onSizeChange: (size) => setState(() => _heights[index] = size?.height ?? 0), | |
child: child, | |
), | |
), | |
), | |
) | |
.values | |
.toList(); | |
} | |
class SizeReportingWidget extends StatefulWidget { | |
final Widget child; | |
final ValueChanged<Size> onSizeChange; | |
const SizeReportingWidget({ | |
Key key, | |
@required this.child, | |
@required this.onSizeChange, | |
}) : super(key: key); | |
@override | |
_SizeReportingWidgetState createState() => _SizeReportingWidgetState(); | |
} | |
class _SizeReportingWidgetState extends State<SizeReportingWidget> { | |
Size _oldSize; | |
@override | |
Widget build(BuildContext context) { | |
WidgetsBinding.instance.addPostFrameCallback((_) => _notifySize()); | |
return widget.child; | |
} | |
void _notifySize() { | |
final size = context?.size; | |
if (_oldSize != size) { | |
_oldSize = size; | |
widget.onSizeChange(size); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Big thanks to @andrzejchm and @Limbou . You are awesome!