【Dart/Flutter開発】検索エンジンでありがちな、単語/言葉の検索機能を実装する、話
こんにちは、たちつてとです。
↓過去記事では(年月やカテゴリIDなど)
「完全一致」に関する検索機能を
実装しました。
【Dart/Flutter開発】表示制御と検索機能の実装、実際の(自作)アプリと考え方を共に...【スマホアプリ】
が、Googleやyahooで見かけるような
単語を入力→一致する項目を表示する
「部分一致」の検索機能は
実装したことがありませんでした。
「あい」で検索すると
- あかさたな
- あいうえお←Hit!
- ゆがんだあい←Hit!
- たあいのない話←Hit!
- あばばばば
とこんな感じの検索機能の話です。
メモアプリを作っているんですが、
それで上記のような単語検索機能を
組みたいなと思い、記事にする次第です。
完成イメージ
実装イメージ、シミュレーターのキーボードの都合で
「aiu」で検索...
作戦
基本的な作戦は、
【Dart/Flutter開発】表示制御と検索機能の実装、実際の(自作)アプリと考え方を共に...【スマホアプリ】
こちらの記事と同じものでいきます。
(※前提:一覧画面があってその項目を絞り込みたい。)
言葉にすると、
- テキスト入力がされた時に
- その言葉を含む項目「だけ」を
- 一覧画面にセットしたい
です。
ですんで、より具体的には
- TextFieldのChangeイベントで
- TextFieldに入力された内容を含む項目を、if分岐しつつList変数にぶちこみ
- ListView.builderのitemBuilderに渡す
となります。
ここで厄介なのが、キーワードを含む
なんですが、割と余裕?というか
プログラムにして一文でクリアできたりします。
正規表現
これタイトルに入れると、アクセス数少なくなりそうで
入れませんでした。
ブラウザバックはしないでください...。
正直、僕も100%は使いこなせません。
業務の中で必要に応じて、必要な記述を勉強してくって感じで。
はい、結論から。
for (Memo memo in mesBox.values) {
if(memo.categoryCd == _categoryCd) {
if(new RegExp(r'.*' + _searchWord +'.*').hasMatch(memo.title)) {//ここが大事
_memos.add(memo);
}
}
}
RegExpが最も大事な記述です。
RegexExpression(のはず...)が正規表現と呼ばれるものです。
は?って方もいっぺんコピペして
動かしてみてください。
何かしらのパターンで文字検索したい時に
よく利用されます。
ほんの少し解説すると、
「.」:任意の一文字
「*」:0回以上の繰り返し
という意味になります。0回以上ってなんやねんて方。
うーん、あってもなくてもいいって感じ?
この解説に長くなるのは本意ではないので、
ここまでにします。
要は
「.*」任意の文字の繰り返しが0回以上ある
→
任意の文字列(あってもなくてもいい)
+
検索ワード
+
任意の文字列(あってもなくてもいい)
という形式でパターン一致をみます。
先の例を繰り返し書くと
「あい」で検索すると
- あかさたな
- あいうえお←Hit!
- ゆがんだあい←Hit!
- たあいのない話←Hit!
- あばばばば
2番目のは、「あい」 + 任意の文字列(以下、任)
3番目のは、任 + 「あい」
4番目のは、任 + 「あい」 + 任
でヒット!となります。
2,3番目は、任意の文字列があってもなくてもいい
ということに注意です。
最後に、
メソッドhasMatchはこのパターンに一致したら
trueという感覚的にわかるものです。
このように、正規表現によって
検索機能が実現できます。
補足
一応補足ですが、
「アイ」で検索すると
- あかさたな
- あいうえお
- ゆがんだあい
- たあいのない話
- あばばばば
はヒットしませんし、
「愛」で検索しても
- あかさたな
- あいうえお
- ゆがんだあい
- たあいのない話
- あばばばば
ヒットしません。
ヒットしません...
ヒットしないのです...
検索ワードとパターン内の文字のひらがな・カタカナ・漢字は
完全に一致していないといけません。
アルファベットも同様です。
ここを解消しようとすると、
カタカナの例では、
「あい」ときたら
「アイ」、「アい」、「あイ」
も検索ワードに含めるという処理を書くことが必要です。
余談の余談ですが、
これを実装するために、if分岐で||(or)を書かなくても
正規表現の中で「または」と書くことができます。
めんどくさいので、僕はやりませんよ。
あんまり面白そうでもないし...
コード全貌
僕のコード独自のものが入っているので、
コピペしただけでは動きませんが...
黄色のコメントのところがポイントですので
真似するのも難しくはないんじゃないかなと思います。
プロパティ宣言
TextEditingController _cont= new TextEditingController();//補足に詳細
final List<Memo> _memos = [];
static int _categoryCd = -1;
String _searchWord = '';
絞り込み用ロジック
void loadData() async {//テキスト変更イベントで呼び出される、項目絞り込みロジック
Hive.openBox<Memo>(_memo).then((value) {
Box<Memo> mesBox = value;
_memos.clear();
for (Memo memo in mesBox.values) {
if(memo.categoryCd == _categoryCd) {
//項目の絞り込み
if(new RegExp(r'.*' + _searchWord +'.*').hasMatch(memo.title)) {
_memos.add(memo);
}
}
}
setState(() {});//絞り込みを反映するために、再描画。非同期処理の最後に記述。
});
}
検索用テキストボックス
TextField(
controller: _cont,
style: TextStyle(fontSize: 16),
textAlign: TextAlign.left,
onChanged: (word) {//テキスト変更イベント
_searchWord = word;
loadData();// 絞り込み開始
},
decoration: const InputDecoration(
labelText: '',
filled: true,
fillColor:
Color.fromRGBO(255, 255, 255, 1.0),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.black12, width: 1.0),
),
),
)
一覧表示用のロジック
ListView.builder(
// リストを生成
itemBuilder: (context, index) {
final daymemo = _memos[index];// 絞り込まれた項目が表示
return Dismissible(...)
後から気づいたんですが、
変数「_serachWord」は実は不要でした。
TextEditingController(=_cont).textというので
テキスト入力に入っているワードを取得できるので
わざわざプロパティとして準備しなくてよかったです。
補足
コードにあるコメントですが、
TextEditingControllerをあえて、プロパティにしているのは
訳があります。
TextFiled
controller:new TextEditingController
とかくと、(Onchangeイベント等で)
画面が描画されるたびにカーソル位置が
リセットされ、一番左端になってしまいます。
つまり、素朴にaiuと入力しても
(カーソルを|とすると)
- |a
- |ia
- |uia
となるのです。ですので、
いちいちnewしないで、プロパティ化しています。
という具合でキーワード検索を実装しました。
どうでしょうか。正規表現という言葉さえ知っておけば
割と誰でもすぐ実装できます。
正規表現は、
もっといろんな表現で様々なパターンの
文字検索ができます。
正規表現だけで無限に記事が書けそうなくらい。
ま、「含む」くらいの実装なら
今回書いたような内容で十分です。
それ以上のことは、僕は知りません!
|
|