Created
January 21, 2022 22:32
-
-
Save darwin-morocho/5e3f97a35a094724f8d517bcb2171d38 to your computer and use it in GitHub Desktop.
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 WithBottomScrollableContent extends StatefulWidget { | |
final Widget child; | |
final Widget? bottomContent; | |
const WithBottomScrollableContent({ | |
Key? key, | |
required this.child, | |
this.bottomContent, | |
}) : super(key: key); | |
@override | |
State<WithBottomScrollableContent> createState() => _WithBottomScrollableContentState(); | |
factory WithBottomScrollableContent.list({ | |
Key? key, | |
required List<Widget> children, | |
Widget? bottomContent, | |
EdgeInsets padding = EdgeInsets.zero, | |
}) => | |
WithBottomScrollableContent( | |
key: key, | |
child: ListView( | |
shrinkWrap: true, | |
padding: padding, | |
physics: const NeverScrollableScrollPhysics(), | |
children: children, | |
), | |
bottomContent: bottomContent, | |
); | |
} | |
class _WithBottomScrollableContentState extends State<WithBottomScrollableContent> { | |
final _controller = ScrollController(); | |
final _ancestorKey = GlobalKey(); | |
final _bottomKey = GlobalKey(), _dividerKey = GlobalKey(); | |
double? _bottomHeight, _dividerOffset; | |
void _validate() { | |
final child = widget.child; | |
assert(child is! SingleChildScrollView, 'you should use a Column or a ListView'); | |
if (child is ListView) { | |
assert( | |
child.shrinkWrap, | |
'child is a ListView so shrinkWrap must be true', | |
); | |
assert( | |
child.physics is NeverScrollableScrollPhysics, | |
'child is a ListView so physics must be a NeverScrollableScrollPhysics', | |
); | |
} | |
} | |
@override | |
void initState() { | |
super.initState(); | |
_calculateBottomHeight(); | |
} | |
@override | |
void dispose() { | |
_controller.dispose(); | |
super.dispose(); | |
} | |
@override | |
void didUpdateWidget(covariant WithBottomScrollableContent oldWidget) { | |
super.didUpdateWidget(oldWidget); | |
_bottomHeight = null; | |
_calculateBottomHeight(); | |
} | |
void _calculateBottomHeight() { | |
WidgetsBinding.instance!.addPostFrameCallback( | |
(timeStamp) { | |
if (mounted && widget.bottomContent != null) { | |
final prevH = _bottomHeight; | |
final prevDividerOffset = _dividerOffset; | |
final ancestor = _ancestorKey.currentContext!.findRenderObject(); | |
final box = _bottomKey.currentContext!.findRenderObject() as RenderBox; | |
final dBox = _dividerKey.currentContext!.findRenderObject() as RenderBox; | |
_bottomHeight = box.size.height; | |
_dividerOffset = dBox | |
.localToGlobal( | |
Offset.zero, | |
ancestor: ancestor, | |
) | |
.dy + | |
_bottomHeight!; | |
if (_bottomHeight != prevH || prevDividerOffset != _dividerOffset) { | |
setState(() {}); | |
} | |
} | |
}, | |
); | |
} | |
@override | |
Widget build(BuildContext context) { | |
_validate(); | |
return Builder( | |
key: _ancestorKey, | |
builder: ( | |
_, | |
) { | |
double dividerHeight = 0; | |
if (_controller.hasClients && widget.bottomContent != null && _dividerOffset != null) { | |
final viewport = _controller.position.viewportDimension; | |
if (_dividerOffset! < viewport) { | |
dividerHeight = viewport - _dividerOffset!; | |
} | |
} | |
return ListView( | |
controller: _controller, | |
padding: EdgeInsets.zero, | |
children: [ | |
widget.child, | |
if (widget.bottomContent != null) ...[ | |
Container( | |
key: _dividerKey, | |
height: dividerHeight, | |
), | |
Opacity( | |
key: _bottomKey, | |
opacity: _bottomHeight != null ? 1 : 0, | |
child: widget.bottomContent!, | |
), | |
], | |
], | |
); | |
}, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment