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

SliverWithPinnedFooter #59

Closed
wants to merge 1 commit into from
Closed
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
103 changes: 103 additions & 0 deletions lib/src/rendering/sliver_with_pinned_footer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import 'dart:math';

import 'package:flutter/rendering.dart';

class RenderSliverWithPinnedFooterParentData extends ParentData
with ContainerParentDataMixin<RenderObject> {}

class RenderSliverWithPinnedFooter extends RenderSliver
with
ContainerRenderObjectMixin<RenderObject,
RenderSliverWithPinnedFooterParentData> {
@override
void setupParentData(covariant RenderObject child) {
if (child.parentData is! RenderSliverWithPinnedFooterParentData) {
child.parentData = RenderSliverWithPinnedFooterParentData();
}
}

@override
void applyPaintTransform(covariant RenderObject child, Matrix4 transform) {
if (child == lastChild) {
final geometry = this.geometry!;
final footer = child as RenderBox;
final footerTop =
geometry.paintOrigin + geometry.paintExtent - footer.size.height;
transform.translate(0.0, footerTop);
}
}

@override
void performLayout() {
final sliver = firstChild as RenderSliver;
final footer = lastChild as RenderBox;
sliver.layout(constraints, parentUsesSize: true);
footer.layout(constraints.asBoxConstraints(), parentUsesSize: true);
final paintExtent = min(constraints.remainingPaintExtent,
sliver.geometry!.paintExtent + footer.size.height);
final layoutExtent = min(constraints.remainingPaintExtent,
sliver.geometry!.layoutExtent + footer.size.height);
final cacheExtent = min(constraints.remainingPaintExtent,
sliver.geometry!.cacheExtent + footer.size.height);
geometry = SliverGeometry(
scrollExtent: sliver.geometry!.scrollExtent + footer.size.height,
paintExtent: paintExtent,
paintOrigin: sliver.geometry!.paintOrigin,
layoutExtent: layoutExtent,
maxPaintExtent: sliver.geometry!.maxPaintExtent + footer.size.height,
maxScrollObstructionExtent: sliver.geometry!.maxScrollObstructionExtent,
hitTestExtent: paintExtent,
visible: sliver.geometry!.visible,
hasVisualOverflow: sliver.geometry!.hasVisualOverflow,
scrollOffsetCorrection: sliver.geometry!.scrollOffsetCorrection,
cacheExtent: cacheExtent,
);
}

@override
bool hitTestChildren(
SliverHitTestResult result, {
double? mainAxisPosition,
double? crossAxisPosition,
}) {
if (mainAxisPosition == null || crossAxisPosition == null) return false;

final geometry = this.geometry!;
final sliver = firstChild as RenderSliver;
final footer = lastChild as RenderBox;
final footerTop =
geometry.paintOrigin + geometry.paintExtent - footer.size.height;
if (mainAxisPosition >= footerTop) {
final hit = footer.hitTest(
BoxHitTestResult.wrap(result),
position: Offset(crossAxisPosition, mainAxisPosition - footerTop),
);
if (hit) {
return true;
}
}
return sliver.hitTest(
result,
mainAxisPosition: mainAxisPosition,
crossAxisPosition: crossAxisPosition,
);
}

@override
void paint(PaintingContext context, Offset offset) {
final geometry = this.geometry!;
final sliver = firstChild as RenderSliver;
final footer = lastChild as RenderBox;
context.paintChild(sliver, offset);
context.paintChild(
footer,
Offset(
offset.dx,
offset.dy +
geometry.paintOrigin +
geometry.paintExtent -
footer.size.height,
Copy link

@TomBursch TomBursch Jan 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To fix the footer appearing first, line 99 should be replaced with min(footer.size.height, geometry.paintExtent) (same for the hitTest)

),
);
}
}
24 changes: 24 additions & 0 deletions lib/src/sliver_with_pinned_footer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:flutter/widgets.dart';

import 'rendering/sliver_with_pinned_footer.dart';

/// [SliverWithPinnedFooter] adds the ability to have a pinned footer at the bottom of a sliver.
///
/// The sliver will be layed out first and the footer will be added below if there
/// is enough room left in the viewport. If there is not enough room then the footer
/// will be painted on top of the sliver at the bottom of the painted area of the sliver.
///
/// The total size of this sliver will be the paintExtent of the passed in sliver
/// plus the height of the footer.
class SliverWithPinnedFooter extends MultiChildRenderObjectWidget {
SliverWithPinnedFooter({
Key? key,
required Widget sliver,
required Widget footer,
}) : super(key: key, children: [sliver, footer]);

@override
RenderObject createRenderObject(BuildContext context) {
return RenderSliverWithPinnedFooter();
}
}