Created
March 26, 2021 22:20
-
-
Save roipeker/7e2316a118bb1369539e7883cf7097aa to your computer and use it in GitHub Desktop.
Auto constrained height for PageView (same concept applies to ListView).
This file contains hidden or 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
/// uses getx to notify changes. | |
final _pagesSizes = <double>[120, 150, 180, 210]; | |
final count = 4.obs; | |
class DemoAutoSizePageView extends StatefulWidget { | |
@override | |
_DemoAutoSizePageViewState createState() => _DemoAutoSizePageViewState(); | |
} | |
class _DemoAutoSizePageViewState extends State<DemoAutoSizePageView> { | |
PageController controller; | |
@override | |
void initState() { | |
super.initState(); | |
controller = PageController(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('autosize page view.'), | |
), | |
body: Center( | |
child: Column( | |
children: [ | |
const Text("page view dynamic test!"), | |
Obx( | |
() => AutoSizePageView( | |
itemCount: count(), | |
controller: controller, | |
builder: (_, idx) { | |
return Container( | |
height: _pagesSizes[idx], | |
color: Colors.primaries[idx], | |
child: Center(child: Text('Page $idx')), | |
); | |
}, | |
), | |
), | |
// Obx( | |
// () => JellyPageIndicator( | |
// controller: controller, | |
// pageCount: count(), | |
// ), | |
// ), | |
const Text("i'm a bottom text"), | |
const Divider(), | |
], | |
), | |
), | |
floatingActionButton: FloatingActionButton( | |
onPressed: () { | |
_pagesSizes.add(_pagesSizes.last + 20); | |
count(_pagesSizes.length); | |
}, | |
), | |
); | |
} | |
} |
This file contains hidden or 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
/// roipeker 2021. | |
class AutoSizePageView extends StatefulWidget { | |
final IndexedWidgetBuilder builder; | |
final int itemCount; | |
final PageController controller; | |
final ValueChanged<int> onPageChanged; | |
const AutoSizePageView({ | |
Key key, | |
@required this.builder, | |
this.itemCount, | |
this.controller, | |
this.onPageChanged, | |
}) : super(key: key); | |
@override | |
_AutoSizePageViewState createState() => _AutoSizePageViewState(); | |
} | |
class _AutoSizePageViewState extends State<AutoSizePageView> { | |
bool _selfController = false; | |
double currentHeight = 0; | |
bool _firstTime = true ; | |
List<double> _sizes; | |
int _currPage = 0; | |
PageController _pageController; | |
@override | |
void initState() { | |
super.initState(); | |
_sizes = List.filled(widget.itemCount, 0.0); | |
_selfController = widget.controller != null; | |
_pageController = widget.controller ?? PageController(initialPage: 0); | |
_pageController.addListener(_onPageChange); | |
} | |
@override | |
void didUpdateWidget(AutoSizePageView oldWidget) { | |
if (widget.itemCount != oldWidget.itemCount) { | |
_sizes = List.filled(widget.itemCount, 0.0); | |
_sizes[_currPage] = currentHeight; | |
} | |
super.didUpdateWidget(oldWidget); | |
} | |
@override | |
void dispose() { | |
_pageController.removeListener(_onPageChange); | |
if (_selfController) { | |
_pageController.dispose(); | |
} | |
_pageController = null; | |
super.dispose(); | |
} | |
void _updateSize() { | |
currentHeight = _sizes[_currPage]; | |
setState(() {}); | |
} | |
void _onPageChange(){ | |
final newPage = _pageController.page.round(); | |
if( newPage != _currPage ){ | |
_currPage=newPage; | |
currentHeight = _sizes[_currPage]; | |
setState(() {}); | |
} | |
} | |
@override | |
Widget build(BuildContext context) { | |
return AnimatedContainer( | |
duration: .3.seconds, | |
curve: Curves.decelerate, | |
height: currentHeight, | |
child: PageView.builder( | |
controller: _pageController, | |
itemCount: widget.itemCount, | |
itemBuilder: (_, idx) => OverflowBox( | |
alignment: Alignment.topCenter, | |
minHeight: 0, | |
maxHeight: double.infinity, | |
child: WidgetSizeNotifier( | |
onChange: (Size size) { | |
if (_sizes[idx] != size.height) { | |
_sizes[idx] = size.height; | |
} | |
if( _firstTime ){ | |
_firstTime = false; | |
_updateSize(); | |
} | |
}, | |
child: widget.builder(_, idx), | |
), | |
), | |
), | |
); | |
} | |
} | |
class WidgetSizeNotifier extends StatefulWidget { | |
final Widget child; | |
final ValueChanged<Size> onChange; | |
const WidgetSizeNotifier({Key key, this.onChange, this.child}) | |
: super(key: key); | |
@override | |
_WidgetSizeNotifierState createState() => _WidgetSizeNotifierState(); | |
} | |
class _WidgetSizeNotifierState extends State<WidgetSizeNotifier> { | |
Size _oldSize; | |
@override | |
Widget build(BuildContext context) { | |
WidgetsBinding.instance.addPostFrameCallback((_) => _notify()); | |
return widget.child; | |
} | |
void _notify() { | |
final size = context?.size; | |
if (size != _oldSize) { | |
_oldSize = size; | |
widget.onChange(size); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment