🕌
FlutterのTextFieldにメンション機能を実装する
こんにちは 😁
今回は Flutter の TextField にメンション機能を実装してみました。いくつか気になることはありますがとりあえず公開しようと思います 🎉
こんな感じ
-
@{userId}
のように入力すると、対応するユーザー名に自動変換される - メンションの前でデリートキーを入力すると、メンション全体が削除される
-
+
ボタンを押すとユーザーを追加 - 値の実態は
@{userId}
リポジトリはこちら
何が嬉しいか
- ユーザー名が変更されても変更後のユーザー名を表示できる
- メンションごとに TextStyle を指定できる
どんな時に使うか
ユーザーがユーザー ID を入力することはないと思うので、リプライボタンを押した時に自動でユーザー ID を入力してあげたり、@
を入力した時にユーザー名一覧を表示し選択するとユーザー ID が入力されるように実装することになると思います。
実装
実装するのはTextEditingController
を継承したクラスのみ。あとはそれをTextField
なり使うだけ。
TextEditingController
を継承
class MentionTextEditingController extends TextEditingController {
TextSpan buildTextSpan({
BuildContext? context,
TextStyle? style,
bool? withComposing,
}) {
...
}
}
buildTextSpan
を override し、ユーザー ID をユーザー名に変更
TextSpan buildTextSpan({
BuildContext? context,
TextStyle? style,
bool? withComposing,
}) {
final children = <InlineSpan>[];
text.splitMapJoin(
RegExp(r'@([a-zA-Z0-9_]+)(\u200b*)');, // NOTE: \u200b*については後ほど説明します。
onMatch: (Match match) {
...
children.add(TextSpan(text: fixedText, style: style));
return '';
},
onNonMatch: (String text) {
children.add(TextSpan(text: text, style: style));
return '';
},
);
return TextSpan(style: style, children: children);
}
ユーザー ID とユーザー名の文字列数は違うのでカーソル位置にバグが発生します。それを修正するために以下をします。
- ユーザ ID.length > ユーザー名.length: 実値より表示テキストの方が短いので、表示テキストに
WidgetSpan(child: SizedBox())
(ゼロ幅 Widget)を足りない分だけ足します。 - ユーザー ID.length < ユーザー名.length: 実値より表示されるテキストの方が長いので、実値に
\u200b
(ゼロ幅スペース)を足りない分だけ足します。
以下ちょっとトリッキーなことしていますが、説明が難しいので省略させてください 🙏
コード全体
気になること
- トリッキーな実装のせいでメンションの後に 1 つ見えない文字ができてしまい、左にカーソルを移動すると一度目は動かない
- メンションの途中でデリートキーを押すと、削除のされ方にむらが出る
- 全部削除されたり、途中から前半だけ削除されたり、その逆だったり
- 外部から text プロパティを参照したときに、ゼロ幅スペースをトリムしたい
いつか調査して直したいなと思ますが、今はやる気が出ないので置いておきます。
最後に
もっと簡単な方法があれば教えていただきたいです!
Discussion