Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stick to visible content #37

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions lib/sticky_headers/render.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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
}
Expand Down
13 changes: 12 additions & 1 deletion lib/sticky_headers/widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -62,6 +66,7 @@ class StickyHeader extends MultiChildRenderObjectWidget {
scrollable: scrollable,
callback: this.callback,
overlapHeaders: this.overlapHeaders,
stickToVisibleContent: this.stickToVisibleContent,
);
}

Expand All @@ -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;
}
}

Expand All @@ -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.
Expand All @@ -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();
}
Expand All @@ -111,6 +121,7 @@ class _StickyHeaderBuilderState extends State<StickyHeaderBuilder> {
Widget build(BuildContext context) {
return new StickyHeader(
overlapHeaders: widget.overlapHeaders,
stickToVisibleContent: widget.stickToVisibleContent,
header: new LayoutBuilder(
builder: (context, _) => widget.builder(context, _stuckAmount ?? 0.0),
),
Expand Down