Googleの検索窓やECサイトへのログイン時に自動で候補が表示されるオートコンプリート機能って便利ですよね。Flutterにもこれを簡単に実装できるものがないかな〜と思っていた時に丁度Flutter公式YoutubeチャンネルののWidget of the Weekでそれらしきものが紹介されていました。
今回はこちらのWidgetをいじってみたので紹介していきたいと思います。
基本的な使い方
基本的な使い方は下記の通りです。コピペで動きます。
class AutocompleteExample extends StatelessWidget {
const AutocompleteExample({Key? key}) : super(key: key);
static const _sports = <String>[
'football',
'baseball',
'tennis',
'swimming',
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Autocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<String>.empty();
}
return _sports.where((String option) {
return option.contains(textEditingValue.text.toLowerCase());
});
},
onSelected: (String value) {
// 候補選択時に呼び出されます。
print('$valueを選択しました!');
},
),
),
);
}
}
TextFromFieldの代わりにAutocompleteを指定します。optionsBuilder
は現在入力されている値を確認し、一致する文字を含む候補を表示してくれます。onSelected
はユーザーが候補を選択した時に呼び出されます。
また、候補一覧はString型以外にも指定することが可能で、例えばUserというクラスのnameで候補一覧を表示したいといったことも可能です。
class User {
User({
required this.name,
required this.mail,
});
String name;
String mail;
}
class AutocompleteExample extends StatelessWidget {
AutocompleteExample({Key? key}) : super(key: key);
final users = <User>[
User(name: 'Aくん', mail: 'akun@sample.com'),
User(name: 'Bくん', mail: 'bkun@sample.com'),
User(name: 'Cくん', mail: 'ckun@sample.com'),
];
// 何のプロパティを候補に表示するか指定
static String _displayStringForOption(User option) => option.name;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Autocomplete<User>(
displayStringForOption: _displayStringForOption,
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<User>.empty();
}
return users.where((User option) {
return option.name.toString().contains(textEditingValue.text);
});
},
// 選択した値が入る
onSelected: (User user) {
print('${user.name}を選択しました!');
},
),
),
);
}
}
簡単に使えて良いですね!
候補リストをカスタマイズ
optionsViewBuilder
プロパティで候補一覧のスタイルを変更することができます。今回はListView.builder
でリスト形式で表示するように実装してみました。
class AutocompleteExample extends StatelessWidget {
AutocompleteExample({Key? key}) : super(key: key);
final users = <User>[
User(name: 'Aくん', mail: 'akun@sample.com'),
User(name: 'Bくん', mail: 'bkun@sample.com'),
User(name: 'Cくん', mail: 'ckun@sample.com'),
];
static String _displayStringForOption(User option) => option.name;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Autocomplete<User>(
displayStringForOption: _displayStringForOption,
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<User>.empty();
}
return users.where((User option) {
return option.name.toString().contains(textEditingValue.text);
});
},
onSelected: (User user) {
print('${user.name}を選択しました');
},
optionsViewBuilder: (context, onSelected, users) {
return ListView.builder(
itemCount: users.length,
itemBuilder: ((context, index) {
// iterable to list
final userList = users.toList();
return MyWidget(userList[index]);
}),
);
}),
),
);
}
}
class MyWidget extends StatelessWidget {
MyWidget(this.user);
User user;
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
title: Text(user.name),
subtitle: Text(user.mail),
),
);
}
}
フォームをカスタマイズ
fieldViewBuilder
プロパティでフォームのスタイルを変更することができます(スタイルだけというか、通常のTextFormFiledと同じようなことがここで指定できます)
class AutocompleteExample extends StatelessWidget {
const AutocompleteExample({Key? key}) : super(key: key);
static const _sports = <String>[
'football',
'baseball',
'tennis',
'swimming',
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Autocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) {
// 未入力の場合は自動補完を走らせない
if (textEditingValue.text == '') {
return const Iterable<String>.empty();
}
return _sports.where((String option) {
return option.contains(textEditingValue.text.toLowerCase());
});
},
fieldViewBuilder:
(context, textEditingController, focusNode, onFieldSubmitted) {
return TextFormField(
controller: textEditingController,
focusNode: focusNode,
onFieldSubmitted: (String value) {
onFieldSubmitted();
},
decoration: InputDecoration(
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(
color: Colors.green,
width: 2.0,
),
),
labelStyle: TextStyle(
fontSize: 12,
color: Colors.green[300],
),
labelText: 'フレームあり、ラベルあり',
floatingLabelStyle: const TextStyle(fontSize: 12),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(
color: Colors.green[100]!,
width: 1.0,
),
),
),
);
},
// 選択した値が入る
onSelected: (String selection) {
print('$selectionを選択しました!');
},
),
),
);
}
}
活用例
SharedPreferenceなどのパッケージを活用してユーザーのメールアドレスを永続化しておけば、次回ユーザーがログインする時の負担を減らすことができます(ECサイトとかでよく見るやつです)
class AutocompleteSample extends StatefulWidget {
const AutocompleteSample({Key? key}) : super(key: key);
@override
State<AutocompleteSample> createState() => _AutocompleteSampleState();
}
class _AutocompleteSampleState extends State<AutocompleteSample> {
List<String> mails = [];
Future<void> getMail() async {
final prefs = await SharedPreferences.getInstance();
final mail = prefs.getString('mail');
if (mail != null) {
mails.add(mail);
}
}
@override
void initState() {
super.initState();
getMail();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Autocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<String>.empty();
}
return mails.where((String option) {
return option.contains(textEditingValue.text.toLowerCase());
});
},
onSelected: (String selection) {
print('$selectionを選択しました!');
},
),
),
);
}
}
自分で検索ロジックを書く必要がないので検索フォームの実装の負担がかなり減るなと思いました!みなさんもぜひ使ってみてください!
参考