I am using a SliverAppBar for Flutter web with a background image, and I would like the bar to disappear when the user is scrolling down the web and appear again as soon as they scroll up, but only the app bar, without showing the background unless they reach the top. Is this accomplishable in Flutter web?
My SliverAppBar:
class NavBar extends StatelessWidget {
final Widget _background;
const NavBar(this._background);
@override
SliverAppBar build(BuildContext context) {
double _width = MediaQuery.of(context).size.width;
double? _height = MediaQuery.of(context).size.height;
List<Widget> _actions() {
List<Widget> _list = [];
List _titles = Navigation(context).routes.keys.toList();
List _routes = Navigation(context).routes.values.toList();
_selectView(String route) {
Navigator.of(context).pushNamed(route);
}
Widget _singleItem(String text, String route) {
return InkWell(
onTap: () => _selectView(route),
borderRadius: BorderRadius.circular(15),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
alignment: Alignment.center,
child: Text(
text,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
);
}
for (int i = 0; i < Navigation(context).showingLinks; i++) {
_list.add(_singleItem(_titles[i], _routes[i]));
}
return _list;
} // navBarItems
return SliverAppBar(
backgroundColor: Theme.of(context).primaryColor,
expandedHeight: _height,
pinned: true,
elevation: 0,
//TODO make actions appear only when SliverAppBar collapses
actions: _width > 800 ? _actions() : [],
flexibleSpace: FlexibleSpaceBar(
background: _background,
),
);
}
}
And for the general structure that I am using in all of my views here's an example of my HomeView:
class HomeView extends StatelessWidget {
final double paddingHorizontal = 60;
final double paddingVertical = 60;
ScrollController _scrollController = ScrollController();
final _key = GlobalKey();
HomeView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final double width = MediaQuery.of(context).size.width;
Widget navBarBackground() {
return Stack(...)
}
return Scaffold(
backgroundColor: Colors.white,
endDrawer: EndDrawer(),
body: CustomScrollView(
controller: _scrollController,
slivers: [
NavBar(navBarBackground()),
SliverList(
delegate: SliverChildListDelegate(
[
highlights(),
androidIosDesktop(),
multiplatform(),
catchPhrase(),
contact(),
const Footer(),
],
),
)
],
),
);
}
} //HomeView
This is what is shows:
And I would like it to show only this:
Yes, it is. But, I guess not directly.
You can use a ScrollController
to achieve this.
Attach a ScrollController
to the CustomScrollView
, then observe the offset
of the controller where the position is. And based on it, you can achieve the desired output.
I have written a simple demo code which is the exact thing that you need
Try out the dartpad => here
What I did? (look after trying out dartpad)
ScrollController
to the CustomScrollView
moreHeight
that needs to expandexpandedHeight
that will be set while listening to the scrollexpandedHeight
depending on the scroll offset
.expandedHeight
we will change the values of in the SliverAppBar
Edit:
Below code is after separating app bar widget which is a stateless widget, and pass the parameters from the view/page that contains scrollview and can use scroll controller (no change in behaviour)
Note: the following code can be used for the full height expanded app bar
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
double? _expandedHeight;
@override
initState() {
super.initState();
// initialize and add scroll listener
_scrollController = ScrollController();
_scrollController.addListener(_scrollListen);
// initially expanded height is full
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_expandedHeight = MediaQuery.of(context).size.height;
});
});
}
@override
dispose() {
// dispose the scroll listener and controller
_scrollController.removeListener(_scrollListen);
_scrollController.dispose();
super.dispose();
}
_scrollListen() {
final offset = _scrollController.offset;
final height = MediaQuery.of(context).size.height;
if (offset > height) {
// if offset is more height, disable expanded height
if (_expandedHeight != null) {
setState(() {
_expandedHeight = null;
});
}
} else {
// if offset is less, keep increasing the height to offset 0
setState(() {
_expandedHeight = height - offset;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
AppBarWidget(
expandedHeight: _expandedHeight,
),
SliverToBoxAdapter(
child: SizedBox(
height: 2000,
child: Center(
child: Container(
color: Colors.blue,
),
),
),
),
],
),
);
}
}
class AppBarWidget extends StatelessWidget {
const AppBarWidget({super.key, this.expandedHeight});
final double? expandedHeight;
// constant more height that is given to the expandedHeight
// of the SliverAppBar
// static double moreHeight = 200;
@override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
return SliverAppBar(
pinned: false,
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! / height : 0,
duration: const Duration(milliseconds: 300),
child: const FlutterLogo(),
),
),
);
}
}
Edit 2: I have made slight changes to your code
As I see that you require full height expanded for background, I have included in the above code as well as the your code below.
NavBar
class NavBar extends StatelessWidget {
const NavBar({this.background, this.expandedHeight});
final double? expandedHeight;
final Widget? background;
@override
SliverAppBar build(BuildContext context) {
double _width = MediaQuery.of(context).size.width;
double? _height = MediaQuery.of(context).size.height;
List<Widget> _actions() {
List<Widget> _list = [];
List _titles = Navigation(context).routes.keys.toList();
List _routes = Navigation(context).routes.values.toList();
_selectView(String route) {
Navigator.of(context).pushNamed(route);
}
Widget _singleItem(String text, String route) {
return InkWell(
onTap: () => _selectView(route),
borderRadius: BorderRadius.circular(15),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
alignment: Alignment.center,
child: Text(
text,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
);
}
for (int i = 0; i < Navigation(context).showingLinks; i++) {
_list.add(_singleItem(_titles[i], _routes[i]));
}
return _list;
} // navBarItems
return SliverAppBar(
backgroundColor: Theme.of(context).primaryColor,
expandedHeight: expandedHeight,
pinned: true,
elevation: 0,
//TODO make actions appear only when SliverAppBar collapses
actions: _width > 800 ? _actions() : [],
flexibleSpace: FlexibleSpaceBar(
// animate the opacity offset when expanded height is changed
background: AnimatedOpacity(
opacity: expandedHeight != null ? expandedHeight! / _height : 0,
duration: const Duration(milliseconds: 300),
child: background,
),
),
);
}
}
Home View
class HomeView extends StatefulWidget {
const HomeView({Key? key}) : super(key: key);
@override
State<HomeView> createState() => _HomeView();
}
class _HomeView extends State<HomeView> {
final double paddingHorizontal = 60;
final double paddingVertical = 60;
late ScrollController _scrollController;
// variable height passed to SliverAppBar expanded height
double? _expandedHeight;
final _key = GlobalKey();
@override
initState() {
super.initState();
// initialize and add scroll listener
_scrollController = ScrollController();
_scrollController.addListener(_scrollListen);
// initially expanded height is full
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_expandedHeight = MediaQuery.of(context).size.height;
});
});
}
@override
dispose() {
// dispose the scroll listener and controller
_scrollController.removeListener(_scrollListen);
_scrollController.dispose();
super.dispose();
}
_scrollListen() {
final offset = _scrollController.offset;
final height = MediaQuery.of(context).size.height;
if (offset > height) {
// if offset is more height, disable expanded height
if (_expandedHeight != null) {
setState(() {
_expandedHeight = null;
});
}
} else {
// if offset is less, keep increasing the height to offset 0
setState(() {
_expandedHeight = height - offset;
});
}
}
@override
Widget build(BuildContext context) {
final double width = MediaQuery.of(context).size.width;
Widget navBarBackground() {
return Stack(...)
}
return Scaffold(
backgroundColor: Colors.white,
endDrawer: EndDrawer(),
body: CustomScrollView(
controller: _scrollController,
slivers: [
NavBar(
background: navBarBackground(),
expandedHeight: _expandedHeight,
),
SliverList(
delegate: SliverChildListDelegate(
[
highlights(),
androidIosDesktop(),
multiplatform(),
catchPhrase(),
contact(),
const Footer(),
],
),
)
],
),
);
}
} //HomeView