diff --git a/example/lib/main.dart b/example/lib/main.dart index ec66dac..9ad2c79 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -130,8 +130,8 @@ class Example2 extends StatelessWidget { itemBuilder: (context, index) { return StickyHeaderBuilder( controller: controller, // Optional - builder: (BuildContext context, double stuckAmount) { - stuckAmount = 1.0 - stuckAmount.clamp(0.0, 1.0); + builder: (BuildContext context, double? stuckAmount) { + stuckAmount = 1.0 - (stuckAmount ?? 0.0).clamp(0.0, 1.0); return Container( height: 50.0, color: Color.lerp(Colors.blue[700], Colors.red[700], stuckAmount), @@ -201,8 +201,8 @@ class Example3 extends StatelessWidget { return StickyHeaderBuilder( overlapHeaders: true, controller: controller, // Optional - builder: (BuildContext context, double stuckAmount) { - stuckAmount = 1.0 - stuckAmount.clamp(0.0, 1.0); + builder: (BuildContext context, double? stuckAmount) { + stuckAmount = 1.0 - (stuckAmount ?? 0.0).clamp(0.0, 1.0); return Container( height: 50.0, color: Colors.grey.shade900.withOpacity(0.6 + stuckAmount * 0.4), diff --git a/lib/sticky_headers/render.dart b/lib/sticky_headers/render.dart index f83e58f..c2c798b 100644 --- a/lib/sticky_headers/render.dart +++ b/lib/sticky_headers/render.dart @@ -11,7 +11,7 @@ import 'package:flutter/widgets.dart'; /// Called every layout to provide the amount of stickyness a header is in. /// This lets the widgets animate their content and provide feedback. /// -typedef RenderStickyHeaderCallback = void Function(double stuckAmount); +typedef RenderStickyHeaderCallback = void Function(double? stuckAmount); /// RenderObject for StickyHeader widget. /// @@ -27,16 +27,19 @@ class RenderStickyHeader extends RenderBox RenderStickyHeaderCallback? _callback; ScrollPosition _scrollPosition; bool _overlapHeaders; + double _headerSpacing; RenderStickyHeader({ required ScrollPosition scrollPosition, RenderStickyHeaderCallback? callback, bool overlapHeaders = false, + double headerSpacing = 0.0, RenderBox? header, RenderBox? content, }) : _scrollPosition = scrollPosition, _callback = callback, - _overlapHeaders = overlapHeaders { + _overlapHeaders = overlapHeaders, + _headerSpacing = headerSpacing { if (content != null) add(content); if (header != null) add(header); } @@ -70,6 +73,14 @@ class RenderStickyHeader extends RenderBox markNeedsLayout(); } + set headerSpacing(double newValue) { + if (_headerSpacing == newValue) { + return; + } + _headerSpacing = newValue; + markNeedsLayout(); + } + @override void attach(PipelineOwner owner) { super.attach(owner); @@ -125,7 +136,7 @@ class RenderStickyHeader extends RenderBox headerParentData.offset = Offset(0.0, max(0.0, min(-stuckOffset, maxOffset))); // report to widget how much the header is stuck. - if (_callback != null) { + if (_callback != null && stuckOffset != 0) { final stuckAmount = max(min(headerHeight, stuckOffset), -headerHeight) / headerHeight; _callback!(stuckAmount); } @@ -135,7 +146,7 @@ class RenderStickyHeader extends RenderBox final scrollBox = _scrollPosition.context.notificationContext!.findRenderObject(); if (scrollBox?.attached ?? false) { try { - return localToGlobal(Offset.zero, ancestor: scrollBox).dy; + return localToGlobal(Offset(0, -(_headerSpacing)), ancestor: scrollBox).dy; } 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 ca639ef..fa9a03b 100644 --- a/lib/sticky_headers/widget.dart +++ b/lib/sticky_headers/widget.dart @@ -18,7 +18,7 @@ import './render.dart'; /// -1.0 >= value >= 0.0: past stuck /// ``` /// -typedef StickyHeaderWidgetBuilder = Widget Function(BuildContext context, double stuckAmount); +typedef StickyHeaderWidgetBuilder = Widget Function(BuildContext context, double? stuckAmount); /// Stick Header Widget /// @@ -33,6 +33,7 @@ class StickyHeader extends MultiChildRenderObjectWidget { Key? key, required this.header, required this.content, + this.headerSpacing = 0.0, this.overlapHeaders = false, this.controller, this.callback, @@ -48,6 +49,9 @@ class StickyHeader extends MultiChildRenderObjectWidget { /// Content to be shown below the header. final Widget content; + /// Spacing between sticky header and start position. + final double headerSpacing; + /// If true, the header will overlap the Content. final bool overlapHeaders; @@ -60,21 +64,23 @@ class StickyHeader extends MultiChildRenderObjectWidget { @override RenderStickyHeader createRenderObject(BuildContext context) { - final scrollPosition = controller?.position ?? Scrollable.of(context)!.position; + final scrollPosition = controller?.position ?? Scrollable.of(context).position; return RenderStickyHeader( scrollPosition: scrollPosition, callback: callback, overlapHeaders: overlapHeaders, + headerSpacing: headerSpacing, ); } @override void updateRenderObject(BuildContext context, RenderStickyHeader renderObject) { - final scrollPosition = controller?.position ?? Scrollable.of(context)!.position; + final scrollPosition = controller?.position ?? Scrollable.of(context).position; renderObject ..scrollPosition = scrollPosition ..callback = callback - ..overlapHeaders = overlapHeaders; + ..overlapHeaders = overlapHeaders + ..headerSpacing = headerSpacing; } } @@ -91,6 +97,7 @@ class StickyHeaderBuilder extends StatefulWidget { Key? key, required this.builder, required this.content, + this.headerSpacing = 0.0, this.overlapHeaders = false, this.controller, }) : super(key: key); @@ -102,6 +109,9 @@ class StickyHeaderBuilder extends StatefulWidget { /// Content to be shown below the header. final Widget content; + /// Spacing between sticky header and start position. + final double headerSpacing; + /// If true, the header will overlap the Content. final bool overlapHeaders; @@ -119,12 +129,13 @@ class _StickyHeaderBuilderState extends State { Widget build(BuildContext context) { return StickyHeader( overlapHeaders: widget.overlapHeaders, + headerSpacing: widget.headerSpacing, header: LayoutBuilder( - builder: (context, _) => widget.builder(context, _stuckAmount ?? 0.0), + builder: (context, _) => widget.builder(context, _stuckAmount), ), content: widget.content, controller: widget.controller, - callback: (double stuckAmount) { + callback: (double? stuckAmount) { if (_stuckAmount != stuckAmount) { _stuckAmount = stuckAmount; WidgetsBinding.instance.endOfFrame.then((_) {