🏝️

「Flutter」検索バーUIと検索機能

2022/09/26に公開

日本語版:

はじめに

今日は、入力をフィルタリングして結果を表示する検索バーを作ってみます。

準備

StatefulWidget を作成し、SearchBar と名付けましょう。

search_bar.dart
class SearchBar extends StatefulWidget {
  const SearchBar({Key? key}) : super(key: key);

  
  State<SearchBar> createState() => _SearchBarState();
}

class _SearchBarState extends State<SearchBar> {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(body: Container()),
    );
  }
}

AppBar

クラスの作成が完了したら、AppBar()Scaffold に追加し、以下のプロパティを追加しょう。

  1. title: const Text("検索バー")
  2. centerTitle: true
  3. actions:[IconButton(icon: const Icon(Icons.search),onPressed: () {})]
    これで、ビルドメソッドは次のようになります。
search_bar.dart
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
        appBar: AppBar(
            title: const Text("検索バー"),
            centerTitle: true,
            actions: [
              IconButton(icon: const Icon(Icons.search), onPressed: () {})
            ]),
        body: Container()),
  );
}

そして、このような形になるはずです。

アイコンの変更

検索のアイコンがありますが、これを❌アイコンに変更したいと思います。
そのためには、onPressedメソッドを処理する変数と、別のアイコンが必要になります。

SearchBarStatebool wasTapped を作成して、それに false という値を代入しましょう。

ここで、新しいアイコンをどこかに配置したいのですが、適切な場所は AppBar() 内の actions だけです。しかし、ただそこに置くだけでは、2つのアイコンが隣り合わせになってしまい、これは私たちが望んでいるものではありません。

そのため、wasTapped変数を宣言しています。

このように作業したいと思います。Start=> Icons.search =>Press=>Icons.clear=>Press=>Icon.search....

そこで、条件 (三項) 演算子が必要である。

条件 (三項) 演算子

条件 ?exprIfTrue(真値) : exprIfFalse(偽値)

このように書いてみましょう。

search_bar.dart
AppBar(
            title: const Text("検索バー"),
            centerTitle: true,
            actions: wasTapped
                  ? [
                      IconButton(
                          icon: const Icon(Icons.clear),
                          onPressed: () {})
                    ]
                  : [
                      IconButton(
                          icon: const Icon(Icons.search),
                          onPressed: () {});
                    ]),

今書いたのは wasTappedがtrueなら❌、falseなら🔎を表示してください。しかし、まだ何かが欠けています...
アイコンを押しても何も起こらない。なぜかというと、アイコンを押すたびに wasTapped の状態を変更しなければならないからです。

そこで、onPressed関数にSetStateを追加してみよう。

search_bar.dart
AppBar(
            title: const Text("検索バー"),
            centerTitle: true,
            actions: wasTapped
                  ? [
                      IconButton(
                          icon: const Icon(Icons.clear),
                          onPressed: () {
+                           setState(() {
+                              wasTapped = false;
                            });
                          })
                    ]
                  : [
                      IconButton(
                          icon: const Icon(Icons.search),
                          onPressed: () {
+                            setState(() {
+                              wasTapped = true;
                            });
                          })
                    ]),

そしてやっと正しく動作するようになりました :D

検索テキストフィールド

アイコンが押されるたびに変化するアイコンもう書きました。今度は、🔎アイコンを押したときにテキストフィールドを表示させたいと思います。
そこで、_SearchBarStateに、TextFieldを返すWidgetを作成しましょう。

search_bar.dart
  Widget searchTextField() {
    return TextField();
  }

ご存知のように、TextField を制御するには、TextEditingController が必要です。そこで、bool wasTapped = false の下に、 TextEditingController txtController = TextEditingController(); と記述してみよう。そして、それを TextFieldcontroller プロパティに代入する。

search_bar.dar
bool wasTapped = false
TextEditingController txtController = TextEditingController();

Widget searchTextField() {
    return TextField(controller: txtController);
  }

ふむふむ、問題なさそうですね。ということで、Widgetをtitleプロパティに配置してみましょう。もちろん、🔎アイコンを押したら表示、❌アイコンを押したら非表示になるようにしたいですね。

search_bar.dart
AppBar(
-           title: const Text("検索バー"),
+           title: wasTapped ? searchTextField() : const Text("検索バー")
            centerTitle: true,
            actions: wasTapped
                  ? [
                      IconButton(
                          icon: const Icon(Icons.clear),
                          onPressed: () {
                           setState(() {
                              wasTapped = false;
                            });
                          })
                    ]
                  : [
                      IconButton(
                          icon: const Icon(Icons.search),
                          onPressed: () {
                            setState(() {
                              wasTapped = true;
                            });
                          })
                    ]),

動作はしているが...そう、「良い」感じではないですね。
この TextField には、いくつかプロパティーを追加する必要がある。

search_bar.dart
Widget searchTextField() {
    return TextField(
      controller: txtController,
      autofocus: true, //TextFieldの表示時にキーボードを表示する
      cursorColor: Colors.white,
      style: const TextStyle(
        color: Colors.white,
        fontSize: 20,
      ),
      textInputAction:
          TextInputAction.search, //キーボードのアクションボタンを指定する
      decoration: const InputDecoration(
        //TextFieldのスタイル
        enabledBorder: UnderlineInputBorder(
            //extFieldのデフォルトの境界線
            borderSide: BorderSide(color: Colors.white)),
        focusedBorder: UnderlineInputBorder(
            //TextFieldにフォーカスがあるときのボーダーライン
            borderSide: BorderSide(color: Colors.white)),
        hintText: '検索', //何も入力されていない時に表示されるテキスト。
        hintStyle: TextStyle(
          //hintTextのスタイル
          color: Colors.white60,
          fontSize: 20,
        ),
      ),
    );
  }

これでいい感じです :D

あなたのコードは次のようになります

search_bar.dart

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

  
  State<SearchBar> createState() => _SearchBarState();
}

class _SearchBarState extends State<SearchBar> {
  bool wasTapped = false;
  TextEditingController txtController = TextEditingController();

  Widget searchTextField() {
    return TextField(
      controller: txtController,
      autofocus: true, //TextFieldの表示時にキーボードを表示する
      cursorColor: Colors.white,
      style: const TextStyle(
        color: Colors.white,
        fontSize: 20,
      ),
      textInputAction:
          TextInputAction.search, //キーボードのアクションボタンを指定する
      decoration: const InputDecoration(
        //TextFieldのスタイル
        enabledBorder: UnderlineInputBorder(
            //extFieldのデフォルトの境界線
            borderSide: BorderSide(color: Colors.white)),
        focusedBorder: UnderlineInputBorder(
            //TextFieldにフォーカスがあるときのボーダーライン
            borderSide: BorderSide(color: Colors.white)),
        hintText: '検索', //何も入力されていない時に表示されるテキスト。
        hintStyle: TextStyle(
          //hintTextのスタイル
          color: Colors.white60,
          fontSize: 20,
        ),
      ),
    );
  }


  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(
              title: wasTapped ? searchTextField() : const Text("検索バー"),
              centerTitle: true,
              actions: wasTapped
                  ? [
                      IconButton(
                          icon: const Icon(Icons.clear),
                          onPressed: () {
                            setState(() {
                              wasTapped = false;
                            });
                          })
                    ]
                  : [
                      IconButton(
                          icon: const Icon(Icons.search),
                          onPressed: () {
                            setState(() {
                              wasTapped = true;
                            });
                          })
                    ]),
          body: Container()),
    );
  }
}

本体

アプリケーションが起動したとき、ユーザーに何かを表示し、検索しようとしたとき、アプリケーションがフィルタリングされた結果を表示するようにしたいです。

コレクション

コレクションから何かを必要とすることになる。Map/List/Array...何でもいいです。
しかし、私たちのアプリケーションでは、Map<String,String> を使用することにします。そして、そこに 超能力 名とその 説明 を格納することになる。

このように、_SearchBarStateMap<String, String> powerDetail という変数を作成します。

search_bar.dart
Map<String, String> powerDetail = {
    "Reactive adaptation/evolution":
        "Ability to develop a resistance or immunity to whatever they were injured by or exposed to. This effect can be permanent or temporary.",
    "Empathy":
        "Ability to read or sense the emotions or control the emotions or feelings of others",
    "Hydrokinesis":
        "Ability to control, generate or absorb water and moisture.",
    "Magnetism manipulation": "Ability to control or generate magnetic fields",
    "Energy sourcing":
        "Ability to draw power from large or small but abundant sources of energy, such as turning kinetic energy into physical blasts or converting solar energy into other forms.",
    "Psychic surgery":
        "The ability to remove disease or disorder within or over the body tissue via an energetic incision that heals immediately afterwards.",
    "Portal creation":
        "Ability to create wormholes, portation discs or other spatial portals for transport between two non-adjacent locations",
    "Echolocation":
        "Ability to determine location of objects in the environment by use of reflected sound waves, whether generated by the character or ambient sound. Also known as sonar or radar sense.",
    "Pyrokinesis": "The ability to manipulate fire.",
    "Firebreathing":
        "Ability to generate gases from the body and exhale fire from the mouth.",
    "Elemental transmutation":
        "The ability to alter chemical elements, changing them from one substance to another by rearranging the atomic structure. May be limited to self-transmutation",
    "Divination":
        "The ability to gain insight into a situation by way of an occultic standardized process.",
    "Psychometry":
        "Ability to relate details about the past or future condition of an object or location, usually by being in close contact with it",
    "Energy blasts": "Ability to expel various forms of energy from the body",
    "Merging":
        "Ability to temporarily merge two or more beings into a single being, which results in a completely new and stronger being.",
    "Invulnerability":
        "Ability to be immune to one or more forms of physical, mental, and spiritual damage and influence.",
    "Cross-dimensional awareness":
        "Ability to detect actions and events in other dimensions. This is occasionally used in comics as an awareness of the fourth wall between the characters and the artist or audience.",
    "Inorganic":
        "Ability to transform completely into an inorganic substance while retaining organic properties",
    "Waterbreathing":
        "Ability to respirate through water in lieu of a gaseous medium. Not to be confused with an ability to go without breathing or to be able to breathe an alternative air supply.",
    "Power bestowal": "Ability to bestow powers or jump-start latent powers.",
    "Telesthesia":
        "The ability to see a distant and unseen target using extrasensory perception.",
    "Claircognizance":
        "The ability to acquire psychic knowledge by means of intrinsic knowledge.",
    "X-ray vision": "Ability to see through solid matter",
    "Superhuman strength":
        "Ability to have a level of strength much higher than normally possible given their proportions.",
    "Aura reading":
        "The ability to perceive energy fields surrounding people, places and things.",
    "Air and wind manipulation":
        "Ability to control, generate, or absorb air or wind",
    "Biological manipulation":
        "Ability to control all aspects of a living creature's biological make-up. This includes, but is not limited to, genetic alterations, physical distortion/augmentations, healing, disease, and biological functions.",
    "Microwave manipulation":
        "The ability to convert ambient electromagnetic energy into microwaves and manipulate it into various effects such as heat, light, and radiation",
    "Psionic blast":
        "Ability to overload another's mind causing pain, memory loss, lack of consciousness, vegetative state or death after having created a psionic link into that individual's mind",
    "Possession":
        "Ability to take control and inhabit the body of an individual",
    "Dimensional travel":
        "Ability to travel between two or more dimensions, realities, realms, etc.",
    "Night vision": "The ability to see clearly in total darkness",
    "Liquification": "Ability to turn partially or completely into a liquid",
    "Poison generation":
        "Ability to assault others with one or more varieties of toxins, with widely disparate effects.",
    "Superhuman durability / endurance":
        "Ability to have a higher resistance to one or more forms of damage before being injured as well as the ability to exert oneself in an activity indefinitely without becoming tired or survive for long periods of time without consumption or water.",
    "Superhuman senses":
        "Ability to see, smell, taste, feel or hear more than a normal human.",
    "Animal morphing":
        "Ability to take on animal forms. May be able to take on the abilities of the altered form",
    "Molecular manipulation":
        "Ability to mentally manipulate the molecules of objects or one's self on a molecular level",
    "Animation":
        "Ability to bring inanimate objects to life or to free an individual from petrification",
    "Electrical transportation":
        "Ability to travel through electrical conduits (such as power lines or telephone lines). Can enter through devices such as televisions, electrical poles or computers",
    "Levitation":
        "The ability to undergo bodily uplift or fly by mystical means.",
    "Omnipresence":
        "Ability to be present anywhere and everywhere simultaneously",
    "Elasticity":
        "Ability to stretch, deform, expand or contract one's body into any form imaginable",
    "Scrying":
        "The ability to look into a suitable medium with a view to detect significant information.",
    "Ecological empathy":
        "Ability to sense the overall well-being and conditions of one's immediate environment and natural setting stemming from a psychic sensitivity to nature",
    "Dowsing":
        "The ability to locate water, sometimes using a tool called a dowsing rod.",
    "Self-detonation or explosion and reformation":
        "Ability to explode one's body mass and reform.",
    "Essokinesis":
        "Ability to create, alter, or even destroy reality and the laws it is bound by with the power of the mind.",
    "Prehensile/animated hair": "Ability to animate and lengthen one's hair.",
    "Memory manipulation":
        "Ability to erase or enhance the memories of another",
  };

ListView.builder()

これで、Mapがあれば、body部分に超能力のリストを作成できるようになりました。
そのためには ListView.builder() が必要です。

その中に ListTile を作成します。ここで、 title説明 で、サブタイトルは 超能力 です。

search_bar.dart
body:ListView.builder(
                  itemCount: powerDetail.length,
                  itemBuilder: ((context, index) {
                    var key = powerDetail.keys.elementAt(index);
                    return ListTile(
                        leading: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            crossAxisAlignment: CrossAxisAlignment.center,
                            children: <Widget>[Text((index + 1).toString())]),
                        title: Text(
                          "${powerDetail[key]}",
                          softWrap: true,
                          style: TextStyle(color: Colors.green, fontSize: 15),
                        ),
                        subtitle: Text(key));
                  }),
                )

変数 key のおかげで、コードが読みやすくなりました。

これで、powerDetail変数に、持っている全ての超能力のリストが表示されるようになりました。

検索機能

マップのインデックスに対応する検索結果番号のリストが必要です。

そして、powerDetail.keysに検索された値が含まれていれば、そのインデックスをリストに追加します。

search_bar.dart
          for (int i = 0; i < powerDetail.length; i++) {
            if (powerDetail.keys
                .elementAt(i)
                .toLowerCase()
                .contains(value.toLowerCase())) {
              searchList.add(i);
            }
          }

この関数を searchTextField() Widget に追加する必要があります。
動的に検索したいので、このメソッドを onChange 関数に実装する必要があります。

search_bar.dart
Widget searchTextField() {
    return TextField(
      controller: txtController,
+      onChanged: (String value) {
        setState(() {
          searchList = [];
          for (int i = 0; i < powerDetail.length; i++) {
            if (powerDetail.keys
                .elementAt(i)
                .toLowerCase()
                .contains(value.toLowerCase())) {
              searchList.add(i);
            }
          }
        });
      },
      autofocus: true, //TextFieldの表示時にキーボードを表示する
      cursorColor: Colors.white,
      style: const TextStyle(
        color: Colors.white,
        fontSize: 20,
      ),
      textInputAction:
          TextInputAction.search, //キーボードのアクションボタンを指定する
      decoration: const InputDecoration(
        //TextFieldのスタイル
        enabledBorder: UnderlineInputBorder(
            //extFieldのデフォルトの境界線
            borderSide: BorderSide(color: Colors.white)),
        focusedBorder: UnderlineInputBorder(
            //TextFieldにフォーカスがあるときのボーダーライン
            borderSide: BorderSide(color: Colors.white)),
        hintText: '検索', //何も入力されていない時に表示されるテキスト。
        hintStyle: TextStyle(
          //hintTextのスタイル
          color: Colors.white60,
          fontSize: 20,
        ),
      ),
    );
  }

検索結果が重複しないように、変更するたびにListを空にする必要があります。

リストビューの検索

このステップでは、検索機能を追加し、結果を画面に表示することにします。

このウィジェットは、フィルタリングされた結果を表示するウィジェットです。
先ほど作成した ListView を改良したものになります。
しかし、ここではインデックスを「override」します。

search_bar.dart
  Widget searchListView() {
    //add
    return ListView.builder(
        itemCount: searchList.length,
        itemBuilder: (context, index) {
          index = searchList[index];
          var key = powerDetail.keys.elementAt(index);
          return ListTile(
              leading: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[Text((index + 1).toString())]),
              title: Text(
                "${powerDetail[key]}",
                softWrap: true,
                style: TextStyle(color: Colors.green, fontSize: 15),
              ),
              subtitle: Text(key));
        });
  }

そして、それをビルドメソッドに実装する必要があります。
ここでも 条件演算子 が必要になります。

search_bar.dart
body: wasTapped
              ? searchListView()
              : ListView.builder(
                  itemCount: powerDetail.length,
                  itemBuilder: ((context, index) {
                    var key = powerDetail.keys.elementAt(index);
                    return ListTile(
                        leading: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            crossAxisAlignment: CrossAxisAlignment.center,
                            children: <Widget>[Text((index + 1).toString())]),
                        title: Text(
                          "${powerDetail[key]}",
                          softWrap: true,
                          style: TextStyle(color: Colors.green, fontSize: 15),
                        ),
                        subtitle: Text(key));
                  }),
                )

🔎アイコンが押されたら、検索結果のUIが変わり、そうでなければ、全リストが表示されるようになります。

English ver:

Introduction

Today We will make a searching bar with filtering input and showing the result.

Preparation

Let's create the StatefulWidget and call it SearchBar.
So our code should look like this.

search_bar.dart
class SearchBar extends StatefulWidget {
  const SearchBar({Key? key}) : super(key: key);

  
  State<SearchBar> createState() => _SearchBarState();
}

class _SearchBarState extends State<SearchBar> {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(body: Container()),
    );
  }
}

AppBar

When we already finshed creating our Class, add a AppBar() to Scaffold and
add those properties:

  1. title: const Text("Search Bar")
  2. centerTitle: true
  3. actions:[IconButton(icon: const Icon(Icons.search),onPressed: () {})]

So now our build method should like this.

search_bar.dart
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
        appBar: AppBar(
            title: const Text("Search Bar"),
            centerTitle: true,
            actions: [
              IconButton(icon: const Icon(Icons.search), onPressed: () {})
            ]),
        body: Container()),
  );
}

And this is how it should look like.

Icon change

So, we already have a searching icon, but we want to change it to ❌ icon.
To do this, we gonna need variable that will handle onPressed method and another icon.

In _SearchBarState let's create bool wasTapped and assign to it false value.

Now we want to put somewhere our new Icon, and the only proper place to do that is actions in AppBar(). But if we just will put it there, we have two Icons close to each other and this is not what we want.

Thats why we declare wasTapped variable.

We need to work it in this way: Start=> Icons.search =>Press=>Icons.clear=>Press=>Icon.search....

So, We need conditional (ternary) operators

conditional (ternary) operators

condition ? exprIfTrue : exprIfFalse

Let's write it in this way.

search_bar.dart
AppBar(
            title: const Text("Search Bar"),
            centerTitle: true,
            actions: wasTapped
                  ? [
                      IconButton(
                          icon: const Icon(Icons.clear),
                          onPressed: () {})
                    ]
                  : [
                      IconButton(
                          icon: const Icon(Icons.search),
                          onPressed: () {});
                    ]),

What we just wrote is: If wasTapped is true show ❌ Icon, if is false then show 🔎 Icon. But still there is something missing...
Even if we press the icon nothing happen. The reason why is becuase we have to change state of wasTapped every time we press the Icon.

So let's add SetState in onPressed fuction.

search_bar.dart
AppBar(
            title: const Text("Search Bar"),
            centerTitle: true,
            actions: wasTapped
                  ? [
                      IconButton(
                          icon: const Icon(Icons.clear),
                          onPressed: () {
+                           setState(() {
+                              wasTapped = false;
                            });
                          })
                    ]
                  : [
                      IconButton(
                          icon: const Icon(Icons.search),
                          onPressed: () {
+                            setState(() {
+                              wasTapped = true;
                            });
                          })
                    ]),

And finally it works properly. :D

Search Text Field

We already have Icons that change every time they are pressed. Now we want to show a TextField when we press the 🔎 Icon.
So, in _SearchBarState lets create a Widget that will return TextField.

search_bar.dart
  Widget searchTextField() {
    return TextField();
  }

As we know, TextField needs TextEditingController to control our TextField.
So, under the bool wasTapped = false, lets write TextEditingController txtController = TextEditingController();
And lets assign it to controller property in our TextField.

search_bar.dar
bool wasTapped = false
TextEditingController txtController = TextEditingController();

Widget searchTextField() {
    return TextField(controller: txtController);
  }

Hmmm, seems fine. So, let's put our Widget in title property.
Of course, we want to show when we press 🔎 Icon and hide it when we press ❌ Icon.

search_bar.dart
AppBar(
-           title: const Text("Search Bar"),
+           title: wasTapped ? searchTextField() : const Text("Search Bar")
            centerTitle: true,
            actions: wasTapped
                  ? [
                      IconButton(
                          icon: const Icon(Icons.clear),
                          onPressed: () {
                           setState(() {
                              wasTapped = false;
                            });
                          })
                    ]
                  : [
                      IconButton(
                          icon: const Icon(Icons.search),
                          onPressed: () {
                            setState(() {
                              wasTapped = true;
                            });
                          })
                    ]),

It's working but...Yeah, doesn't look "good".
We need add some extra things to our TextField.

search_bar.dart
Widget searchTextField() {
    return TextField(
      controller: txtController,
      autofocus: true, //Display the keyboard when TextField is displayed
      cursorColor: Colors.white,
      style: const TextStyle(
        color: Colors.white,
        fontSize: 20,
      ),
      textInputAction:
          TextInputAction.search, //Specify the action button on the keyboard
      decoration: const InputDecoration(
        //Style of TextField
        enabledBorder: UnderlineInputBorder(
            //Default TextField border
            borderSide: BorderSide(color: Colors.white)),
        focusedBorder: UnderlineInputBorder(
            //Borders when a TextField is in focus
            borderSide: BorderSide(color: Colors.white)),
        hintText: 'Search', //Text that is displayed when nothing is entered.
        hintStyle: TextStyle(
          //Style of hintText
          color: Colors.white60,
          fontSize: 20,
        ),
      ),
    );
  }

Now it looks nice :D

Your code shoold looks like this

search_bar.dart

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

  
  State<SearchBar> createState() => _SearchBarState();
}

class _SearchBarState extends State<SearchBar> {
  bool wasTapped = false;
  TextEditingController txtController = TextEditingController();

  Widget searchTextField() {
    return TextField(
      controller: txtController,
      autofocus: true, //Display the keyboard when TextField is displayed
      cursorColor: Colors.white,
      style: const TextStyle(
        color: Colors.white,
        fontSize: 20,
      ),
      textInputAction:
          TextInputAction.search, //Specify the action button on the keyboard
      decoration: const InputDecoration(
        //Style of TextField
        enabledBorder: UnderlineInputBorder(
            //Default TextField border
            borderSide: BorderSide(color: Colors.white)),
        focusedBorder: UnderlineInputBorder(
            //Borders when a TextField is in focus
            borderSide: BorderSide(color: Colors.white)),
        hintText: 'Search', //Text that is displayed when nothing is entered.
        hintStyle: TextStyle(
          //Style of hintText
          color: Colors.white60,
          fontSize: 20,
        ),
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(
              title: wasTapped ? searchTextField() : const Text("Search Bar"),
              centerTitle: true,
              actions: wasTapped
                  ? [
                      IconButton(
                          icon: const Icon(Icons.clear),
                          onPressed: () {
                            setState(() {
                              wasTapped = false;
                            });
                          })
                    ]
                  : [
                      IconButton(
                          icon: const Icon(Icons.search),
                          onPressed: () {
                            setState(() {
                              wasTapped = true;
                            });
                          })
                    ]),
          body: Container()),
    );
  }
}

Body

When application starts we want to show to user something and when he/she will try to search, application show filtered results.

Collections

We going to need something from collections. Map/List/Array...whatever is fine.
But in our application we going to use Map<String,String>. And we going to store there superpowers names and their descriptions.

In _SearchBarState let's create this Map<String, String> powerDetail variable.

search_bar.dart
Map<String, String> powerDetail = {
    "Reactive adaptation/evolution":
        "Ability to develop a resistance or immunity to whatever they were injured by or exposed to. This effect can be permanent or temporary.",
    "Empathy":
        "Ability to read or sense the emotions or control the emotions or feelings of others",
    "Hydrokinesis":
        "Ability to control, generate or absorb water and moisture.",
    "Magnetism manipulation": "Ability to control or generate magnetic fields",
    "Energy sourcing":
        "Ability to draw power from large or small but abundant sources of energy, such as turning kinetic energy into physical blasts or converting solar energy into other forms.",
    "Psychic surgery":
        "The ability to remove disease or disorder within or over the body tissue via an energetic incision that heals immediately afterwards.",
    "Portal creation":
        "Ability to create wormholes, portation discs or other spatial portals for transport between two non-adjacent locations",
    "Echolocation":
        "Ability to determine location of objects in the environment by use of reflected sound waves, whether generated by the character or ambient sound. Also known as sonar or radar sense.",
    "Pyrokinesis": "The ability to manipulate fire.",
    "Firebreathing":
        "Ability to generate gases from the body and exhale fire from the mouth.",
    "Elemental transmutation":
        "The ability to alter chemical elements, changing them from one substance to another by rearranging the atomic structure. May be limited to self-transmutation",
    "Divination":
        "The ability to gain insight into a situation by way of an occultic standardized process.",
    "Psychometry":
        "Ability to relate details about the past or future condition of an object or location, usually by being in close contact with it",
    "Energy blasts": "Ability to expel various forms of energy from the body",
    "Merging":
        "Ability to temporarily merge two or more beings into a single being, which results in a completely new and stronger being.",
    "Invulnerability":
        "Ability to be immune to one or more forms of physical, mental, and spiritual damage and influence.",
    "Cross-dimensional awareness":
        "Ability to detect actions and events in other dimensions. This is occasionally used in comics as an awareness of the fourth wall between the characters and the artist or audience.",
    "Inorganic":
        "Ability to transform completely into an inorganic substance while retaining organic properties",
    "Waterbreathing":
        "Ability to respirate through water in lieu of a gaseous medium. Not to be confused with an ability to go without breathing or to be able to breathe an alternative air supply.",
    "Power bestowal": "Ability to bestow powers or jump-start latent powers.",
    "Telesthesia":
        "The ability to see a distant and unseen target using extrasensory perception.",
    "Claircognizance":
        "The ability to acquire psychic knowledge by means of intrinsic knowledge.",
    "X-ray vision": "Ability to see through solid matter",
    "Superhuman strength":
        "Ability to have a level of strength much higher than normally possible given their proportions.",
    "Aura reading":
        "The ability to perceive energy fields surrounding people, places and things.",
    "Air and wind manipulation":
        "Ability to control, generate, or absorb air or wind",
    "Biological manipulation":
        "Ability to control all aspects of a living creature's biological make-up. This includes, but is not limited to, genetic alterations, physical distortion/augmentations, healing, disease, and biological functions.",
    "Microwave manipulation":
        "The ability to convert ambient electromagnetic energy into microwaves and manipulate it into various effects such as heat, light, and radiation",
    "Psionic blast":
        "Ability to overload another's mind causing pain, memory loss, lack of consciousness, vegetative state or death after having created a psionic link into that individual's mind",
    "Possession":
        "Ability to take control and inhabit the body of an individual",
    "Dimensional travel":
        "Ability to travel between two or more dimensions, realities, realms, etc.",
    "Night vision": "The ability to see clearly in total darkness",
    "Liquification": "Ability to turn partially or completely into a liquid",
    "Poison generation":
        "Ability to assault others with one or more varieties of toxins, with widely disparate effects.",
    "Superhuman durability / endurance":
        "Ability to have a higher resistance to one or more forms of damage before being injured as well as the ability to exert oneself in an activity indefinitely without becoming tired or survive for long periods of time without consumption or water.",
    "Superhuman senses":
        "Ability to see, smell, taste, feel or hear more than a normal human.",
    "Animal morphing":
        "Ability to take on animal forms. May be able to take on the abilities of the altered form",
    "Molecular manipulation":
        "Ability to mentally manipulate the molecules of objects or one's self on a molecular level",
    "Animation":
        "Ability to bring inanimate objects to life or to free an individual from petrification",
    "Electrical transportation":
        "Ability to travel through electrical conduits (such as power lines or telephone lines). Can enter through devices such as televisions, electrical poles or computers",
    "Levitation":
        "The ability to undergo bodily uplift or fly by mystical means.",
    "Omnipresence":
        "Ability to be present anywhere and everywhere simultaneously",
    "Elasticity":
        "Ability to stretch, deform, expand or contract one's body into any form imaginable",
    "Scrying":
        "The ability to look into a suitable medium with a view to detect significant information.",
    "Ecological empathy":
        "Ability to sense the overall well-being and conditions of one's immediate environment and natural setting stemming from a psychic sensitivity to nature",
    "Dowsing":
        "The ability to locate water, sometimes using a tool called a dowsing rod.",
    "Self-detonation or explosion and reformation":
        "Ability to explode one's body mass and reform.",
    "Essokinesis":
        "Ability to create, alter, or even destroy reality and the laws it is bound by with the power of the mind.",
    "Prehensile/animated hair": "Ability to animate and lengthen one's hair.",
    "Memory manipulation":
        "Ability to erase or enhance the memories of another",
  };

ListView.builder()

So now, when we have a Map we can create list of superpowers in body part.
For that we will need ListView.builder().

And inside of it let's create ListTile, where title is description and subtitle is superpower.

search_bar.dart
body:ListView.builder(
                  itemCount: powerDetail.length,
                  itemBuilder: ((context, index) {
                    var key = powerDetail.keys.elementAt(index);
                    return ListTile(
                        leading: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            crossAxisAlignment: CrossAxisAlignment.center,
                            children: <Widget>[Text((index + 1).toString())]),
                        title: Text(
                          "${powerDetail[key]}",
                          softWrap: true,
                          style: TextStyle(color: Colors.green, fontSize: 15),
                        ),
                        subtitle: Text(key));
                  }),
                )

Thanks to key variable, our code looks more readable.

Now we can see a list of all superpowers we have in our powerDetail variable.

Searching function

We will need a list of search result numbers that correspond to the indexes of our map.
In _SearchBarState lets create List<int> searchList = [];

And if powerDetail.keys containes searched value, we will add it index to our list.

search_bar.dart
          for (int i = 0; i < powerDetail.length; i++) {
            if (powerDetail.keys
                .elementAt(i)
                .toLowerCase()
                .contains(value.toLowerCase())) {
              searchList.add(i);
            }
          }

We need to add this this function to our searchTextField() Widget.
We want to search dynamically, so we have to implement this method to onChange function.

search_bar.dart
 Widget searchTextField() {
    return TextField(
      controller: txtController,
+      onChanged: (String value) {
        setState(() {
          searchList = [];
          for (int i = 0; i < powerDetail.length; i++) {
            if (powerDetail.keys
                .elementAt(i)
                .toLowerCase()
                .contains(value.toLowerCase())) {
              searchList.add(i);
            }
          }
        });
      },
      autofocus: true, //Display the keyboard when TextField is displayed
      cursorColor: Colors.white,
      style: const TextStyle(
        color: Colors.white,
        fontSize: 20,
      ),
      textInputAction:
          TextInputAction.search, //Specify the action button on the keyboard
      decoration: const InputDecoration(
        //Style of TextField
        enabledBorder: UnderlineInputBorder(
            //Default TextField border
            borderSide: BorderSide(color: Colors.white)),
        focusedBorder: UnderlineInputBorder(
            //Borders when a TextField is in focus
            borderSide: BorderSide(color: Colors.white)),
        hintText: 'Search', //Text that is displayed when nothing is entered.
        hintStyle: TextStyle(
          //Style of hintText
          color: Colors.white60,
          fontSize: 20,
        ),
      ),
    );
  }

We don't want to have duplicated results so we have to make our List empty on every change.

Search List View

In this step we will add searching function and show results on screen.

Let's create another Widget, but this widget will show us filtered results.
It's going to be a modified version of ListView we create before.
But here we will "override" our index.

search_bar.dart
  Widget searchListView() {
    //add
    return ListView.builder(
        itemCount: searchList.length,
        itemBuilder: (context, index) {
          index = searchList[index];
          var key = powerDetail.keys.elementAt(index);
          return ListTile(
              leading: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[Text((index + 1).toString())]),
              title: Text(
                "${powerDetail[key]}",
                softWrap: true,
                style: TextStyle(color: Colors.green, fontSize: 15),
              ),
              subtitle: Text(key));
        });
  }

And now we need to implement it to our build method.
Once again we will need conditional operators.

search_bar.dart
body: wasTapped
              ? searchListView()
              : ListView.builder(
                  itemCount: powerDetail.length,
                  itemBuilder: ((context, index) {
                    var key = powerDetail.keys.elementAt(index);
                    return ListTile(
                        leading: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            crossAxisAlignment: CrossAxisAlignment.center,
                            children: <Widget>[Text((index + 1).toString())]),
                        title: Text(
                          "${powerDetail[key]}",
                          softWrap: true,
                          style: TextStyle(color: Colors.green, fontSize: 15),
                        ),
                        subtitle: Text(key));
                  }),
                )

So, if 🔎 Icon is pressed, our UI change for searching result, otherwise it's a whole list.

Discussion