🤪
タグ入力の自作をする
tagを自作してみた
Flutterの入力フォームで、タグ機能の自作をしようとライブラリの選定をしていたが、自分たちの考えているものと違うものしかなかったので、実験で自作してみた。
こんなものを作った📺
しかし困ったことに、そのままリストを渡すと、Tagのインスタンスを渡しているようで、Swiftとかテキストを渡せていなかった😨
タイトルだけ欲しいので抽出する必要があった。こんな感じでロジックを書いてみた。Listの操作が複雑だったので、Dartのロジックの理解が必要でしたね😅
import 'package:flutter/material.dart';
class TagWidget extends StatelessWidget {
const TagWidget({super.key});
Widget build(BuildContext context) {
return CustomTagInput(
initialTags: [
TagData('Flutter', Colors.blue),
TagData('Dart', Colors.teal),
],
suggestedTags: [
TagData('Swift', Colors.orange),
TagData('Kotlin', Colors.purple),
TagData('React', Colors.blue),
TagData('AWS', Colors.yellow),
TagData('Rails', Colors.red),
TagData('Sigma', Colors.green),
TagData('AdobeXD', Colors.pink),
],
);
}
}
// データモデル
class TagData {
final String tag;
final Color color;
TagData(this.tag, this.color);
}
class CustomTagInput extends StatefulWidget {
final List<TagData> initialTags;
final List<TagData> suggestedTags;
const CustomTagInput({
Key? key,
this.initialTags = const <TagData>[], // この行を修正
this.suggestedTags = const <TagData>[], // この行を修正
}) : super(key: key);
_CustomTagInputState createState() => _CustomTagInputState();
}
class _CustomTagInputState extends State<CustomTagInput> {
final TextEditingController _textEditingController = TextEditingController();
final FocusNode _focusNode = FocusNode();
List<TagData> _tags = [];
void initState() {
super.initState();
_tags = List<TagData>.from(widget.initialTags);
}
void _addTag(String tagText) {
if (tagText.isNotEmpty && !_tags.any((tag) => tag.tag == tagText)) {
setState(() {
_tags.add(TagData(tagText, Colors.grey)); // デフォルトの色を設定
});
_textEditingController.clear();
}
}
void _removeTag(TagData tag) {
setState(() {
_tags.remove(tag);
});
}
void _onSuggestedTagTap(TagData tag) {
if (!_tags.contains(tag)) {
setState(() {
_tags.add(tag);
});
}
}
void dispose() {
_textEditingController.dispose();
_focusNode.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 8,
runSpacing: 4,
children: _tags
.map((tag) => Chip(
label: Text(tag.tag),
backgroundColor: tag.color,
onDeleted: () => _removeTag(tag),
))
.toList(),
),
TextField(
controller: _textEditingController,
focusNode: _focusNode,
decoration: const InputDecoration(
hintText: 'タグを入力してください',
helperText: 'スペースまたはカンマで区切ってタグを追加',
),
onSubmitted: (value) => _addTag(value.trim()),
onChanged: (value) {
if (value.endsWith(' ') || value.endsWith(',')) {
_addTag(value.trim().replaceAll(',', ''));
}
},
),
SizedBox(height: 10),
Wrap(
spacing: 8,
runSpacing: 4,
children: widget.suggestedTags
.map((tag) => ActionChip(
label: Text(tag.tag),
backgroundColor: tag.color,
onPressed: () => _onSuggestedTagTap(tag),
))
.toList(),
),
ElevatedButton(
onPressed: () {
// タグのタイトルだけを抽出して保存
final tagTitles = _tags.map((tag) => tag.tag).toList();
print(tagTitles);
print(tagTitles.runtimeType);
},
child: const Text('Submit'),
),
],
);
}
}
実行するコードはこちら
import 'package:flutter/material.dart';
import 'package:widget_cookbook/tag_widget/tag_widget.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: const MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('My Widget'),
),
body: const Center(
child: TagWidget(),
),
);
}
}
まとめ
この機能を使うかはわからないですが、参考にはなりましたね。いい感じの機能かもですね。
おまけ
flutter_hooks
で書いてみたかったので、試しにやってみました。良いコードかわからないが動いて入る。皆さんもいろんなパターンで、タグを使った機能を自作してみてください。
タグを生成するWidget
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class TagWidget extends HookWidget {
const TagWidget({super.key});
Widget build(BuildContext context) {
final initialTags = useMemoized(() => [
TagData('Flutter', Colors.blue),
TagData('Dart', Colors.teal),
], []);
final suggestedTags = useMemoized(() => [
TagData('Swift', Colors.orange),
TagData('Kotlin', Colors.purple),
TagData('React', Colors.blue),
TagData('AWS', Colors.yellow),
TagData('Rails', Colors.red),
TagData('Sigma', Colors.green),
TagData('AdobeXD', Colors.pink),
], []);
return CustomTagInput(
initialTags: initialTags,
suggestedTags: suggestedTags,
);
}
}
class TagData {
TagData(this.tag, this.color);
final String tag;
final Color color;
}
class CustomTagInput extends HookWidget {
const CustomTagInput({
super.key,
this.initialTags = const <TagData>[],
this.suggestedTags = const <TagData>[],
});
final List<TagData> initialTags;
final List<TagData> suggestedTags;
Widget build(BuildContext context) {
final tags = useState<List<TagData>>(initialTags);
final textEditingController = useTextEditingController();
final focusNode = useFocusNode();
final addTag = useCallback((String tagText) {
if (tagText.isNotEmpty && !tags.value.any((tag) => tag.tag == tagText)) {
tags.value = [...tags.value, TagData(tagText, Colors.grey)];
textEditingController.clear();
}
}, [tags, textEditingController]);
final removeTag = useCallback((TagData tag) {
tags.value = tags.value.where((t) => t != tag).toList();
}, [tags]);
final onSuggestedTagTap = useCallback((TagData tag) {
if (!tags.value.contains(tag)) {
tags.value = [...tags.value, tag];
}
}, [tags]);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 8,
runSpacing: 4,
children: tags.value.map((tag) => Chip(
label: Text(tag.tag),
backgroundColor: tag.color,
onDeleted: () => removeTag(tag),
)).toList(),
),
TextField(
controller: textEditingController,
focusNode: focusNode,
decoration: const InputDecoration(
hintText: 'タグを入力してください',
helperText: 'スペースまたはカンマで区切ってタグを追加',
),
onSubmitted: (value) => addTag(value.trim()),
onChanged: (value) {
if (value.endsWith(' ') || value.endsWith(',')) {
addTag(value.trim().replaceAll(',', ''));
}
},
),
const SizedBox(height: 10),
Wrap(
spacing: 8,
runSpacing: 4,
children: suggestedTags.map((tag) => ActionChip(
label: Text(tag.tag),
backgroundColor: tag.color,
onPressed: () => onSuggestedTagTap(tag),
)).toList(),
),
const SizedBox(height: 10),
ElevatedButton(onPressed: () {
print(tags.value.map((tag) => tag.tag).toList());
}, child: const Text('Submit')),
],
);
}
}
実行するコード
import 'package:flutter/material.dart';
import 'package:widget_cookbook/tag_hook/tag_widget.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: const MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Hook Tag Widget'),
),
body: const Center(
child: TagWidget(),
),
);
}
}
Discussion