APIから文字列を取得してウィジェットへ表示したときにその文字列の中に**https://**が含まれていた場合、マッチしたテキスト部分のみタップイベント発火させてブラウザアプリケーションを起動してリンクさせたい
そこで!その処理をどこでも利用できるようなStatefulWidgetを作成してみました。
利用したパッケージ
- url_launcher
import 'package:flutter/gestures.dart';
import 'package:url_launcher/url_launcher.dart';
class LinkTextAtoms extends StatefulWidget {
LinkTextAtoms(this.text, {this.linkStyle, this.textStyle});
final String text;
final TextStyle linkStyle;
final TextStyle textStyle;
@override
createState() => LinkTextAtomsState();
}
class LinkTextAtomsState extends State<LinkTextAtoms> {
List<GestureRecognizer> recList = List<GestureRecognizer>();
@override
Widget build(BuildContext context) {
// 通常のテキストデフォルトスタイル
var text = Theme.of(context).textTheme.bodyText1.merge(widget.textStyle);
// リンク時のテキストデフォルトスタイル
var link = Theme.of(context)
.textTheme
.bodyText1
.merge(TextStyle(
inherit: true,
color: Colors.blue,
decoration: TextDecoration.underline))
.merge(widget.linkStyle);
List children = List<TextSpan>();
List matches = RegExp(r'https?://[a-zA-Z0-9\-%_/=&?.]+')
.allMatches(widget.text)
.toList();
var links = matches.map<TextSpan>((m) {
var rec = TapGestureRecognizer()
..onTap = () async {
// URL を処理できるアプリケーションが存在しない可能性があるためcanLaunchメソッドで処理可能かどうかチェックし、OK の場合のみlaunch()メソッドを呼ぶようにする
if (await canLaunch(m.group(0))) {
await launch(m.group(0), forceSafariVC: false);
}
};
recList.add(rec);
return TextSpan(text: m.group(0), style: link, recognizer: rec);
}).toList();
if (matches.length > 0) {
for (var i = 0; i < matches.length; i++) {
if (i == 0) {
if (matches[i].start != 0) {
children.add(TextSpan(
text: widget.text.substring(0, matches[i].start), style: text));
}
children.add(links[i]);
continue;
}
children.add(TextSpan(
text: widget.text.substring(matches[i - 1].end, matches[i].start),
style: text));
children.add(links[i]);
}
if (matches.last.end != widget.text.length) {
children.add(TextSpan(
text: widget.text.substring(matches.last.end), style: text));
}
} else {
children.add(TextSpan(text: widget.text, style: text));
}
return RichText(text: TextSpan(children: children));
}
@override
dispose() {
for (var i = recList.length - 1; i >= 0; i--) {
recList.removeAt(i);
}
super.dispose();
}
}
テキストの一部の**色を変えたい、リンクにしたい、イベント発火させたい、**ので、TextSpanウィジェットを利用し、通常テキストのデフォルトスタイルとリンクテキストだった場合のテキストスタイルを振り分けています。
リンクテキストだった場合はテキストの文字を青くし、アンダーラインを引いています。ここのスタイルはお好みに合わせて変更してしまってよいです。
TextSpanのrecognizerプロパティでタップを検出できるよう設定し、url_launcherパッケージで外部ブラウザを起動します。
...コード量長いし力技感いなめない。
利用方法
// ↑上で作成したファイルをimportする
import 'package:<プロジェクト名>/widgets/components/atoms/link_text_atoms.dart';
class SampleClass extends StatelessWidget {
SampleClass({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: LinkTextAtoms(
'このテキストはリンクですhttps://www.google.com/このテキストはリンクです')));
}
}
関係ないけどproviderの作者がhooksパッケージ出してたの最近気が付きました。。
https://github.com/rrousselGit/flutter_hooks
新しい状態管理パッケージのRiverpodと併用してうまく使えていければと思います
早くこのあたりのベストプラクティスが決まってくれると嬉しいなと思う今日この頃でございます
たまにFlutter(Dart)関係のことつぶやいていたりするので、@amitatantanをフォローしてくれると嬉しいです。