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.

  1. ListView with Scroll notification
  2. SliverList with Scroll notification
  3. 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.

enter image description here

Steps

  1. Stack the DraggableScrollableSheet and the AppBar inside the body of the Scaffold
  2. Use DraggableScrollableNotification to update the header height and AppBar 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.

Share:
4,986
Goku
Author by

Goku

serial up-voter/down-voter God Bless You keep patience karma will work

Updated on December 18, 2022

Comments

  • Goku
    Goku over 1 year

    In android we are using app:behavior_overlapTop="64dp" to achieve this

    enter image description here

    I 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

    If need more information please do let me know. Thanks in advance. Your efforts will be appreciated.

  • Goku
    Goku about 4 years
    thank you for the answer i have checked it, but there is an issue i want add image as background not pink color
  • Darish
    Darish about 4 years
    You can use image as well. No problem.
  • Darish
    Darish about 4 years
    Do you have any example gif?