Flutter : how Allow content to overlap SliverAppBar?
4,986
You can make the App bar scrolling with overlapping content like flexible space by using any of the following combo depending upon your use case.
-
ListView
with Scroll notification -
SliverList
with Scroll notification -
DraggableScrollableSheet
with DraggableScrollableNotification
Well, Here I am going with the easiest one, DraggableScrollableSheet
which allows to scroll and drag simultaneously to create the desired effect.
Steps
- Stack the
DraggableScrollableSheet
and theAppBar
inside the body of the Scaffold - Use
DraggableScrollableNotification
to update the header height andAppBar
shadow.
Here you go
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(debugShowCheckedModeBanner: false, home: HomePage()));
}
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
final ValueNotifier<double> headerNegativeOffset = ValueNotifier<double>(0);
final ValueNotifier<bool> appbarShadow = ValueNotifier<bool>(false);
final double maxHeaderHeight = 250.0;
final double minHeaderHeight = 56.0;
final double bodyContentRatioMin = .8;
final double bodyContentRatioMax = 1.0;
///must be between min and max values of body content ratio.
final double bodyContentRatioParallax = .9;
@override
void dispose() {
headerNegativeOffset.dispose();
appbarShadow.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
//just for status bar color
appBar: PreferredSize(
preferredSize: Size.fromHeight(0.0),
child: AppBar(
backgroundColor: Colors.pink,
elevation: 0.0,
),
),
body: Stack(
children: <Widget>[
Stack(children: [
Container(
child: ValueListenableBuilder<double>(
valueListenable: headerNegativeOffset,
builder: (context, offset, child) {
return Transform.translate(
offset: Offset(0, offset * -1),
child: SizedBox(
height: maxHeaderHeight,
child: Container(
color: Colors.pink,
),
),
);
})),
NotificationListener<DraggableScrollableNotification>(
onNotification: (notification) {
if (notification.extent == bodyContentRatioMin) {
appbarShadow.value = false;
headerNegativeOffset.value = 0;
} else if (notification.extent == bodyContentRatioMax) {
appbarShadow.value = true;
headerNegativeOffset.value =
maxHeaderHeight - minHeaderHeight;
} else {
double newValue = (maxHeaderHeight - minHeaderHeight) -
((maxHeaderHeight - minHeaderHeight) *
((bodyContentRatioParallax - (notification.extent)) /
(bodyContentRatioMax -
bodyContentRatioParallax)));
appbarShadow.value = false;
if (newValue >= maxHeaderHeight - minHeaderHeight) {
appbarShadow.value = true;
newValue = maxHeaderHeight - minHeaderHeight;
} else if (newValue < 0) {
appbarShadow.value = false;
newValue = 0;
}
headerNegativeOffset.value = newValue;
}
return true;
},
child: Stack(
children: <Widget>[
DraggableScrollableSheet(
initialChildSize: bodyContentRatioMin,
minChildSize: bodyContentRatioMin,
maxChildSize: bodyContentRatioMax,
builder: (BuildContext context,
ScrollController scrollController) {
return Stack(
children: <Widget>[
Container(
alignment: AlignmentDirectional.center,
padding: EdgeInsets.only(
left: 16.0, right: 16.0, top: 16.0),
child: Material(
type: MaterialType.canvas,
color: Colors.white,
elevation: 2.0,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24.0),
topRight: Radius.circular(24.0),
),
child: ListView.builder(
controller: scrollController,
itemCount: 200,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
),
),
],
);
},
),
],
),
)
]),
Positioned(
left: 0.0,
right: 0.0,
top: 0.0,
child: ValueListenableBuilder<bool>(
valueListenable: appbarShadow,
builder: (context, value, child) {
///default height of appbar is 56.0. You can also
///use a custom widget with custom height if you want.
return AppBar(
backgroundColor: Colors.pink,
title: Text("Notes"),
elevation: value ? 2.0 : 0.0,
);
}),
),
],
),
drawer: Drawer(),
);
}
}
See the live demo here.
Author by
Goku
serial up-voter/down-voter God Bless You keep patience karma will work
Updated on December 18, 2022Comments
-
Goku over 1 year
In android we are using
app:behavior_overlapTop="64dp"
to achieve thisI want overlap content same like above GIF in flutter
My code
class DetailsPage extends StatefulWidget { @override _DetailsPage createState() => _DetailsPage(); } class _DetailsPage extends State<DetailsPage> { @override void initState() { super.initState(); SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); _scrollController = ScrollController(); _scrollController.addListener(_scrollListener); } ScrollController _scrollController; bool lastStatus = true; _scrollListener() { if (isShrink != lastStatus) { setState(() { lastStatus = isShrink; }); } } bool get isShrink { return _scrollController.hasClients && _scrollController.offset > (250 - kToolbarHeight); } @override void dispose() { _scrollController.removeListener(_scrollListener); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( // backgroundColor: Colors.transparent, body: NestedScrollView( controller: _scrollController, headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return <Widget>[ SliverAppBar( expandedHeight: 250.0, floating: false, brightness: Brightness.light, pinned: true, // elevation: 0.0, // backgroundColor: AppColors.colorCreateTripOrange, backgroundColor: Colors.white, actions: <Widget>[ GestureDetector( onTap: () { Navigator.of(context).pop(); }, child: Padding( padding: const EdgeInsets.only(right: 20.0), child: Image.asset( 'assets/images/close.png', width: 25.0, height: 25.0, ), ), ) ], leading: Padding( padding: const EdgeInsets.only(left: 20.0, top: 0), child: IconButton( iconSize: 25, icon: Image.asset( 'assets/images/back.png', width: 25, height: 25, ), color: Colors.black, onPressed: () { Navigator.of(context).pop(); }, )), flexibleSpace: FlexibleSpaceBar( centerTitle: false, collapseMode: CollapseMode.parallax, title: Text(isShrink ? "Rome" : "", style: TextStyle( color: isShrink ? Colors.black : Colors.white, fontFamily: 'bin_bold', fontSize: 18.0, )), background: Image.network( "https://media.istockphoto.com/photos/great-colosseum-rome-italy-picture-id692334500", fit: BoxFit.cover, )), ), ]; }, body: Container( // padding: const EdgeInsets.only(left: 20.0, right: 20.0, top: 40.0), decoration: BoxDecoration( color: AppColors.colorWhite, borderRadius: BorderRadius.all(Radius.circular(20)), ), child: Column( children: <Widget>[ Expanded( child: Container( padding: const EdgeInsets.only(left: 20.0, right: 20.0, top: 40.0), decoration: BoxDecoration( color: AppColors.colorWhite, borderRadius: BorderRadius.all(Radius.circular(20)), ), child: ListView( children: <Widget>[ Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Padding( padding: EdgeInsets.symmetric(horizontal: 15.0), child: Text( "Rome", style: TextStyle( color: Colors.black, fontFamily: 'bin_bold', fontSize: 25.0), ), ), Padding( padding: EdgeInsets.only(top: 20, left: 15, right: 15), child: Row( children: <Widget>[ Image.asset( 'assets/images/calender.png', width: 25.0, height: 25.0, ), Padding( padding: const EdgeInsets.only(left: 10.0), child: Text( "March 6-12, 2020", style: TextStyle( fontFamily: 'bin', fontSize: 18, color: AppColors.colorActivityGray), ), ) ], ), ), Container( width: double.infinity, margin: const EdgeInsets.only( top: 20.0, left: 20.0, right: 30.0), // padding: const EdgeInsets.only(left: 20.0, right: 20.0), decoration: BoxDecoration( color: AppColors.colorTripsGray, borderRadius: BorderRadius.all(Radius.circular(20)), border: Border.all(color: AppColors.colorDivider), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Padding( padding: const EdgeInsets.only( top: 15.0, bottom: 0.0, left: 20.0, right: 20.0), child: Text( Constants.region, style: TextStyle( color: AppColors.colorLightBorderOrange, fontFamily: 'din', fontSize: 20), ), ), Padding( padding: const EdgeInsets.only( top: 10.0, bottom: 0.0, left: 20.0, right: 20.0), child: Text( Constants.firstName, style: TextStyle( color: AppColors.colorCreateGreyTrans, fontFamily: 'din', fontSize: 20), ), ), Padding( padding: const EdgeInsets.only( top: 10.0, bottom: 0.0, left: 0.0, right: 00.0), child: Divider(), ), Padding( padding: const EdgeInsets.only( top: 10.0, bottom: 0.0, left: 20.0, right: 20.0), child: Text( Constants.activities, style: TextStyle( color: AppColors.colorLightBorderOrange, fontFamily: 'din', fontSize: 20), ), ), Padding( padding: const EdgeInsets.only( top: 10.0, bottom: 0.0, left: 20.0, right: 20.0), child: Text( "Food & Bar, Must See Attractions", style: TextStyle( color: AppColors.colorCreateGreyTrans, fontFamily: 'din', fontSize: 20), ), ), Padding( padding: const EdgeInsets.only( top: 10.0, bottom: 0.0, left: 0.0, right: 00.0), child: Divider(), ), Padding( padding: const EdgeInsets.only( top: 10.0, bottom: 0.0, left: 20.0, right: 20.0), child: Text( Constants.noOfTravellers, style: TextStyle( color: AppColors.colorLightBorderOrange, fontFamily: 'din', fontSize: 20), ), ), Padding( padding: const EdgeInsets.only( top: 10.0, bottom: 0.0, left: 20.0, right: 20.0), child: Text( "2 Adults, 1 kid", style: TextStyle( color: AppColors.colorCreateGreyTrans, fontFamily: 'din', fontSize: 20), ), ), Padding( padding: const EdgeInsets.only( top: 10.0, bottom: 0.0, left: 0.0, right: 00.0), child: Divider(), ), Padding( padding: const EdgeInsets.only( top: 10.0, bottom: 0.0, left: 20.0, right: 20.0), child: Text( Constants.email, style: TextStyle( color: AppColors.colorLightBorderOrange, fontFamily: 'din', fontSize: 20), ), ), Padding( padding: const EdgeInsets.only( top: 10.0, bottom: 10.0, left: 20.0, right: 20.0), child: Text( "[email protected]", style: TextStyle( color: AppColors.colorCreateGreyTrans, fontFamily: 'din', fontSize: 20), ), ), ], ), ), ], ) ], ), ), ), Container( width: double.infinity, margin: const EdgeInsets.only(top: 20.0), padding: const EdgeInsets.all(10.0), decoration: BoxDecoration( color: AppColors.colorWhite, borderRadius: BorderRadius.all(Radius.circular(40)), border: Border.all(color: AppColors.colorDivider, width: 2.0), ), child: Center( child: Wrap( children: <Widget>[ MaterialButton( padding: const EdgeInsets.symmetric( horizontal: 40, vertical: 20), textColor: Colors.black, color: AppColors.colorWhite, child: Text( Constants.messageTripDesigner, style: TextStyle( fontFamily: 'din_bold', fontSize: Constants.regionFontSize), ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.0), side: BorderSide( color: AppColors.colorLightBorderOrange, width: 2), ), onPressed: () { Navigator.of(context).pop(); }, ) ], ), ), ) ], ), ), ), ); } } class MySliverAppBar extends SliverPersistentHeaderDelegate { final double expandedHeight; MySliverAppBar({@required this.expandedHeight}); @override Widget build( BuildContext context, double shrinkOffset, bool overlapsContent) { return Stack( fit: StackFit.expand, overflow: Overflow.visible, children: [ Image.network( "https://images.pexels.com/photos/396547/pexels-photo-396547.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500", fit: BoxFit.cover, ), Center( child: Opacity( opacity: shrinkOffset / expandedHeight, child: Text( "MySliverAppBar", style: TextStyle( color: Colors.white, fontWeight: FontWeight.w700, fontSize: 23, ), ), ), ), ], ); } @override double get maxExtent => expandedHeight; @override double get minExtent => kToolbarHeight; @override bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true; }
Below are some post that i have tried so far
- https://github.com/flutter/flutter/issues/404
- Allow GridView to overlap SliverAppBar
- How to overlap SliverList on a SliverAppBar
- https://flutter.dev/docs/cookbook/lists/floating-app-bar
- https://medium.com/flutter-community/flutter-increase-the-power-of-your-appbar-sliverappbar-c4f67c4e076f
If need more information please do let me know. Thanks in advance. Your efforts will be appreciated.
-
Goku about 4 yearsthank you for the answer i have checked it, but there is an issue i want add image as background not pink color
-
Darish about 4 yearsYou can use image as well. No problem.
-
Darish about 4 yearsDo you have any example gif?