-
-
Save andrzejchm/02c1728b6f31a69fde2fb4e10b636060 to your computer and use it in GitHub Desktop.
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); | |
} | |
} | |
} |
Thank you so much for this Widget 👍
Thanks for the widget, I made some changes to adapt to my need, just added an external PageController and an onPageChanged function
import 'package:flutter/material.dart';
class ExpandablePageView extends StatefulWidget {
final List<Widget> children;
final PageController controller;
final ValueChanged<int> onPageChanged;
const ExpandablePageView({
Key key,
@required this.children,
this.controller,
this.onPageChanged,
}) : 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 = widget.controller ?? PageController() //
..addListener(() {
final _newPage = _pageController.page.round();
if (_currentPage != _newPage) {
widget.onPageChanged(_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);
}
}
}
If anyone needs a dynamic version of that page view (with itemBuilder) here is one:
class ExpandablePageView extends StatefulWidget {
final int itemCount;
final Widget Function(BuildContext, int) itemBuilder;
final PageController controller;
final ValueChanged<int> onPageChanged;
final bool reverse;
const ExpandablePageView({
@required this.itemCount,
@required this.itemBuilder,
this.controller,
this.onPageChanged,
this.reverse = false,
Key key,
}) : assert(itemCount != null),
assert(itemBuilder != null),
super(key: key);
@override
_ExpandablePageViewState createState() => _ExpandablePageViewState();
}
class _ExpandablePageViewState extends State<ExpandablePageView> {
PageController _pageController;
List<double> _heights;
int _currentPage = 0;
double get _currentHeight => _heights[_currentPage];
@override
void initState() {
super.initState();
_heights = List.filled(widget.itemCount, 0, growable: true);
_pageController = widget.controller ?? PageController();
_pageController.addListener(_updatePage);
}
@override
void dispose() {
_pageController?.removeListener(_updatePage);
_pageController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return TweenAnimationBuilder<double>(
curve: Curves.easeInOutCubic,
tween: Tween<double>(begin: _heights.first, end: _currentHeight),
duration: const Duration(milliseconds: 100),
builder: (context, value, child) => SizedBox(height: value, child: child),
child: PageView.builder(
controller: _pageController,
itemCount: widget.itemCount,
itemBuilder: _itemBuilder,
onPageChanged: widget.onPageChanged,
reverse: widget.reverse,
),
);
}
Widget _itemBuilder(BuildContext context, int index) {
final item = widget.itemBuilder(context, index);
return OverflowBox(
minHeight: 0,
maxHeight: double.infinity,
alignment: Alignment.topCenter,
child: SizeReportingWidget(
onSizeChange: (size) => setState(() => _heights[index] = size?.height ?? 0),
child: item,
),
);
}
void _updatePage() {
final newPage = _pageController.page.round();
if (_currentPage != newPage) {
setState(() {
_currentPage = newPage;
});
}
}
}
class SizeReportingWidget extends StatefulWidget {
final Widget child;
final ValueChanged<Size> onSizeChange;
const SizeReportingWidget({
@required this.child,
@required this.onSizeChange,
Key key,
}) : assert(child != null),
assert(onSizeChange != null),
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);
}
}
}
@andrzejchm
It would be good to pass the key
of the ExpandablePageView
widget to the PageView
, to be able to persist the state of the PageView
itself using a PageStorageKey
, for example.
@andrzejchm
Fixed version of SizeReportingWidget
(credit: https://github.com/fluttercommunity/backdrop/blob/d4e10a5547a192052731992285bc9b2625f277b0/lib/scaffold.dart#L608). The previous one didn't work with dynamic widgets like ExpandableListTile
. After expanding, the PageView
height didn't resize.
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> {
final _widgetKey = GlobalKey();
Size _oldSize;
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => _notifySize());
return NotificationListener<SizeChangedLayoutNotification>(
onNotification: (_) {
WidgetsBinding.instance.addPostFrameCallback((_) => _notifySize());
return true;
},
child: SizeChangedLayoutNotifier(
child: Container(
key: _widgetKey,
child: widget.child,
),
),
);
}
void _notifySize() {
final context = _widgetKey.currentContext;
if (context == null) return;
final size = context?.size;
if (_oldSize != size) {
_oldSize = size;
widget.onSizeChange(size);
}
}
}
An example that reproduces the problem:
ExpandablePageView(
children: [
ExpansionTile(
title: Text('Title'),
children: [
Text('Content'),
],
),
],
);
By permission from @andrzejchm I created a package from that code: expandable_page_view
@proninyaroslav I would be really grateful if You could create an issue or even open a pull request with suggested change for that package.
I thought that people would be more interested in just including the package name in pubspec.yaml, rather than copy and paste the code inside their projects. I also hope it will be improved in the future and found bugs will be fixed (as the one You found).
Which widget to use to put that ExpandablePageView inside, cause if I don't put height to parent container, Height of that PageView becomes 0
By permission from @andrzejchm I created a package from that code: expandable_page_view
@proninyaroslav I would be really grateful if You could create an issue or even open a pull request with suggested change for that package.
I thought that people would be more interested in just including the package name in pubspec.yaml, rather than copy and paste the code inside their projects. I also hope it will be improved in the future and found bugs will be fixed (as the one You found).
Thanks for the library. It is working a treat!
@jeremylcarter Thanks for important package. I am facing issue when i change page it will refresh whole parent widget. So to avoid that is there any way.
@jeremylcarter Thanks for important package. I am facing issue when i change page it will refresh whole parent widget. So to avoid that is there any way.
@fitterfly-bimal
this gist is not about the library, for any library-related questions please reach out directly at https://github.com/Limbou/expandable_page_view
Big thanks to @andrzejchm and @Limbou . You are awesome!
Small showcase