Flutterでテキストの一部を簡単に装飾できるパッケージを作成した

# はじめに

こんにちは．AndroidとiOSの両アプリを同じコードで作れるFlutterに惹かれて，ここ1ヶ月くらい色々触ってみました．
レイアウトもDartファイル内に記述できてめちゃくちゃ書きやすいですが，気になる点もいくつかありました．特に不便に感じたのが，以下のようなテキストの一部を装飾することです．

- テキスト内のURLやメールをリンクにする
- テキストの一部をタップした時になにか動作させる（画面遷移させるとか）
- テキストの一部のスタイルを変える（色とか）

Flutterでは，これらのようなテキストの一部に変化をもたせたい場合は，以下のような感じで[RichText](https://api.flutter.dev/flutter/widgets/RichText-class.html)と[InlineSpan](https://api.flutter.dev/flutter/painting/InlineSpan-class.html)で書いていく必要があります．

``` dart
RichText(
  text: TextSpan(
    text: 'Hello ',
    style: DefaultTextStyle.of(context).style,
    children: <TextSpan>[
      TextSpan(text: 'bold', style: TextStyle(fontWeight: FontWeight.bold)),
      TextSpan(text: ' world!'),
    ],
  ),
)
```

簡単なテキストなら良いですが，複雑になってくると結構大変です．

そこで，テキストの一部の装飾を簡単にできるパッケージを作成し，公開したのでその紹介です．

# つくったもの

[decoratable_text](https://pub.dev/packages/decoratable_text)
テキストから任意のパターンにマッチした部分を装飾（タップアクションを追加したり，スタイルを変えたり）します．

## 使い方

### URL部分のスタイルを変えてリンク可能にする

``` dart
const DecoratableText(
  text: "url: https://flutter.dev/",
  decorations: [
    DecorationOption(
      pattern: TextPattern.url,
      style: TextStyle(color: Colors.blue),
      tapAction: TapAction.launchUrl,
      showRipple: true,
    ),
  ],
),
```

`DecorationOption`の`displayText`を指定することで，URL部分の表示を変更させることも可能です．

``` dart
DecorationOption(
  pattern: TextPattern.url,
  displayText: "Tap hare",
  style: TextStyle(color: Colors.blue),
  tapAction: TapAction.launchUrl,
  showRipple: true,
),
```

|`displayText`指定なし|`displayText`指定あり|
|---|---|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261100/abdd24b4-daf7-b4b6-dd8f-2b99ab287676.gif" width="320px">|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261100/7f8c5fdc-ae66-0633-d4ec-508c6369b1b5.gif" width="320px">|

### メール部分のスタイルを変えてリンク可能にする

``` dart
const DecoratableText(
  text: "mail: hogehoge@foomail.com",
  decorations: [
    DecorationOption(
      pattern: TextPattern.mail,
      style: TextStyle(color: Colors.blue),
      tapAction: TapAction.launchMail,
      showRipple: true,
    ),
  ],
),
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261100/8728289d-4b28-c5bf-bd78-5f60462ff7d8.gif" width="320px">

### 任意のタップアクションを追加する

``` dart
DecoratableText(
  text: "You can set custom tap actions. #SnackBar",
  decorations: [
    DecorationOption(
      pattern: r"#[a-zA-Z0-9_]+",
      style: TextStyle(color: Colors.teal),
      onTap: () => Scaffold.of(context).showSnackBar(
        const SnackBar(
          content: Text("Tapped #SnackBar"),
        ),
      ),
      showRipple: true,
    ),
  ],
),
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261100/c46dcb7c-8888-2304-38f4-a9dca72a8b4b.gif" width="320px">

### 複数箇所装飾する

``` dart
const DecoratableText(
  text:
      "You can set multiple decoration options. \nFlutter: https://flutter.dev/ #flutter \nDart: https://dart.dev/ #dart",
  decorations: [
    DecorationOption(
      pattern: r"#[a-zA-Z0-9_]+",
      style: TextStyle(color: Colors.teal),
    ),
    DecorationOption(
      pattern: TextPattern.url,
      style: TextStyle(color: Colors.blue),
      tapAction: TapAction.launchUrl,
      showRipple: true,
    ),
  ],
),
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261100/03e672fa-a03b-c957-84d0-3dc26da46f59.png" width="320px">

その他，詳しい使い方は[example/lib/main.dart](https://github.com/saccho/decoratable_text/blob/master/example/lib/main.dart)を参照してください．

# さいごに

正規表現とループで処理させているので，`decorations`が増えるとパフォーマンスが低下したり，改善点は多そうですが，一応簡単にテキストの一部を装飾したいという目的は達成できた気がします．
PRなど大歓迎です．

Package: https://pub.dev/packages/decoratable_text
Repository: https://github.com/saccho/decoratable_text