Skip to content

Instantly share code, notes, and snippets.

@darwin-morocho
Created January 21, 2022 22:32
Show Gist options
  • Save darwin-morocho/5e3f97a35a094724f8d517bcb2171d38 to your computer and use it in GitHub Desktop.
Save darwin-morocho/5e3f97a35a094724f8d517bcb2171d38 to your computer and use it in GitHub Desktop.
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