Open madmini opened 5 months ago
Having a SliverAppBar with
pinned: false, floating: true
and a not-nullexpandedHeight
, the AppBar is collapsed and hidden when scrolling down and pops in and is expanded again when scrolling up.
@madmini Can you provide a runnable code sample that shows the current behavior and what's the expected behavior you are looking for having the proposed option ?
scroll far down: appbar is hidden scroll up a bit: appbar is shown and expands
runnable code for current behavior: test in dartpad (inconsistent behavior of scrollbar in web on desktop (only use scroll wheel), open dartpad on android for best experience)
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
debugShowCheckedModeBanner: false,
home: const Scaffold(
body: Center(
child: MyStatefulWidget(),
),
),
);
}
}
class MyStatefulWidget extends StatelessWidget {
const MyStatefulWidget({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
floating: true,
expandedHeight: 200,
actions: [
TextButton(
onPressed: () {},
child: const Text('test'),
),
],
flexibleSpace: FlexibleSpaceBar(
background: const FlutterLogo(),
),
),
SliverList.list(
children: [
for (var i = 0; i < 50; i++)
Container(
height: kToolbarHeight,
width: double.infinity,
color: i.isEven ? Colors.green : Colors.blue,
),
],
),
],
),
);
}
}
scroll far down: appbar is hidden scroll up a bit: appbar is shown but does not expand scroll to the top: appbar expands
runnable code (workaround) for intended behavior: test in dartpad (see also my answer on stackoverflow. also borked on desktop, use android for best experience)
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: const Scaffold(
body: Center(
child: MyStatefulWidget(),
),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
late ScrollController _scrollController;
// variable height passed to SliverAppBar expanded height
late double? _expandedHeight;
// constant more height that is given to the expandedHeight
// of the SliverAppBar
final double _moreHeight = 200;
@override
initState() {
super.initState();
// initialize and add scroll listener
_scrollController = ScrollController();
_scrollController.addListener(_scrollListen);
// initially expanded height is full
_expandedHeight = _moreHeight;
}
@override
dispose() {
// dispose the scroll listener and controller
_scrollController.removeListener(_scrollListen);
_scrollController.dispose();
super.dispose();
}
void _scrollListen() {
final pos = _scrollController.position;
final offset = pos.pixels;
if (_expandedHeight == null) {
if (offset == 0) {
// AppBar is collapsed and user scrolls to top => enable expansion
setState(() => _expandedHeight = _moreHeight);
// but reset scroll position to avoid jump
pos.correctPixels(_moreHeight - kToolbarHeight);
}
} else {
if (offset > _moreHeight - kToolbarHeight) {
// AppBar is expandable and user has collapsed it by scrolling => disable expansion
setState(() => _expandedHeight = null);
// but reset scroll position to avoid jump
pos.correctPixels(0);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverAppBar(
floating: true,
expandedHeight: _expandedHeight,
actions: [
TextButton(
onPressed: () {},
child: const Text('test'),
),
],
flexibleSpace: FlexibleSpaceBar(
// animate the opacity offset when expanded height is changed
background: AnimatedOpacity(
opacity: _expandedHeight != null
? _expandedHeight! / _moreHeight
: 0,
duration: const Duration(milliseconds: 300),
child: const FlutterLogo(),
),
),
),
SliverList.list(
children: [
for (var i = 0; i < 50; i++)
Container(
height: kToolbarHeight,
width: double.infinity,
color: i.isEven ? Colors.green : Colors.blue,
),
],
),
],
),
);
}
}
Thanks for the update @madmini
Can you take a look at https://stackoverflow.com/questions/57572037/my-sliverappbar-doesnt-expand-when-i-start-scrolling-back-up-it-only-expands-w and see if it helps in your case or not ?
Also, the official documentation has examples with floating
, pinned
and snap
properties that you can take a look and see if it helps further in your case or not.
https://api.flutter.dev/flutter/material/SliverAppBar-class.html. (look for Animated Examples
section)
I have looked at these properties and know their respective effects. I do not want the appbar to hide when scrolling away from the appbar and reappear when scrolling towards it, and I do not want it to snap. The only combination which achieves this is floating = true, pinned = false, snap = false
. However, in addition to this I also want the appbar to only fully expand at the top, like sketched in the runnable code example for the intended behavior in my comment above.
Thanks for the update.
cc @HansMuller
Use case
Having a SliverAppBar with
pinned: false, floating: true
and a not-nullexpandedHeight
, the AppBar is collapsed and hidden when scrolling down and pops in and is expanded again when scrolling up.However, I would like for it to stay collapsed until the scroll view is scrolled all the way to the top again, at which point it should expand again.
See also this StackOverflow post asking how to achieve this, including two flawed workarounds in the answers.
Proposal
Implement this behavior, with a corresponding SliverAppBar parameter (for instance
bool expandOnlyAtTop = false
) that enables it.