Last active
February 9, 2024 00:20
-
-
Save HansMuller/3b3f24c14fa201f752407a21ca9c9456 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// PinnedHeaderSliver demo | |
// https://github.com/flutter/flutter/pull/143196 | |
import 'dart:math' as math; | |
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
void main() { | |
runApp(const PinnedHeaderSliverApp()); | |
} | |
class PinnedHeaderSliverApp extends StatelessWidget { | |
const PinnedHeaderSliverApp({ super.key }); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
theme: ThemeData(useMaterial3: true), | |
home: const PinnedHeaderSliverExample(), | |
); | |
} | |
} | |
class PinnedHeaderSliver extends SingleChildRenderObjectWidget { | |
const PinnedHeaderSliver({ | |
super.key, | |
super.child, | |
}); | |
@override | |
RenderPinnedHeaderSliver createRenderObject(BuildContext context) { | |
return RenderPinnedHeaderSliver(); | |
} | |
} | |
class RenderPinnedHeaderSliver extends RenderSliverSingleBoxAdapter { | |
RenderPinnedHeaderSliver({ super.child }); | |
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); | |
final double layoutExtent = clampDouble(childExtent - constraints.scrollOffset, 0, constraints.remainingPaintExtent); | |
final double paintExtent = math.min(childExtent, constraints.remainingPaintExtent - constraints.overlap); | |
geometry = SliverGeometry( | |
scrollExtent: childExtent, | |
paintOrigin: constraints.overlap, | |
paintExtent: paintExtent, | |
layoutExtent: layoutExtent, | |
maxPaintExtent: childExtent, | |
maxScrollObstructionExtent: childExtent, | |
cacheExtent: calculateCacheOffset(constraints, from: 0.0, to: childExtent), | |
hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. | |
); | |
} | |
} | |
class PinnedHeaderSliverExample extends StatefulWidget { | |
const PinnedHeaderSliverExample({ super.key }); | |
@override | |
State<PinnedHeaderSliverExample> createState() => _PinnedHeaderSliverExampleState(); | |
} | |
class _PinnedHeaderSliverExampleState extends State<PinnedHeaderSliverExample> { | |
int count = 0; | |
late final ScrollController scrollController; | |
@override | |
void initState() { | |
super.initState(); | |
scrollController = ScrollController(); | |
} | |
@override | |
void dispose() { | |
scrollController.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
final ThemeData theme = Theme.of(context); | |
final ColorScheme colorScheme = theme.colorScheme; | |
final Widget header = Container( | |
color: colorScheme.background, | |
padding: const EdgeInsets.all(4), | |
child: Material( | |
color: colorScheme.primaryContainer, | |
shape: RoundedRectangleBorder( | |
borderRadius: BorderRadius.circular(8), | |
side: BorderSide( | |
width: 7, | |
color: colorScheme.outline, | |
), | |
), | |
child: Container( | |
alignment: Alignment.center, | |
padding: const EdgeInsets.symmetric(vertical: 48), | |
child: Text( | |
count.isOdd ? 'Alternative Title\nWith Two Lines' : 'PinnedHeaderSliver', | |
style: theme.textTheme.headlineMedium!.copyWith( | |
color: colorScheme.onPrimaryContainer, | |
), | |
), | |
), | |
), | |
); | |
return Scaffold( | |
body: SafeArea( | |
child: Padding( | |
padding: const EdgeInsets.symmetric(horizontal: 4), | |
child: Scrollbar( | |
controller: scrollController, | |
child: CustomScrollView( | |
controller: scrollController, | |
slivers: <Widget>[ | |
PinnedHeaderSliver(child: header), | |
const ItemList(), | |
], | |
), | |
), | |
), | |
), | |
floatingActionButton: FloatingActionButton( | |
onPressed: () { | |
setState(() { | |
count += 1; | |
}); | |
}, | |
child: const Icon(Icons.add), | |
), | |
); | |
} | |
} | |
// A placeholder SliverList of 25 items. | |
class ItemList extends StatelessWidget { | |
const ItemList({ | |
super.key, | |
this.itemCount = 25, | |
}); | |
final int itemCount; | |
@override | |
Widget build(BuildContext context) { | |
final ColorScheme colorScheme = Theme.of(context).colorScheme; | |
return SliverList( | |
delegate: SliverChildBuilderDelegate( | |
(BuildContext context, int index) { | |
return Card( | |
color: colorScheme.onSecondary, | |
child: ListTile( | |
textColor: colorScheme.secondary, | |
title: Text('Item $index'), | |
), | |
); | |
}, | |
childCount: itemCount, | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment