Skip to content

Instantly share code, notes, and snippets.

@jogboms
Last active September 4, 2022 11:35
Show Gist options
  • Save jogboms/21f82a71d4890d92436ca7fd38a895b3 to your computer and use it in GitHub Desktop.
Save jogboms/21f82a71d4890d92436ca7fd38a895b3 to your computer and use it in GitHub Desktop.
Parallax Header
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(
const MaterialApp(
debugShowCheckedModeBanner: false,
home: ParallaxHeaderDemo(),
),
);
class ParallaxHeaderDemo extends StatefulWidget {
const ParallaxHeaderDemo({super.key});
@override
State<ParallaxHeaderDemo> createState() => _ParallaxHeaderDemoState();
}
class _ParallaxHeaderDemoState extends State<ParallaxHeaderDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ParallaxHeaderScrollView(
header: AppBar(
title: const Text('Parallax Header'),
actions: [
IconButton(onPressed: () {}, icon: const Icon(Icons.add)),
],
),
slivers: const [
SliverFillRemaining(child: ColoredBox(color: Colors.greenAccent)),
SliverFillRemaining(child: ColoredBox(color: Colors.redAccent)),
],
),
);
}
}
class ParallaxHeaderScrollView extends ScrollView {
const ParallaxHeaderScrollView({
super.key,
required this.header,
required this.slivers,
}) : super(center: _centerKey);
final Widget header;
final List<Widget> slivers;
static const Key _centerKey = Key('center');
@override
List<Widget> buildSlivers(BuildContext context) => [
SliverParallaxHeader(child: header),
KeyedSubtree(key: _centerKey, child: slivers.first),
...slivers.sublist(1),
];
}
class SliverParallaxHeader extends SingleChildRenderObjectWidget {
const SliverParallaxHeader({super.key, required super.child});
@override
RenderObject createRenderObject(BuildContext context) => RenderSliverParallaxHeader();
}
class RenderSliverParallaxHeader extends RenderSliverSingleBoxAdapter {
RenderSliverParallaxHeader({super.child});
@override
void performLayout() {
if (child == null) {
geometry = SliverGeometry.zero;
return;
}
final SliverConstraints constraints = this.constraints;
child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
final double childExtent = child!.size.height;
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent);
final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: childExtent);
assert(paintedChildSize.isFinite);
assert(paintedChildSize >= 0.0);
geometry = SliverGeometry(
scrollExtent: childExtent,
paintExtent: paintedChildSize,
cacheExtent: cacheExtent,
maxPaintExtent: childExtent,
hitTestExtent: paintedChildSize,
scrollOffsetCorrection: geometry == null ? childExtent : null,
hasVisualOverflow: childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
);
setChildParentData(child!, constraints, geometry!);
}
@override
void paint(PaintingContext context, Offset offset) {
if (child != null && geometry!.visible) {
context.paintChild(child!, offset);
// For when you want it fixed to the top when you over scroll on iOS
// context.paintChild(child!, Offset.zero);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment