diff --git a/lib/sticky_headers/render.dart b/lib/sticky_headers/render.dart index 3e49dff..bc0edef 100644 --- a/lib/sticky_headers/render.dart +++ b/lib/sticky_headers/render.dart @@ -28,17 +28,20 @@ class RenderStickyHeader extends RenderBox RenderStickyHeaderCallback _callback; ScrollableState _scrollable; bool _overlapHeaders; + bool _stickToVisibleContent; RenderStickyHeader({ @required ScrollableState scrollable, RenderStickyHeaderCallback callback, bool overlapHeaders: false, + bool stickToVisibleContent: false, RenderBox header, RenderBox content, }) : assert(scrollable != null), _scrollable = scrollable, _callback = callback, - _overlapHeaders = overlapHeaders { + _overlapHeaders = overlapHeaders, + _stickToVisibleContent = stickToVisibleContent { if (content != null) add(content); if (header != null) add(header); } @@ -73,6 +76,14 @@ class RenderStickyHeader extends RenderBox markNeedsLayout(); } + set stickToVisibleContent(bool newValue) { + if (_stickToVisibleContent == newValue) { + return; + } + _stickToVisibleContent = newValue; + markNeedsLayout(); + } + @override void attach(PipelineOwner owner) { super.attach(owner); @@ -117,7 +128,7 @@ class RenderStickyHeader extends RenderBox contentParentData.offset = new Offset(0.0, _overlapHeaders ? 0.0 : headerHeight); // determine by how much the header should be stuck to the top - final double stuckOffset = determineStuckOffset(); + final double stuckOffset = determineStuckOffset(height); // place header over content relative to scroll offset final double maxOffset = height - headerHeight; @@ -131,11 +142,17 @@ class RenderStickyHeader extends RenderBox } } - double determineStuckOffset() { + double determineStuckOffset(double height) { final scrollBox = _scrollable.context.findRenderObject(); if (scrollBox?.attached ?? false) { try { - return localToGlobal(Offset.zero, ancestor: scrollBox).dy; + double scrollOffset = localToGlobal(Offset.zero, ancestor: scrollBox).dy; + + if (_stickToVisibleContent && scrollBox is RenderRepaintBoundary) { + scrollOffset = max(scrollOffset, scrollBox.constraints.maxHeight - height); + } + + return scrollOffset; } catch (e) { // ignore and fall-through and return 0.0 } diff --git a/lib/sticky_headers/widget.dart b/lib/sticky_headers/widget.dart index 333fe2e..57f145c 100644 --- a/lib/sticky_headers/widget.dart +++ b/lib/sticky_headers/widget.dart @@ -34,6 +34,7 @@ class StickyHeader extends MultiChildRenderObjectWidget { @required this.header, @required this.content, this.overlapHeaders: false, + this.stickToVisibleContent: false, this.callback, }) : super( key: key, @@ -50,6 +51,9 @@ class StickyHeader extends MultiChildRenderObjectWidget { /// If true, the header will overlap the Content. final bool overlapHeaders; + /// If true, the header starts scrolling once the bottom of the content is visible. + final bool stickToVisibleContent; + /// Optional callback with stickyness value. If you think you need this, then you might want to /// consider using [StickyHeaderBuilder] instead. final RenderStickyHeaderCallback callback; @@ -62,6 +66,7 @@ class StickyHeader extends MultiChildRenderObjectWidget { scrollable: scrollable, callback: this.callback, overlapHeaders: this.overlapHeaders, + stickToVisibleContent: this.stickToVisibleContent, ); } @@ -70,7 +75,8 @@ class StickyHeader extends MultiChildRenderObjectWidget { renderObject ..scrollable = Scrollable.of(context) ..callback = this.callback - ..overlapHeaders = this.overlapHeaders; + ..overlapHeaders = this.overlapHeaders + ..stickToVisibleContent = this.stickToVisibleContent; } } @@ -88,6 +94,7 @@ class StickyHeaderBuilder extends StatefulWidget { @required this.builder, this.content, this.overlapHeaders: false, + this.stickToVisibleContent: false, }) : super(key: key); /// Called when the sticky amount changes for the header. @@ -100,6 +107,9 @@ class StickyHeaderBuilder extends StatefulWidget { /// If true, the header will overlap the Content. final bool overlapHeaders; + /// If true, the header starts scrolling once the bottom of the content is visible. + final bool stickToVisibleContent; + @override _StickyHeaderBuilderState createState() => new _StickyHeaderBuilderState(); } @@ -111,6 +121,7 @@ class _StickyHeaderBuilderState extends State { Widget build(BuildContext context) { return new StickyHeader( overlapHeaders: widget.overlapHeaders, + stickToVisibleContent: widget.stickToVisibleContent, header: new LayoutBuilder( builder: (context, _) => widget.builder(context, _stuckAmount ?? 0.0), ),