Search code examples
flutterdartanimationappbar

Animate inside a custom search bar in flutter AppBar


I have implemented a custom search bar to be toggled as the user selects it. I'm replacing the SearchBar in the appBar where i need it. Initially the search bar will show an Icon(search) with a String("Search")==> view1 and when click on either of them, it will be replaced with another view which has a searchable textfield ==> view 2. i will post what i did so far. I need to give it a nice animation. The animation should move like this. initiallly the view1 is showing then ==> animate view2 from right to left replacing view1 when need to go back to view1 again then ==> inverse the above animation.

The Workaround so far

class SearchBar extends StatefulWidget with PreferredSizeWidget {
  const SearchBar({Key? key}) : super(key: key);

  @override
  _SearchBarState createState() => _SearchBarState();

  @override
  Size get preferredSize => Size.fromHeight(kToolbarHeight);
}

class _SearchBarState extends State<SearchBar> {
  bool _toggle = true;

  @override
  Widget build(BuildContext context) {
    return AppBar(
      elevation: 0.0,
      backgroundColor: CustomColors.mWhite,
      automaticallyImplyLeading: false,
      title:
          AnimatedContainer(
        width: MediaQuery.of(context).size.width * 0.8,
        decoration: _toggle
            ? null
            : BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(15.0),
                border: Border.all(
                  width: 1,
                  color: CustomColors.grey600,
                ),
              ),
        duration: Duration(seconds: 2),
        child: _toggle
            ? GestureDetector(
                onTap: () {
                  setState(() {
                    _toggle = !_toggle;
                  });
                },
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.start,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    Icon(
                      Icons.search,
                      size: 24.0,
                    ),
                    SizedBox(
                      width: 10.0,
                    ),
                    Text(
                      "Search",
                      style: tBody1,
                    ),
                  ],
                ),
              )
            : Center(
                child: TextField(
                  textInputAction: TextInputAction.search,
                  decoration: InputDecoration(
                      prefixIcon: IconButton(
                        icon: Icon(Icons.arrow_back_ios),
                        onPressed: () {
                          setState(() {
                            _toggle = !_toggle;
                          });
                        },
                      ),
                      border: InputBorder.none),
                ),
              ),
      ),
      actions: [
        Container(
          width: 50.0,
          height: 50.0,
          padding: EdgeInsets.only(right: 8.0),
          child: Image.asset(
            'assets/images/settings.png',
          ),
        ),
      ],
    );
  }
}

Solution

  • AnimatedContainer should have a width that is depending on the _toggle value, and in your case only the search field should be included in the AnimatedContainer.

    I simplified your code a little to show how to get the animation working, see below. The transform parameter is responsible for the right-to-left animation, if you comment it, it will animate from left to right.

    I also added an AnimatedOpacity to fade in / out the back icon.

    (You can paste the code as it is into DartPad to see how it works.)

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(home: Scaffold(appBar: SearchBar()));
      }
    }
    
    class SearchBar extends StatefulWidget with PreferredSizeWidget {
      const SearchBar({Key? key}) : super(key: key);
    
      @override
      _SearchBarState createState() => _SearchBarState();
    
      @override
      Size get preferredSize => const Size.fromHeight(kToolbarHeight);
    }
    
    class _SearchBarState extends State<SearchBar> {
      bool _toggle = true;
    
      void _doToggle() => setState(() => _toggle = !_toggle);
    
      @override
      Widget build(BuildContext context) {
        return AppBar(
          title: Stack(children: [
            GestureDetector(
              onTap: _doToggle,
              child: SizedBox(
                  height: kToolbarHeight * 0.8,
                  child: Row(
                    children: const [
                      Icon(
                        Icons.search,
                        size: 24.0,
                      ),
                      SizedBox(
                        width: 10.0,
                      ),
                      Text("Search"),
                    ],
                  )),
            ),
            AnimatedContainer(
              width: _toggle ? 0 : MediaQuery.of(context).size.width,
              transform: Matrix4.translationValues(_toggle ? MediaQuery.of(context).size.width : 0, 0, 0),
              duration: const Duration(seconds: 1),
              height: kToolbarHeight * 0.8,
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(15.0),
                border: Border.all(
                  width: 1,
                  color: Colors.grey[600]!,
                ),
              ),
              child: TextField(
                textInputAction: TextInputAction.search,
                decoration: InputDecoration(
                    prefixIcon: AnimatedOpacity(
                        duration: const Duration(seconds: 1),
                        opacity: _toggle ? 0 : 1,
                        child: IconButton(
                          icon: const Icon(Icons.arrow_back_ios),
                          onPressed: _doToggle,
                        )),
                    border: InputBorder.none),
              ),
            )
          ]),
        );
      }
    }