Skip to content

Instantly share code, notes, and snippets.

@HansMuller
Created September 26, 2023 01:35
Show Gist options
  • Save HansMuller/39251348d70e79385031bd8266bcd63e to your computer and use it in GitHub Desktop.
Save HansMuller/39251348d70e79385031bd8266bcd63e to your computer and use it in GitHub Desktop.
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class Item extends StatelessWidget {
const Item({ super.key, required this.title, required this.color });
final String title;
final Color color;
@override
Widget build(BuildContext context) {
return Card(
color: color,
child: ListTile(
textColor: Colors.white,
title: Text(title),
),
);
}
}
// A placeholder SliverList of 50 items.
class ItemList extends StatelessWidget {
const ItemList({
super.key,
required this.startColor,
required this.endColor,
this.itemCount = 50,
});
final Color startColor;
final Color endColor;
final int itemCount;
@override
Widget build(BuildContext context) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Item(
title: 'Item $index',
color: Color.lerp(startColor, endColor, index / itemCount)!
);
},
childCount: itemCount,
),
);
}
}
class FloatingHeaderSliver extends StatefulWidget {
const FloatingHeaderSliver({ super.key, required this.child });
final Widget child;
@override
State<FloatingHeaderSliver> createState() => _FloatingHeaderSliverState();
}
class _FloatingHeaderSliverState extends State<FloatingHeaderSliver> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return _FloatingHeaderSliver(
child: widget.child,
);
}
}
class _FloatingHeaderSliver extends SingleChildRenderObjectWidget {
const _FloatingHeaderSliver({
super.child,
});
@override
_RenderFloatingHeaderSliver createRenderObject(BuildContext context) {
return _RenderFloatingHeaderSliver();
}
@override
void updateRenderObject(BuildContext context, _RenderFloatingHeaderSliver renderObject) {
}
}
class _RenderFloatingHeaderSliver extends RenderSliverSingleBoxAdapter {
_RenderFloatingHeaderSliver();
ScrollDirection scrollDirection = ScrollDirection.idle;
double? effectiveScrollOffset;
double? scrollAnchor;
double get childExtent {
if (child == null) {
return 0.0;
}
assert(child!.hasSize);
return switch (constraints.axis) {
Axis.vertical => child!.size.height,
Axis.horizontal => child!.size.width,
};
}
@override
double childMainAxisPosition(covariant RenderObject child) => 0;
@override
void performLayout() {
final SliverConstraints constraints = this.constraints;
child?.layout(constraints.asBoxConstraints(), parentUsesSize: true);
late final double scrollOffset;
late final double paintExtent;
print(constraints.userScrollDirection);
if (constraints.userScrollDirection == ScrollDirection.forward && (scrollAnchor != null || constraints.scrollOffset >= childExtent)) {
scrollAnchor ??= constraints.scrollOffset;
final double scrollAnchorDelta = scrollAnchor! - constraints.scrollOffset;
scrollOffset = clampDouble(childExtent - scrollAnchorDelta, 0, constraints.remainingPaintExtent);
paintExtent = childExtent - scrollOffset;
} else {
scrollAnchor = null;
paintExtent = clampDouble(childExtent - constraints.scrollOffset, 0, constraints.remainingPaintExtent);
scrollOffset = clampDouble(constraints.scrollOffset, 0, childExtent);
}
geometry = SliverGeometry(
scrollExtent: childExtent,
paintOrigin: math.max(-scrollOffset, -childExtent),
paintExtent: paintExtent,
layoutExtent: paintExtent,
maxPaintExtent: childExtent,
maxScrollObstructionExtent: childExtent,
cacheExtent: calculateCacheOffset(constraints, from: 0.0, to: childExtent),
hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
);
}
}
class AppBarParts extends StatefulWidget {
const AppBarParts({ super.key });
@override
State<AppBarParts> createState() => _AppBarPartsState();
}
class _AppBarPartsState extends State<AppBarParts> {
late final ScrollController scrollController;
@override
void initState() {
super.initState();
scrollController = ScrollController();
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
const EdgeInsets horizontalPadding = EdgeInsets.symmetric(horizontal: 8);
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: CustomScrollView(
controller: scrollController,
slivers: const <Widget>[
SliverPadding(
padding: horizontalPadding,
sliver: ItemList(
startColor: Colors.blue,
endColor: Colors.red,
itemCount: 3,
),
),
SliverPadding(
padding: horizontalPadding,
sliver: FloatingHeaderSliver(
child: SizedBox(
height: 128,
child: Item(
title: 'FloatingHeaderSliver',
color: Colors.orange,
),
),
),
),
SliverPadding(
padding: horizontalPadding,
sliver: ItemList(
startColor: Colors.blue,
endColor: Colors.red,
),
),
],
),
),
),
);
}
}
class AppBarPartsApp extends StatelessWidget {
const AppBarPartsApp({ super.key });
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const AppBarParts(),
);
}
}
void main() {
runApp(const AppBarPartsApp());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment