LoginSignup
3
0

More than 1 year has passed since last update.

【Flutter】テキスト中のURLに自動でリンクをはる方法

Last updated at Posted at 2023-04-27

はじめに

テキスト中のURL部分を自動でハイパーリンク化する方法をご紹介します。

目標

テキスト中のURL部分に自動でリンクをはる。
URL部分をタップすると、URLが示すWebページに遷移する。

実装

FlutterにおけるTextウィジェットのソースコードは次のようになっており、TextSpanウィジェットから構成されていることがわかります。

Widget result = RichText(
  ...
  text: TextSpan(
  style: effectiveTextStyle,
  text: data,
  children: textSpan != null ? <InlineSpan>[textSpan!] : null,
  ),
);

ここで、TextSpanにはユーザによる操作に対する処理を登録するrecognizerというプロパティがあります。

したがって、今回の目標である「URL部分をタップしたらリンク先に遷移する」という機能は、TextSpanにタップ時の処理を設定することで実現できます。

URLをタップするとリンク先に遷移する処理の実装

 タップ時の処理については
TextSpan
recognizerプロパティを設定することで実現できます。
指定したURLに遷移する機能はurl_launcherというパッケージを使用して実現します。

仮にURL文字列をurlTextとすると、リンク先への遷移処理は次のようになります(コードサンプルでは外部ブラウザを使用するよう指定しています)。

await launchUrlString(_encadedUrl,mode: LaunchMode.externalApplication);

またTextSpan
recognizerにタップされた時の処理を実装するには、TapGestureRecognizerを使用します。

したがって、テキストをタップした際、そのテキストの文字列が示すWebページに遷移するには、次のような実装を行う必要があります。

final _recognizer = TapGestureRecognizer()
  ..onTap = () async {
  await launchUrlString(_encadedUrl,mode: LaunchMode.externalApplication);
  };
final _textSpan = TextSpan(
  text:  'urlText',
  recognizer:  _recognizer,
);

URLのエンコード処理を追加した結果は次のようになります。

final _encadedUrl = Uri.encodeFull('urlText');
final _recognizer = TapGestureRecognizer()
  ..onTap = () async {
  await launchUrlString(_encadedUrl,mode: LaunchMode.externalApplication);
  };
final _textSpan = TextSpan(
  text:  'urlText',
  recognizer:  _recognizer,
);

これで urlTextという文字列をタップしたときに、 urlTextというURLが表すリンク先に飛ぶ処理が実装できました。

テキストからURL部分を自動で抽出する処理の実装

任意のテキスト中のURL部分を自動でハイパーリンク化するためには、テキストからURL部分を抽出する必要があります。

URLの正規表現を仮に次の様に設定します(実装の目的によって自由に変えて構いません)。

static RegExp get _urlRegExp => RegExp(
  r"(http(s)?:\/\/[a-zA-Z0-9-.!'()*;/?:@&=+$,%_#]+)",
  caseSensitive: false,
);

この正規表現を使って、正規表現にマッチする部分はリンクとして、マッチしない部分はノーマルなテキストとしてTextSpanを生成し、最終的にはTextウィジェットの形にすることを目指します。

文字列が与えられた時に前述のrecognizerをセットしたTextSpanを返却するメソッドgenerateLinkTextSpanを以下の様に定義します。

TextSpan generateLinkTextSpan(String url){
  final _encadedUrl = Uri.encodeFull(url);
  final _recognizer = TapGestureRecognizer()
    ..onTap = () async {
    await launchUrlString(_encadedUrl,mode: LaunchMode.externalApplication);
  };
  final _textSpan = TextSpan(
    text:  url,
    recognizer:  _recognizer,
    //style: リンク部分のテキストスタイル
  );
  return _textSpan;
}

また、URLでない文字列が与えられたときにノーマルなテキストのTextSpanを返却するメソッドgenerateCommonTextSpanを次のように定義します。

TextSpan generateLinkTextSpan(String url){
  final _textSpan = TextSpan(
    text:  url,
  );
  return _textSpan;
}

任意のテキストを正規表現にマッチする部分と、マッチしない部分に分割する処理はsplitMapJoinを利用することで、次のようにかけます。

this._text_.splitMapJoin(RegExp('<正規表現パターン>'),

  //マッチした部分
  onMatch: (Match match) {
    //match.group[0]でパターン一致全体の文字列を取得できる
    return ''; //String型をreturnする必要があるため
  },

  //マッチしなかった部分
  onNonMatch: (String text) {
    //マッチしなかった文字列に対する処理
    return '';
  }
);

よって任意のテキストを正規表現にしたがってURL部分とそれ以外の部分に分け、URLにはタップしたらWeb遷移するような機能を持ったTextSpanを返却するメソッドgenerateは次のようになります。

//実際には引数_rawTextはコンストラクタで与えるが、簡単のため以下とする
TextSpan generate(Text _rawText) {
  final List<TextSpan> _textSpans = [];
  _rawText.splitMapJoin(
    ThisIsAutoLinkTextSpan._urlRegExp,
    onMatch: (Match match) {
      final _urlSpan = this._generateUrlTextSpan(match.group(0) ?? '');
      _textSpans.add(_urlSpan);
      return '';
    },
    onNonMatch: (String text) {
      final _commonSpan = this._generateCommonTextSpan(text);
      _textSpans.add(_commonSpan);
      return '';
    },
  );
  return TextSpan(children: _textSpans);
}

こちらのメソッドを以下のようにText.richウィジェット内で利用すると、任意の文字列のURL部分に自動でハイパーリンク機能を持たせることができます。
(ここではgenerateが定義されるクラスを仮にAutoLinkTextとします。)

Text.rich(
  AutoLinkText.generate(rawText),
)

おわりに

テキスト中のURLを自動でハイパーリンク化する方法を紹介しました。
Textウィジェットだけでは対応できないUI表現は、このようにTextSpanをうまく利用することで可能となる場合があります。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0