PinnedHeaderSliver (#143196)
A sliver that remains âpinnedâ to the top of the scroll view. Subsequent slivers scroll behind it. Typically the sliver is created as the first item in the list however it can be inserted anywhere and it will always stop at the top of the scroll view. When the scrolling axis is vertical, the PinnedHeaderSliverâs height is defined by its widget child. Multiple PinnedHeaderSlivers will layout one after the other, once they've scrolled to the top. This sliver is preferable to the general purpose SliverPersistentHeader - for its relatively narrow use case - because there's no need to create a [SliverPersistentHeaderDelegate] or to predict the header's size. Here's a [working demo in DartPad](https://dartpad.dev/?id=3b3f24c14fa201f752407a21ca9c9456). https://github.com/flutter/flutter/assets/1377460/943f2e02-8e73-48b7-90be-61168978ff71 Related sliver utility PRs: https://github.com/flutter/flutter/pull/143538, https://github.com/flutter/flutter/pull/143325, https://github.com/flutter/flutter/pull/127340.
This commit is contained in:
129
examples/api/lib/widgets/sliver/pinned_header_sliver.0.dart
Normal file
129
examples/api/lib/widgets/sliver/pinned_header_sliver.0.dart
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Flutter code sample for [PinnedHeaderSliver].
|
||||
|
||||
void main() {
|
||||
runApp(const PinnedHeaderSliverApp());
|
||||
}
|
||||
|
||||
class PinnedHeaderSliverApp extends StatelessWidget {
|
||||
const PinnedHeaderSliverApp({ super.key });
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
home: PinnedHeaderSliverExample(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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: 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_api_samples/widgets/sliver/pinned_header_sliver.0.dart' as example;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('PinnedHeaderSliver example', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const example.PinnedHeaderSliverApp(),
|
||||
);
|
||||
|
||||
expect(find.text('PinnedHeaderSliver'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byType(FloatingActionButton));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Alternative Title\nWith Two Lines'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user