Flutter - Using SearchAnchor Widget Examples

This tutorial shows you how to use the SearchAnchor widget in Flutter for creating a search view with suggestion list.

If an application has search functionality, it's quite common to have a search bar where the user can type the search keyword. In many cases, displaying a dynamic suggestion list can be helpful for the users. Usually, when the search bar is tapped, the application will display a suggestion list in a search view. This tutorial shows you how to do it in Flutter.

Basically, there are two main components that you have to create. The first one is the initial widget to be displayed before the user performs a search. Typically, it's a SearchBar or a search icon. When the user clicks on the initial widget, the application should switch to display a search view. The search view contains a list of suggestions where the user can select any of them.

Actually, you can only have a search bar without the suggestion list. In that case, using the SearchBar widget should be enough. We have a tutorial about how to use and customize the SearchBar widget in Flutter.

For creating a search view, you can use Flutter's SearchAnchor widget. Below are the examples of how to use it.

Using SearchAnchor

Here is the constructor of SearchAnchor.

  SearchAnchor({
    Key? key,
    bool? isFullScreen,
    SearchController? searchController,
    ViewBuilder? viewBuilder,
    Widget? viewLeading,
    Iterable<Widget>? viewTrailing,
    String? viewHintText,
    Color? viewBackgroundColor,
    double? viewElevation,
    Color? viewSurfaceTintColor,
    BorderSide? viewSide
    OutlinedBorder? viewShape,
    TextStyle? headerTextStyle,
    TextStyle? headerHintStyle,
    Color? dividerColor,
    BoxConstraints? viewConstraints,
    required SearchAnchorChildBuilder builder
    required SuggestionsBuilder suggestionsBuilder
  })

There are two required arguments. The first one is bulider, for which you need to pass a function that returns the initial widget that can open the search view. The other one is suggestionsBuilder which requires a function that returns the widget for displaying the suggestion list.

Basic Usage

First, we need to create a function to be passed as the builder argument. It has to return the initial widget to be displayed before the user taps on it. The function must have two parameters whose types in order are BuildContext and SearchController. That means you are able to access the BuildContext and the SearchController of the SearchAnchor for building the initial widget.

In this example, we are going to use a SearchBar as the initial widget. However, you can use any widget such as an IconButton or a button. Since the widget is responsible to open the search view when it's tapped, you need to programmatically open the search view. It can be done by using the openView method of the SearchAnchor's SearchController. The SearchController itself can be set by using the searchController argument. Therefore, even though the searchController argument is not required, most likely you will need to use it.

For the suggestionsBuilder builder, you have to pass a function that returns a list of widgets, each representing a suggestion item. The function also has two parameters of type BuildContext and SearchController.

The widgets returned by the function are laid out in a ListView. If you want to use a different widget other than the ListView, you can use the viewBuilder argument. I'm going to explain how to use the viewBuilder later. For a ListView, it's quite common to have ListTile widgets as the children. Therefore, we are going to make the suggestionsBuilder returns a list of ListTile widgets.

The list of suggestions usually depends on the current text in the search bar. Since the function is called with a SearchController argument, you can obtain the current value from it.

Based on the explanation above, we are going to create a basic SearchAnchor implementation. First, create a SearchController using the constructor.

  final SearchController _searchController = SearchController();

Below is a basic example that only passes the required arguments and the searchController argument.

  class _SearchAnchorExampleState extends State<SearchAnchorExample> {

    final SearchController _searchController = SearchController();

    // these will be reused later
    final leading = const Icon(Icons.search);
    final trailing = [
      IconButton(
        icon: const Icon(Icons.keyboard_voice),
        onPressed: () {
          print('Use voice command');
        },
      ),
      IconButton(
        icon: const Icon(Icons.camera_alt),
        onPressed: () {
          print('Use image search');
        },
      ),
    ];

    @override
    Widget build(BuildContext context) {
      return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          SearchAnchor(
            searchController: _searchController,
            builder: (BuildContext context, SearchController controller) {
              return SearchBar(
                leading: leading,
                trailing: trailing,
                onTap: () {
                  _searchController.openView();
                },
              );
            },
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              final keyword = controller.value.text;
              return List.generate(5, (index) => 'Item $index')
                .where((element) => element.toLowerCase().startsWith(keyword.toLowerCase()))
                .map((item) => ListTile(
                  title: Text(item),
                  onTap: () {
                    setState(() {
                      controller.closeView(item);
                      FocusScope.of(context).unfocus();
                    });
                  },
                ));
            },
          ),
          Expanded(
            child: Center(
              child: _searchController.text.isEmpty
                  ? const Text('No keyword')
                  : Text('Keyword: ${_searchController.value.text}'),
            ),
          ),
        ],
      );
    }
  }

Output:

Flutter - Search Anchor

Set Leading and Trailing Widgets

When the search view is open, you can set a widget to be put before the input field by passing it as the viewLeading argument. If you don't pass the argument, Flutter will display a back button.

In addition, you can also pass a list of widgets as the viewTrailing argument and they will be placed after the input field. It defaults to a clear icon that clears the text.

If the initial widget is a search bar with some leading and trailing widgets, you probably want to keep the same leading and trailing widgets to make the transition seamless.

In the example below, the leading and trailing variables are from the previous example.

  SearchAnchor(
    viewLeading: leading,
    viewTrailing: trailing,
    // other arguments
  )

Output:

Flutter - Search Anchor - Leading & Trailing Widgets

Set Text Style

The text style used on the input field can be set by passing a TextStyle as the headerTextStyle argument. If you don't pass the argument, it defaults to the theme's bodyLarge text style. The default text color is ColorScheme.onSurface.

  SearchAnchor(
    headerTextStyle: const TextStyle(fontWeight: FontWeight.bold),
    // other arguments
  )

Output:

Flutter - Search Anchor - Text Style

Set Hint Text

You can pass a hint text as the hintText argument and it will be displayed if the input field is empty.

The text style for the hint text can be set by using the headerHintStyle argument. If null, the default values in order are SearchViewThemeData.headerHintStyle, value of headerTextStyle argument, and the theme's bodyLarge text style. The default text color is ColorScheme.onSurfaceVariant.

  SearchAnchor(
    viewHintText: 'Enter keyword here',
    headerHintStyle: const TextStyle(color: Colors.grey),
    // other arguments
  )

Output:

Flutter - Search Anchor - Hint Text

Set Background Color

The background color of the search view can be set by using the viewBackgroundColor argument. The default value is SearchViewThemeData.backgroundColor. If that's also null, Flutter will use the value of ColorScheme.surface.

  SearchAnchor(
    viewBackgroundColor: const Color.fromARGB(255, 234, 204, 146),
    // other arguments
  )

Output:

Flutter - Search Anchor - Background Color

Set Divider Color

There is a divider between the input field and the search view. The color can be set by using the dividerColor property. It defaults to SearchViewThemeData.dividerColor if it's not null, otherwise it falls back to ColorScheme.outline.

  SearchAnchor(
    dividerColor: Colors.teal,
    // other arguments
  )

Output:

Flutter - Search Anchor - Divider Color

Enable/Disable Full Screen

By default, the search view fills the entire screen on mobile devices. On other platforms, the size is determined by the anchor and the default size. To change that behavior, you can pass a boolean value as the isFullScreen argument.

  SearchAnchor(
    isFullScreen: false,
    // other arguments
  )

Output:

Flutter - Search Anchor - Full Screen False

Set Elevation

The elevation of the search view can be set by passing a double value as the viewElevation argument. The default value is obtained from the SearchViewThemeData.elevation which falls back to 6.0 if the default value is null. The elevation is only visible if isFullScreen is false.

  SearchAnchor(
    isFullScreen: false,
    viewElevation: 100,
    // other arguments
  )

Output:

Flutter - Search Anchor - Elevation

Set Outline

There is another argument named viewSide for setting the outline of the search view. It allows you to define and pass a custom BorderSide value. Flutter uses SearchViewThemeData.side as the default value and there will be no outline if the theme's value is null. Keep in mind that it will not have any effect if the widget is set to full screen.

  SearchAnchor(
    isFullScreen: false,
    viewSide: const BorderSide(color: Colors.pinkAccent),
    // other arguments
  )

Output:

Flutter - Search Anchor - Outline

Set Shape

The shape of the search view can be set by passing an OutlinedBorder value as the viewShape argument. If it's not passed, Flutter will use the value of SearchViewThemeData.shape. If the theme doesn't have the value, the shape is a rectangle for full-screen and a RoundedRectangleBorder with a radius of 28.0 for non full-screen. While it's possible to set the outline using this argument, it will be replaced by the one set using the viewSide argument if it's not null.

  SearchAnchor(
    isFullScreen: false,
    viewShape: const ContinuousRectangleBorder(
      borderRadius: BorderRadius.all(Radius.circular(20)),
      side: BorderSide(color: Colors.pinkAccent),
    ),
    // other arguments
  )

Output:

Flutter - Search Anchor - Shape

Set Size Constraints

When the view is not full screen, by default the width is the same as the anchor, while the height is 2/3 of the screen. You can set different size constraints by defining a BoxConstraints and pass it as the viewConstraints argument. The value of SearchViewThemeData.constraints will be used as the default value. If the theme's constraints value is null, it defaults to const BoxConstraints(minWidth: 360.0, minHeight: 240.0).

  SearchAnchor(
    isFullScreen: false,
    viewConstraints: const BoxConstraints(
      maxHeight: 300,
    ),
    // other arguments
  )

Output:

Flutter - Search Anchor - Constraints

Set View Builder

By default, the widgets returned by the suggestionsBuilder will be set as the children of a ListView. If you want to use a different parent widget other than a ListView (or if you want to customize the ListView, you can do it by passing a function as the viewBuilder argument.

The function has a parameter whose value is the list of widgets returned by the suggestionsBuilder function. Inside the function, you have to return the parent widget that contains the suggestions. In the example below, we use a GridView to put the suggestions.

  SearchAnchor(
    suggestionsBuilder: (BuildContext context, SearchController controller) {
      final keyword = controller.value.text;
      return List.generate(5, (index) => 'Item $index')
        .where((element) => element.toLowerCase().startsWith(keyword.toLowerCase()))
        .map((item) => GestureDetector(
          onTap: () {
            setState(() {
              controller.closeView(item);
            });
          },
          child: Card(
            color: Colors.amber,
            child: Center(child: Text(item)),
          ),
        ));
    },
    viewBuilder: (Iterable<Widget> suggestions) {
      return GridView.builder(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
          ),
          itemCount: suggestions.length,
          itemBuilder: (BuildContext context, int index) {
            return suggestions.elementAt(index);
          }
      );
    },
    // other arguments
  )

Output:

Flutter - Search Anchor - View Builder

Using SearchAnchor.bar

There is another constructor that's suitable for cases when the initial widget is a search bar. With this constructor, you don't have to manually create the SearchBar and handle the tap events for opening the search view.

  SearchAnchor.bar({
    Widget? barLeading,
    Iterable<Widget>? barTrailing,
    String? barHintText,
    GestureTapCallback? onTap,
    MaterialStateProperty<double?>? barElevation,
    MaterialStateProperty<Color?>? barBackgroundColor,
    MaterialStateProperty<Color?>? barOverlayColor,
    MaterialStateProperty<BorderSide?>? barSide,
    MaterialStateProperty<OutlinedBorder?>? barShape,
    MaterialStateProperty<EdgeInsetsGeometry?>? barPadding,
    MaterialStateProperty<TextStyle?>? barTextStyle,
    MaterialStateProperty<TextStyle?>? barHintStyle,
    Widget? viewLeading,
    Iterable<Widget>? viewTrailing,
    String? viewHintText,
    Color? viewBackgroundColor,
    double? viewElevation,
    BorderSide? viewSide,
    OutlinedBorder? viewShape,
    TextStyle? viewHeaderTextStyle,
    TextStyle? viewHeaderHintStyle,
    Color? dividerColor,
    BoxConstraints? constraints,
    BoxConstraints? viewConstraints,
    bool? isFullScreen,
    SearchController searchController,
    required SuggestionsBuilder suggestionsBuilder
  })

The constructor has a lot of arguments. The only required argument is suggestionsBuilder. Those started with bar are used to customize the SearchBar, while those started with view are used to customize the search view. The exception is the constraints argument which is used to add constraints for the SearchBar widget only, even though it doesn't have the bar prefix.

The SearchAnchor's arguments with the bar prefix are equivalent to the respective arguments of SearchBar without the bar. For example, SearchAnchor's barLeading is equivalent to SearchBar's leading. I'm not going to explain those arguments one by one in this tutorial. Instead, you can read our tutorial about SearchBar.

Below is the usage example of the named constructor.

  class _SearchAnchorBarExampleState extends State<SearchAnchorExample> {

    final SearchController _searchController = SearchController();

    final trailing = [
      IconButton(
        icon: const Icon(Icons.keyboard_voice, color: Colors.white),
        onPressed: () {
          print('Use voice command');
        },
      ),
      IconButton(
        icon: const Icon(Icons.camera_alt, color: Colors.white),
        onPressed: () {
          print('Use image search');
        },
      ),
    ];

    @override
    Widget build(BuildContext context) {
      return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          SearchAnchor.bar(
            searchController: _searchController,
            barTrailing: trailing,
            barLeading: const Icon(Icons.search, color: Colors.white),
            barBackgroundColor: MaterialStateProperty.all(Colors.teal),
            barTextStyle: MaterialStateProperty.all(
                const TextStyle(color: Colors.white)
            ),
            barHintText: 'Tap to search',
            barHintStyle: MaterialStateProperty.all(
                const TextStyle(color: Colors.white)
            ),
            constraints: const BoxConstraints(
              maxWidth: 300,
            ),
            viewLeading: const Icon(Icons.search, color: Colors.white),
            viewTrailing: trailing,
            viewBackgroundColor: Colors.pink,
            viewHeaderTextStyle: const TextStyle(color: Colors.white),
            viewHintText: 'Enter keyword here',
            viewHeaderHintStyle: const TextStyle(color: Colors.white),
            viewConstraints: const BoxConstraints(
              maxWidth: 300,
              maxHeight: 300,
            ),
            viewElevation: 100,
            dividerColor: Colors.teal,
            isFullScreen: false,
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              final keyword = controller.value.text;
              return List.generate(5, (index) => 'Item $index')
                  .where((element) => element.toLowerCase().startsWith(keyword.toLowerCase()))
                  .map((item) => GestureDetector(
                onTap: () {
                  setState(() {
                    controller.closeView(item);
                  });
                },
                child: ListTile(
                  title: Text(item, style: const TextStyle(color: Colors.white)),
                  onTap: () {
                    setState(() {
                      controller.closeView(item);
                      FocusScope.of(context).unfocus();
                    });
                  },
                ),
              ));
            },
          ),
          Expanded(
            child: Center(
              child: _searchController.text.isEmpty
                  ? const Text('No keyword')
                  : Text('Keyword: ${_searchController.value.text}'),
            ),
          ),
        ],
      );
    }
  }

Output:

Flutter - Search Anchor Bar

Set SearchViewThemeData

If the application has more than one search views and you want to apply a general theme, you can define a SearchViewThemeData. Below is the constructor.

  const SearchViewThemeData({
    Color? backgroundColor,
    double? elevation,
    Color? surfaceTintColor,
    BoxConstraints? constraints,
    BorderSide? side,
    OutlinedBorder? shape,
    TextStyle? headerTextStyle,
    TextStyle? headerHintStyle,
    Color? dividerColor,
  })

You need to pass it as the searchViewTheme argument of a ThemeData. The ThemeData itself has to be passed as the theme argument of the MaterialApp.

  MaterialApp(
    title: 'Woolha.com Flutter Tutorial',
    theme: ThemeData.light().copyWith(
      searchViewTheme: const SearchViewThemeData(
        dividerColor: Colors.teal,
      ),
    ),
    home: const Home(),
  )

SearchAnchor Parameters

  • Key? key: The widget's key, used to control how a widget is replaced with another.
  • bool? isFullScreen: Whether the search view should fill the entire screen.
  • SearchController? searchController: A controller that can be used to open and close the search view.
  • ViewBuilder? viewBuilder: A function to return the widget to lay out the suggestion list of the search view.
  • Widget? viewLeading: The widget to be displayed before the input field.
  • Iterable<Widget>? viewTrailing: List of widgets to be displayed after the input field when the search view is open.
  • String? viewHintText: The hint text to be displayed when the search bar is empty and the search view is open.
  • Color? viewBackgroundColor: The background color of the search view.
  • double? viewElevation: The elevation of the search view.
  • Color? viewSurfaceTintColor: The surface tint color of the search view.
  • BorderSide? viewSide: The outline of the search view.
  • OutlinedBorder? viewShape: The shape of the search view.
  • TextStyle? headerTextStyle: The style for the inputted text on search view.
  • TextStyle? headerHintStyle: The style for the hint text on search view.
  • Color? dividerColor: The color of the search view divider.
  • BoxConstraints? viewConstraints: The size constraints for the search view.
  • required SearchAnchorChildBuilder builder: A function to create a widget that can open the search view when tapped.
  • required SuggestionsBuilder suggestionsBuilder: A function to return the widgets for displaying suggestion item.

SearchAnchor.bar Parameters

  • Widget? barLeading: A widget to be displayed before the input field of the search bar.
  • Iterable<Widget>? barTrailing: List of widgets to be displayed after the input field of the search bar.
  • String? barHintText: Text that suggests what should be inputted.
  • GestureTapCallback? onTap: The hint text to be displayed when the search bar is empty and the search view is closed.
  • MaterialStateProperty<double?>? barElevation: The elevation of the search bar when the search view is closed.
  • MaterialStateProperty<Color?>? barBackgroundColor: The background color of the search bar when the search view is closed.
  • MaterialStateProperty<Color?>? barOverlayColor: The highlight color to indicate the state of the search bar when the search view is closed.
  • MaterialStateProperty<BorderSide?>? barSide: The outline of the search bar when the search view is closed.
  • MaterialStateProperty<OutlinedBorder?>? barShape: The shape of the search bar when the search view is closed.
  • MaterialStateProperty<EdgeInsetsGeometry?>? barPadding: The padding between the search bar's boundary and the contents when the search view is closed.
  • MaterialStateProperty<TextStyle?>? barTextStyle: The style for the inputted text.
  • MaterialStateProperty<TextStyle?>? barHintStyle: The style for the barHintText.
  • Widget? viewLeading: The widget to be displayed before the input field.
  • Iterable<Widget>? viewTrailing: List of widgets to be displayed after the input field when the search view is open.
  • String? viewHintText: The hint text to be displayed when the search bar is empty and the search view is open.
  • Color? viewBackgroundColor: The background color of the search view.
  • double? viewElevation: The elevation of the search view.
  • BorderSide? viewSide: The outline of the search view.
  • OutlinedBorder? viewShape: The shape of the search view.
  • TextStyle? viewHeaderTextStyle: The style for the inputted text on search view.
  • TextStyle? viewHeaderHintStyle: The style for the hint text on search view.
  • Color? dividerColor: The color of the search view divider.
  • BoxConstraints? constraints: The size constraints for the search bar.
  • BoxConstraints? viewConstraints: The size constraints for the search view.
  • bool? isFullScreen: Whether the search view should fill the entire screen.
  • SearchController searchController: The controller that can be used to open or close the search view.
  • required SuggestionsBuilder suggestionsBuilder: A function to return the widgets for displaying suggestion item.

SearchViewThemeData Parameters

  • Color? backgroundColor: The background color of the search view.
  • double? elevation: The elevation of the search view.
  • Color? surfaceTintColor: The surface tint color of the search view.
  • BoxConstraints? constraints: The size constraints for the search view.
  • BorderSide? side: The outline of the search view.
  • OutlinedBorder? shape: The shape of the search view.
  • OutlinedBorder? viewShape: The shape of the search view.
  • TextStyle? headerTextStyle: The style for the inputted text on search view.
  • TextStyle? headerHintStyle: The style for the hint text on search view.
  • Color? dividerColor: The color of the search view divider.

Full Code

  import 'package:flutter/material.dart';
  
  void main() => runApp(const MyApp());
  
  class MyApp extends StatelessWidget {
  
    const MyApp({Key? key}) : super(key: key);
  
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Woolha.com Flutter Tutorial',
        theme: ThemeData.light().copyWith(
          searchViewTheme: const SearchViewThemeData(
            dividerColor: Colors.teal,
          ),
        ),
        home: Scaffold(
          appBar: AppBar(
            title: const Text('Woolha.com Flutter Tutorial'),
            backgroundColor: Colors.teal,
          ),
          body: const Padding(
            padding: EdgeInsets.all(10),
            child: SearchAnchorExample(),
          ),
        ),
      );
    }
  }
  
  class SearchAnchorExample extends StatefulWidget {
  
    const SearchAnchorExample({super.key});
  
    @override
    State<StatefulWidget> createState() {
      return _SearchAnchorExampleState();
      // return _SearchAnchorBarExampleState();
    }
  }
  
  class _SearchAnchorExampleState extends State<SearchAnchorExample> {
  
    final SearchController _searchController = SearchController();
  
    final leading = const Icon(Icons.search);
    final trailing = [
      IconButton(
        icon: const Icon(Icons.keyboard_voice),
        onPressed: () {
          print('Use voice command');
        },
      ),
      IconButton(
        icon: const Icon(Icons.camera_alt),
        onPressed: () {
          print('Use image search');
        },
      ),
    ];
  
    @override
    Widget build(BuildContext context) {
      return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          SearchAnchor(
            searchController: _searchController,
            headerTextStyle: const TextStyle(fontWeight: FontWeight.bold),
            viewHintText: 'Enter keyword here',
            headerHintStyle: const TextStyle(color: Colors.grey),
            viewBackgroundColor: const Color.fromARGB(255, 234, 204, 146),
            dividerColor: Colors.teal,
            isFullScreen: false,
            viewElevation: 100,
            viewSide: const BorderSide(color: Colors.pinkAccent),
            viewShape: const ContinuousRectangleBorder(
              borderRadius: BorderRadius.all(Radius.circular(20)),
              side: BorderSide(color: Colors.pinkAccent),
            ),
            viewConstraints: const BoxConstraints(
              maxHeight: 300,
            ),
            viewLeading: leading,
            viewTrailing: trailing,
            builder: (BuildContext context, SearchController controller) {
              return SearchBar(
                leading: const Icon(Icons.search),
                trailing: trailing,
                onTap: () {
                  _searchController.openView();
                },
              );
            },
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              final keyword = controller.value.text;
              return List.generate(5, (index) => 'Item $index')
                .where((element) => element.toLowerCase().startsWith(keyword.toLowerCase()))
                .map((item) => ListTile(
                  title: Text(item),
                  onTap: () {
                    setState(() {
                      controller.closeView(item);
                      FocusScope.of(context).unfocus();
                    });
                  },
                ));
            },
            // suggestionsBuilder: (BuildContext context, SearchController controller) {
            //   final keyword = controller.value.text;
            //   print('keyword:$keyword');
            //   return List.generate(5, (index) => 'Item $index')
            //     .where((element) => element.toLowerCase().startsWith(keyword.toLowerCase()))
            //     .map((item) => GestureDetector(
            //       onTap: () {
            //         setState(() {
            //           controller.closeView(item);
            //         });
            //       },
            //       child: Card(
            //         color: Colors.amber,
            //         child: Center(child: Text(item)),
            //       ),
            //     ));
            // },
            // viewBuilder: (Iterable<Widget> suggestions) {
            //   return GridView.builder(
            //       gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            //         crossAxisCount: 3,
            //       ),
            //       itemCount: suggestions.length,
            //       itemBuilder: (BuildContext context, int index) {
            //         return suggestions.elementAt(index);
            //       }
            //   );
            // },
          ),
          Expanded(
            child: Center(
              child: _searchController.text.isEmpty
                  ? const Text('No keyword')
                  : Text('Keyword: ${_searchController.value.text}'),
            ),
          ),
        ],
      );
    }
  }
  
  class _SearchAnchorBarExampleState extends State<SearchAnchorExample> {
  
    final SearchController _searchController = SearchController();
  
    final leading = const Icon(Icons.search, color: Colors.white);
    final trailing = [
      IconButton(
        icon: const Icon(Icons.keyboard_voice, color: Colors.white),
        onPressed: () {
          print('Use voice command');
        },
      ),
      IconButton(
        icon: const Icon(Icons.camera_alt, color: Colors.white),
        onPressed: () {
          print('Use image search');
        },
      ),
    ];
  
    @override
    Widget build(BuildContext context) {
      return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          SearchAnchor.bar(
            searchController: _searchController,
            barTrailing: trailing,
            barLeading: leading,
            barBackgroundColor: MaterialStateProperty.all(Colors.teal),
            barTextStyle: MaterialStateProperty.all(
                const TextStyle(color: Colors.white)
            ),
            barHintText: 'Tap to search',
            barHintStyle: MaterialStateProperty.all(
                const TextStyle(color: Colors.white)
            ),
            constraints: const BoxConstraints(
              maxWidth: 300,
            ),
            viewLeading: leading,
            viewTrailing: trailing,
            viewBackgroundColor: Colors.pink,
            viewHeaderTextStyle: const TextStyle(color: Colors.white),
            viewHintText: 'Enter keyword here',
            viewHeaderHintStyle: const TextStyle(color: Colors.white),
            viewConstraints: const BoxConstraints(
              maxWidth: 300,
              maxHeight: 300,
            ),
            viewElevation: 100,
            dividerColor: Colors.teal,
            isFullScreen: false,
            suggestionsBuilder: (BuildContext context, SearchController controller) {
              final keyword = controller.value.text;
              return List.generate(5, (index) => 'Item $index')
                  .where((element) => element.toLowerCase().startsWith(keyword.toLowerCase()))
                  .map((item) => GestureDetector(
                onTap: () {
                  setState(() {
                    controller.closeView(item);
                  });
                },
                child: ListTile(
                  title: Text(item, style: const TextStyle(color: Colors.white)),
                  onTap: () {
                    setState(() {
                      controller.closeView(item);
                      FocusScope.of(context).unfocus();
                    });
                  },
                ),
              ));
            },
          ),
          Expanded(
            child: Center(
              child: _searchController.text.isEmpty
                  ? const Text('No keyword')
                  : Text('Keyword: ${_searchController.value.text}'),
            ),
          ),
        ],
      );
    }
  }

Summary

In this tutorial, we have learned about how to use the SearchAnchor widget in Flutter. It allows you to build a search view with a suggestion list. The widget has a lot of arguments for customizations such as changing the text style, enabling/disabling full screen, changing the shape, and much more. There is also a named constructor SearchAnchor.bar that's easier to use if the initial widget is a SearchBar. Flutter also allows you to create a SearchViewThemeData to be set as the default theme for all SearchAnchor widgets in your application.

You can also read about: