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)
- Add
ScrollController
to the CustomScrollView
- add a const variable
moreHeight
that needs to expand
- add changeable
expandedHeight
that will be set while listening to the scroll
- add a listener to the controller which changes variable
expandedHeight
depending on the scroll offset
.
- using this
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
- add new field expandedHeight
- made all named parameters
- add animated opacity to flexible bar depending on expanded height
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
- Made it stateful widget
- add scroll controller, listener, expandedHeight
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