Flutter SearchViewとListViewのチュートリアル例です。

この作品では、最高で最も簡単なFlutter SearchViewの例を見てみたいと思います。

例1 - カードの検索フィルターリストビュー

flutterでサーチビューを使って、リストビューを検索/フィルタリングする方法を見てみましょう。いわゆるサーチビューとは、実際にはカスタムテキストフィールドのことです。TextEditingControllerを使って、テキストフィールドのテキストが変更されたことを通知します。これで簡単にデータの検索ができるようになります。


Flutter SearchView Example

このデータは実際には単純なリストから束ねられています。実際には、2つのリストを管理します。

  1. 最初のリスト - 実際のデータソースです。これは決して変更されません。
  2. 2.第2リスト - フィルターの結果を保持します。例えば、検索して100件のリストから5件の結果が出た場合、その5件の結果をこの第2リストに保持します。

検索自体はとても簡単です。単にstringクラスのcontains()メソッドを使うだけです。ただし、大文字と小文字の一貫性を確保しなければなりません。たとえば、クエリが小文字の場合は、データも小文字にしなければなりません。

デモ

このプロジェクトのデモをご紹介します。


Flutter SearchView Example

ビデオチュートリアル

ビデオチュートリアルをご希望の方は、こちらのYouTubeチャンネルでご覧いただけます。

(a). pubspec.yaml

ここでは、サードパーティのライブラリは使用しません。

代わりに、アプリの名前と説明を記述します。

name: mr_searchview
description: Flutter SearchView example project.

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter

(b). main.dart

ここでは、完全な main.dart コードを紹介します。

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

//Our MyApp class. Represents our application
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "ListView SearchView",
      home: new Home(),
      theme: ThemeData(primaryColor: Colors.orange),
    );
  }
}
//Represents the Homepage widget
class Home extends StatefulWidget {
  //createState() will create the mutable state for this widget at
  //a given location in the tree.
  @override
  _HomeState createState() => _HomeState();
}

//Our Home state, the logic and internal state for a StatefulWidget.
class _HomeState extends State<Home> {
  //A controller for an editable text field.
  //Whenever the user modifies a text field with an associated
  //TextEditingController, the text field updates value and the
  //controller notifies its listeners.
  var _searchview = new TextEditingController();

  bool _firstSearch = true;
  String _query = "";

  List<String> _nebulae;
  List<String> _filterList;

  @override
  void initState() {
    super.initState();
    _nebulae = new List<String>();
    _nebulae = [
      "Orion",
      "Boomerang",
      "Cat's Eye",
      "Pelican",
      "Ghost Head",
      "Witch Head",
      "Snake",
      "Ant",
      "Bernad 68",
      "Flame",
      "Eagle",
      "Horse Head",
      "Elephant's Trunk",
      "Butterfly"
    ];
    _nebulae.sort();
  }

  _HomeState() {
    //Register a closure to be called when the object changes.
    _searchview.addListener(() {
      if (_searchview.text.isEmpty) {
        //Notify the framework that the internal state of this object has changed.
        setState(() {
          _firstSearch = true;
          _query = "";
        });
      } else {
        setState(() {
          _firstSearch = false;
          _query = _searchview.text;
        });
      }
    });
  }

//Build our Home widget
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: new AppBar(
        title: new Text("SearchView ListView"),
      ),
      body: new Container(
        margin: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0),
        child: new Column(
          children: <Widget>[
           _createSearchView(),
            _firstSearch ? _createListView() : _performSearch()
          ],
        ),
      ),
    );
  }
 //Create a SearchView
  Widget _createSearchView() {
    return new Container(
      decoration: BoxDecoration(border: Border.all(width: 1.0)),
      child: new TextField(
        controller: _searchview,
        decoration: InputDecoration(
          hintText: "Search",
          hintStyle: new TextStyle(color: Colors.grey[300]),
        ),
        textAlign: TextAlign.center,
      ),
    );
  }
  //Create a ListView widget
  Widget _createListView() {
    return new Flexible(
      child: new ListView.builder(
          itemCount: _nebulae.length,
          itemBuilder: (BuildContext context, int index) {
            return new Card(
              color: Colors.white,
              elevation: 5.0,
              child: new Container(
                margin: EdgeInsets.all(15.0),
                child: new Text("${_nebulae[index]}"),
              ),
            );
          }),
    );
  }
  //Perform actual search
  Widget _performSearch() {
    _filterList = new List<String>();
    for (int i = 0; i < _nebulae.length; i++) {
      var item = _nebulae[i];

      if (item.toLowerCase().contains(_query.toLowerCase())) {
        _filterList.add(item);
      }
    }
    return _createFilteredListView();
  }
  //Create the Filtered ListView
  Widget _createFilteredListView() {
    return new Flexible(
      child: new ListView.builder(
          itemCount: _filterList.length,
          itemBuilder: (BuildContext context, int index) {
            return new Card(
              color: Colors.white,
              elevation: 5.0,
              child: new Container(
                margin: EdgeInsets.all(15.0),
                child: new Text("${_filterList[index]}"),
              ),
            );
          }),
    );
  }
}

ダウンロードして実行する方法です。

このコードを main.dart ファイルにコピーするだけです。セットアップは必要ありません。

例2 - AppBar/Toolbarからの検索フィルターリストビュー

これは2つ目のflutter searchviewの例です。この例も理解しやすく、Dartプログラミングを使ってflutterに検索フィルターを実装するのに役立ちます。この例では、セットアップや依存関係も必要ありません。この例では、アプリケーションのツールバーやアプリバーにサーチビューをレンダリングします。

それでは始めましょう。

**デモ

ここにデモがあります。


Flutter SearchView

Flutter AppBar SearchView

(a). search_list.dart

このファイルでは、検索可能なリストビューを表現するためのステートフルなウィジェットを定義します。

インポートの追加

まず、material.dartをインポートします。

import 'package:flutter/material.dart';

**ステートフル・ウィジェットの作成

次に、StatefulWidgetを拡張して、ウィジェットを作成します。

class SearchList extends StatefulWidget {

を拡張し、コンストラクタを定義してウィジェットを作成します。

  SearchList({ Key key }) : super(key: key);

そして、最後にcreateState()メソッドをオーバーライドします。

  @override
  _SearchListState createState() => new _SearchListState();

}

**Stateクラスの作成

まず、Stateクラスを拡張します。

class _SearchListState extends State<SearchList>
{

次にインスタンスフィールドを定義します。

  Widget appBarTitle = new Text("Search Sample", style: new TextStyle(color: Colors.white),);
  Icon actionIcon = new Icon(Icons.search, color: Colors.white,);
  final key = new GlobalKey<ScaffoldState>();
  final TextEditingController _searchQuery = new TextEditingController();
  List<String> _list;
  bool _IsSearching;
  String _searchText = "";

インスタンスフィールドには、appBarのタイトル、アイコン、TextEditingControllerなどが含まれていることがわかります。

コンストラクタでは、TextEditingControllerにリスナーをアタッチします。これにより、ユーザーの入力による編集フィールドのテキスト変更イベントを通知することができます。

  _SearchListState() {
    _searchQuery.addListener(() {
      if (_searchQuery.text.isEmpty) {
        setState(() {
          _IsSearching = false;
          _searchText = "";
        });
      }
      else {
        setState(() {
          _IsSearching = true;
          _searchText = _searchQuery.text;
        });
      }
    });
  }

次に、initState()メソッドをオーバーライドします。

  @override
  void initState() {
    super.initState();
    _IsSearching = false;
    init();

  }

データソースとして動作するアイテムのリストを追加します。

  void init() {
    _list = List();
    _list.add("Google");
    _list.add("IOS");
    _list.add("Andorid");
    _list.add("Dart");
    _list.add("Flutter");
    _list.add("Python");
    _list.add("React");
    _list.add("Xamarin");
    _list.add("Kotlin");
    _list.add("Java");
    _list.add("RxAndroid");
  }

このステートクラスのbuild()メソッドをオーバーライドします。

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      key: key,
      appBar: buildBar(context),
      body: new ListView(
        padding: new EdgeInsets.symmetric(vertical: 8.0),
        children: _IsSearching ? _buildSearchList() : _buildList(),
      ),
    );
  }

これを見ると、appBarとbodyを指定してウィジェットを構築していることがわかります。この場合、bodyにはListViewが含まれます。

そして、データのリストを返す2つのヘルパー・メソッドがあります。

  List<ChildItem> _buildList() {
    return _list.map((contact) => new ChildItem(contact)).toList();
  }

  List<ChildItem> _buildSearchList() {
    if (_searchText.isEmpty) {
      return _list.map((contact) => new ChildItem(contact))
          .toList();
    }
    else {
      List<String> _searchList = List();
      for (int i = 0; i < _list.length; i++) {
        String  name = _list.elementAt(i);
        if (name.toLowerCase().contains(_searchText.toLowerCase())) {
          _searchList.add(name);
        }
      }
      return _searchList.map((contact) => new ChildItem(contact))
          .toList();
    }
  }

次に、AppBarウィジェットの構築を助けるヘルパー・メソッドがあります。

  Widget buildBar(BuildContext context) {
    return new AppBar(
        centerTitle: true,
        title: appBarTitle,
        actions: <Widget>[
          new IconButton(icon: actionIcon, onPressed: () {
            setState(() {
              if (this.actionIcon.icon == Icons.search) {
                this.actionIcon = new Icon(Icons.close, color: Colors.white,);
                this.appBarTitle = new TextField(
                  controller: _searchQuery,
                  style: new TextStyle(
                    color: Colors.white,

                  ),
                  decoration: new InputDecoration(
                      prefixIcon: new Icon(Icons.search, color: Colors.white),
                      hintText: "Search...",
                      hintStyle: new TextStyle(color: Colors.white)
                  ),
                );
                _handleSearchStart();
              }
              else {
                _handleSearchEnd();
              }
            });
          },),
        ]
    );
  }

上記のメソッドでわかるように、このAppBarウィジェットにはsearchviewが含まれています。

そして最後に、このステートクラスのために、検索開始と検索終了を処理するメソッドを作成します。

  void _handleSearchStart() {
    setState(() {
      _IsSearching = true;
    });
  }

  void _handleSearchEnd() {
    setState(() {
      this.actionIcon = new Icon(Icons.search, color: Colors.white,);
      this.appBarTitle =
      new Text("Search Sample", style: new TextStyle(color: Colors.white),);
      _IsSearching = false;
      _searchQuery.clear();
    });
  }

}

これでStateクラスは終わりです。

その下には、childItemというクラスを作ります。これもまた、1つのListViewアイテムを表すウィジェットです。


class ChildItem extends StatelessWidget {
  final String name;
  ChildItem(this.name);
  @override
  Widget build(BuildContext context) {
    return new ListTile(title: new Text(this.name));
  }

}

(b). main.dart

mainファイルに以下のコードを追加します。

import 'package:flutter/material.dart';
import 'package:flutter_search_app/search_list.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Search',
      debugShowCheckedModeBanner: false,
      theme: new ThemeData(
        brightness: Brightness.light,
        primarySwatch:Colors.yellow,
        primaryColor: Color(0xFFFFBB54),
        accentColor: Color(0xFFECEFF1),
      ),
      home: new SearchList(),
    );
  }
}

以上で完成です。

Special Thanks to @MageshPandian20 for this wonderful example.

Download Projectをご覧ください。

それでは、良い一日をお過ごしください。

Categorized in: