sliver app bar with search functionality in flutter

3,578

This is a solution to make the search bar fixed and stop it from shrinking:

You can use two SilverAppBars, one for the background image and one for the search bar. The first SilverAppBar has no title and elevation and is not pinned. The second SilverAppBar is pinned and has elevation and its title is the SearchBar.

 @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxScrolled) {
            return <Widget>[
              createSilverAppBar1(),
              createSilverAppBar2()
            ];
          },
          body: ListView.builder(
              itemCount: itemList.length,
              itemBuilder: (context, index) {
                return Card(
                  child: ListTile(
                    title: Text(itemList[index]),
                  ),
                );
              })),
    );
  }

  SliverAppBar createSilverAppBar1() {
    return SliverAppBar(
      backgroundColor: Colors.redAccent,
      expandedHeight: 300,
      floating: false,
      elevation: 0,
      flexibleSpace: LayoutBuilder(
          builder: (BuildContext context, BoxConstraints constraints) {
            return FlexibleSpaceBar(
              collapseMode: CollapseMode.parallax,
              background: Container(
                color: Colors.white,
                child: Image.asset(
                  'assets/mainBackImage.jpg',
                  fit: BoxFit.cover,
                ),
              ),
            );
          }),
    );
  }

  SliverAppBar createSilverAppBar2() {
    return SliverAppBar(
      backgroundColor: Colors.redAccent,
      pinned: true,
      title: Container(
        margin: EdgeInsets.symmetric(horizontal: 10),
        height: 40,
        decoration: BoxDecoration(
          boxShadow: <BoxShadow>[
            BoxShadow(
                color: Colors.grey.withOpacity(0.6),
                offset: const Offset(1.1, 1.1),
                blurRadius: 5.0),
          ],
        ),
        child: CupertinoTextField(
          controller: _filter,
          keyboardType: TextInputType.text,
          placeholder: 'Search',
          placeholderStyle: TextStyle(
            color: Color(0xffC4C6CC),
            fontSize: 14.0,
            fontFamily: 'Brutal',
          ),
          prefix: Padding(
            padding: const EdgeInsets.fromLTRB(5.0, 5.0, 0.0, 5.0),
            child: Icon(
              Icons.search,
              size: 18,
              color: Colors.black,
            ),
          ),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(8.0),
            color: Colors.white,
          ),
        ),
      ),
    );
  }

Result:

res

This is a solution to make a layout based on gif image 1:

Using Stack you can make the search bar stack on top of the background. The search bar's offset would be expandedHeight - shrinkOffset - 20 since it should be dependent on how much the app bar is shrinked and the total height of the app bar when its not shrinked. The 20 is half the height of the search bar and its subtracted to make the search bar move up half its height.

@override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxScrolled) {
            return <Widget>[
              SliverPersistentHeader(
                delegate: MySliverAppBar(expandedHeight: 200, filter: _filter),
                pinned: true,
              ),
            ];
          },
          body: ListView.builder(
              itemCount: itemList.length,
              itemBuilder: (context, index) {
                return Card(
                  child: ListTile(
                    title: Text(itemList[index]),
                  ),
                );
              })),
    );
  }

class MySliverAppBar extends SliverPersistentHeaderDelegate {
  final double expandedHeight;
  final TextEditingController filter;
  MySliverAppBar({@required this.expandedHeight, @required this.filter});
  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    var searchBarOffset = expandedHeight - shrinkOffset - 20;
    return Stack(
      fit: StackFit.expand,
      overflow: Overflow.visible,
      children: [
        Container(
          child: Image.network(
            'assets/mainBackImage.jpg',
            fit: BoxFit.cover,
          ),
        ),
        (shrinkOffset < expandedHeight - 20) ? Positioned(
          top: searchBarOffset,
          left: MediaQuery.of(context).size.width / 4,
          child: Card(
            elevation: 10,
            child: SizedBox(
            height: 40,
            width: MediaQuery.of(context).size.width / 2,
            child: CupertinoTextField(
              controller: filter,
              keyboardType: TextInputType.text,
              placeholder: 'Search',
              placeholderStyle: TextStyle(
                color: Color(0xffC4C6CC),
                fontSize: 14.0,
                fontFamily: 'Brutal',
              ),
              prefix: Padding(
                padding: const EdgeInsets.fromLTRB(5.0, 5.0, 0.0, 5.0),
                child: Icon(
                  Icons.search,
                  size: 18,
                  color: Colors.black,
                ),
              ),
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(8.0),
                color: Colors.white,
              ),
            ),
          ),
          ),
        ) : Container(
          margin: EdgeInsets.symmetric(
              horizontal: MediaQuery.of(context).size.width / 4,
              vertical: (kToolbarHeight - 40) / 4
          ),
          child: Card(
            elevation: 10,
            child: CupertinoTextField(
              controller: filter,
              keyboardType: TextInputType.text,
              placeholder: 'Search',
              placeholderStyle: TextStyle(
                color: Color(0xffC4C6CC),
                fontSize: 14.0,
                fontFamily: 'Brutal',
              ),
              prefix: Padding(
                padding: const EdgeInsets.fromLTRB(5.0, 5.0, 0.0, 5.0),
                child: Icon(
                  Icons.search,
                  size: 18,
                  color: Colors.black,
                ),
              ),
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(8.0),
                color: Colors.white,
              ),
            ),
          ),
        ),
      ],
    );
  }

  @override
  double get maxExtent => expandedHeight;

  @override
  double get minExtent => kToolbarHeight;

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}

Result:

res2

Share:
3,578
justaguy
Author by

justaguy

Updated on December 23, 2022

Comments

  • justaguy
    justaguy over 1 year

    hope you have good day. I wanna achieve something like this below => gif image 1

    for whom gif is not clear.it is screenshot from app called Yelp. it is sliver app bar with expanding and collapsing. when it collapse search bar goes fixed to title. anyway i have done by far this => gif image 2

    my search bar is shrinking when i collapse sliver app bar. i want that search wont shrink when i collapse sliver app bar and fix search bar in title above. this is my code

    import 'package:flutter/material.dart';
    
    class HomePage extends StatefulWidget {
      @override
      _HomePageState createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      double changingHeight;
      double appBarHeight;
      bool appBarSearchShow = false;
      final TextEditingController _filter = new TextEditingController();
    
      List<String> itemList = [];
    
      @override
      void initState() {
        for (int count = 0; count < 50; count++) {
          itemList.add("Item $count");
        }
        changingHeight = 300;
      }
    
      @override
      Widget build(BuildContext context) {
        appBarHeight = MediaQuery.of(context).padding.top + kToolbarHeight;
        return Scaffold(
          backgroundColor: Colors.white,
          body: NestedScrollView(
              headerSliverBuilder: (BuildContext context, bool innerBoxScrolled) {
                return <Widget>[createSilverAppBar()];
              },
              body: ListView.builder(
                  itemCount: itemList.length,
                  itemBuilder: (context, index) {
                    return Card(
                      child: ListTile(
                        title: Text(itemList[index]),
                      ),
                    );
                  })),
        );
      }
    
      SliverAppBar createSilverAppBar() {
        return SliverAppBar(
          backgroundColor: Colors.white,
          expandedHeight: 300,
          floating: false,
          pinned: true,
          // title: appBarSearchShow == true
          //     ? CupertinoTextField(
          //         controller: _filter,
          //         keyboardType: TextInputType.text,
          //         placeholder: "Search..",
          //         placeholderStyle: TextStyle(
          //           color: Color(0xffC4C6CC),
          //           fontSize: 14.0,
          //           fontFamily: 'Brutal',
          //         ),
          //         prefix: Padding(
          //           padding: const EdgeInsets.fromLTRB(9.0, 6.0, 9.0, 6.0),
          //           child: Icon(
          //             Icons.search,
          //           ),
          //         ),
          //         decoration: BoxDecoration(
          //           borderRadius: BorderRadius.circular(8.0),
          //           color: Colors.white,
          //         ),
          //       )
          //     : Container(),
          flexibleSpace: LayoutBuilder(
              builder: (BuildContext context, BoxConstraints constraints) {
            if (constraints.biggest.height == appBarHeight) {
              appBarSearchShow = true;
            } else {
              appBarSearchShow = false;
            }
            return FlexibleSpaceBar(
              collapseMode: CollapseMode.parallax,
              titlePadding: EdgeInsets.only(bottom: 10),
              centerTitle: true,
              title: constraints.biggest.height != appBarHeight
                  ? Container(
                      //margin: EdgeInsets.symmetric(horizontal: 10),
                      constraints: BoxConstraints(minHeight: 30, maxHeight: 30),
                      width: 220,
                      decoration: BoxDecoration(
                        boxShadow: <BoxShadow>[
                          BoxShadow(
                              color: Colors.grey.withOpacity(0.6),
                              offset: const Offset(1.1, 1.1),
                              blurRadius: 5.0),
                        ],
                      ),
                      child: CupertinoTextField(
                        controller: _filter,
                        keyboardType: TextInputType.text,
                        placeholder: 'Search',
                        placeholderStyle: TextStyle(
                          color: Color(0xffC4C6CC),
                          fontSize: 14.0,
                          fontFamily: 'Brutal',
                        ),
                        prefix: Padding(
                          padding: const EdgeInsets.fromLTRB(5.0, 5.0, 0.0, 5.0),
                          child: Icon(
                            Icons.search,
                            size: 18,
                          ),
                        ),
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(8.0),
                          color: Colors.white,
                        ),
                      ),
                    )
                  : Container(),
              background: Container(
                //height: constraints.maxHeight - 15,
                color: Colors.white,
                margin: EdgeInsets.only(bottom: 30),
                child: Image.asset(
                  'assets/mainBackImage.jpg',
                  fit: BoxFit.cover,
                ),
              ),
            );
          }),
        );
      }
    }
    

    any help would be appreciated.

  • justaguy
    justaguy over 3 years
    hey @Mobina. Nice trick you did here. but I need more similar to the one i showed above. I will wait for other answers. for now your is best option. plus could you check for 'expansion' i have edited on my question?
  • Mobina
    Mobina over 3 years
    I didn't get what in the first gif you want. Can you elaborate more? Also, can you put an image that shows where the expansion tile would be? @justaguy
  • dev-aentgs
    dev-aentgs over 3 years
    Maybe OP needs transparent background for the search AppBar so that the image of the first AppBar will be visible when expanded @Mobina
  • Mobina
    Mobina over 3 years
    The OP has asked "i want that search wont shrink when i collapse sliver app bar and fix search bar in title above ". I think both of these are solved in my answer. @dev-aentgs
  • justaguy
    justaguy over 3 years
    can you guys @Dev and @Mobina check for the app called Yelp. I need something like that. And i have edited my question. Thanks
  • justaguy
    justaguy over 3 years
    this can work @Mobina. Thanks for your effort. By the way how can i give height for appBar when it is shrinked fully. Also can you check for expansion tile on my edited question above. how can i put that when appBar expanded. Thanks again
  • Newaj
    Newaj about 3 years
    @Mobina , There is some extra space above searchbar (Colors.redAccent), which is visible before scrolling, how to reduce that extra space?