Flutter で Liquid Glass やってみた

Flutter の Liquid Glass 対応について SNS ではいろいろ言われたり言ったりしていますが、それはそれとして「Flutter でも Liquid Glass してみたい！」ということで、`liquid_glass_renderer` パッケージで少し遊んでみました。

https://pub.dev/packages/liquid_glass_renderer

いったん出来上がったものがこちらです。

https://x.com/tsuyoshi_chujo/status/1935999132260614298

自作パッケージの [crop_your_image](https://pub.dev/packages/crop_your_image) と組み合わせて、ガラス風の画像切り抜き画面を作ってみました。どうでしょう？

ということで、この記事では liquid_glass_renderer パッケージの紹介をしながら Flutter の Liquid Glass 対応の状況（2025.06.20 現在）を整理してみたいと思います。

なお、Flutter の Liquid Glass に対する議論はこちらの issue を追っていただくのが手っ取り早いと思います。

https://github.com/flutter/flutter/issues/170310

## liquid_glass_renderer パッケージ

Flutter フレームワークは元々プラットフォーム固有のデザインを再現することをあまり重視していません。そのため、Liquid Glass についても「フレームワーク本体としては」対応には消極的です。

たしかに、仮に本体で対応されたとしても実際にアプリでそれを使おうと思った時、「じゃあ Android はどうするの？」という問題が出ることは簡単に予想できますので、Flutter チームが頑張って対応してもあまり使われないことが予想されます。そう考えるとこの方針は妥当だと個人的に思えます。

いっぽうだからと言って Flutter ではずっと Liquid Glass 風の UI が実現できないかというとそうではありません。（詳しい仕組みは理解できていないですが）シェーダーを駆使して Liquid Glass を再現しようという取り組みがコミュニティ内で盛り上がっており、今回紹介する [liquid_glass_renderer](https://pub.dev/packages/liquid_glass_renderer) パッケージなどはいち早くそれをパッケージとして触れるように作り込んでくれました。ありがたや。

https://x.com/imadetheseworks/status/1933585381967937952

`liquid_glass_renderer` を使うことで、任意の円形、四角形のガラス風 Widget を配置することができたり、さらには複数のガラスを近づけるときれいに溶け込んでひとつになるような動きをさせたり、テキスト自体をガラス風にしたりとさまざまなことが可能です。

https://x.com/imadetheseworks/status/1935624842587615392

https://x.com/imadetheseworks/status/1935616652017561758

今回はこれを使って、画像切り抜きの UI をガラス風にしてみました。

## liquid_glass_renderer の使い方

では使い方を見ていきましょう。一点注意ですが、このパッケージはまだ開発中ですので、今後使い方が大きく変わる可能性もあります。この記事ではバージョン `0.1.1-dev.9` 時点での使い方を前提に説明していきます。

### LiquidGlass を配置する

もっともシンプルな使い方は `LiquidGlass` という Widget を配置することです。

```dart
return LiquidGlass(
  shape: LiquidRoundedSuperellipse(
    borderRadius: Radius.circular(20),
  ),
  child: SizedBox(width: 160, height: 160),
),
```

ただし、基本的に `LiquidGlass` は透明ですので、無地の背景にこれを配置してもほぼ何も見えません。ドキュメントでも `Stack` を使ってアプリの画面に重ねて使うように説明しています。

今回は、適当な画像を背景に `LiquidGlass` を配置してみます。

```dart
return Scaffold(
  body: Stack(
    children: [
      Positioned.fill(
        child: Image.asset('assets/photo.jpg', fit: BoxFit.cover),
      ),
      Center(
        child: LiquidGlass(
          shape: LiquidRoundedSuperellipse(
            borderRadius: Radius.circular(20),
          ),
          child: SizedBox(width: 160, height: 160),
        ),
      ),
    ],
  ),
),
```

実行すると、以下のような画面が表示されます。

![スクリーンショット 2025-06-20 19.07.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134943/9ed78581-31fb-4dac-b02e-28bf27615827.png)

中心にそれらしい枠が出ているような気がしますが、まだ若干分かりづらいです。

`Liquid Glass` には `settings` という引数が用意されていて、これを使ってガラスの見た目をいろいろと調整できるようになっています。

今回は見た目に分かりやすいよう少し大げさに設定してみて、その変化を確認してみたいと思います。

```dart
child: LiquidGlass(
  settings: LiquidGlassSettings(
    blur: 2, // 背景をぼかす
    glassColor: Colors.white.withAlpha(40), // ガラス自体に色をつける
    lightAngle: 0.25 * pi, // 光を斜め45度からあてる
    lightIntensity: 20, // 光を少し強めにする
    thickness: 40,  // ガラスの縁を太くする
  ),
  shape: LiquidRoundedSuperellipse(
    borderRadius: Radius.circular(20),
  ),
  child: SizedBox(width: 200, height: 200),
),
```

![スクリーンショット 2025-06-20 19.12.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134943/b1606cab-b99d-4f67-a8e3-81943fdf7cb8.png)

だいぶ目に見えるようになってきました。

### LiquidGlass を動かす

`LiquidGlass` のエフェクトはコンテンツが動いているときにとても目立ちます。

今回は画像を `InteractiveViewer` に乗せることで、ズームしたり動かしたりできるようにしてみます。

```dart
Positioned.fill(
  child: InteractiveViewer(
    child: Image.asset('assets/crop.jpg', fit: BoxFit.cover),
  ),
),
```

![glass_qiita.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134943/a4cbe211-d8f0-4730-8325-fb36b461665c.gif)

背景が動いているとだいぶ分かりやすいですね！

### 画像切り抜き UI に組み込む

さらに、`Liquid Glass` と [crop_your_image](https://pub.dev/packages/crop_your_image) を組み合わせてガラス風の画像切り抜き UI を作ってみたいと思います。

まずは `Stack` の中に crop_your_image の `Crop` を配置します。[^1]

```dart
  Positioned.fill(
    child: Crop(
      controller: _controller, // 切り抜き処理を呼び出すコントローラー
      image: image, // 画像
      interactive: true,  // ズームで画像を動かせるように
      initialRectBuilder: InitialRectBuilder.withSizeAndRatio(
        size: 0.5, // 画面サイズの半分の幅、高さで
        aspectRatio: 1, // 正方形で
      ),
      fixCropRect: true, // 切り抜きエリア自体は動かせないようにする
      radius: 20, // 丸角にする
      onMoved: (viewportRect, _) {
        setState(() => _cropRect = viewportRect), // 切り抜き範囲を覚えておく
      },
    ),
  ),
```

`Crop` は `onMoved` で現在の切り抜き範囲を表す `Rect` を通知してくれるので、これを State に保持しておきます。

次に、先ほどの `LiquidGlass` を `onMoved` で受け取った切り抜き範囲にちょうど重なる位置に配置します。

```dart
if (_cropRect != null) Positioned(
  left: _cropRect!.left, // x座標を合わせる
  top: _cropRect!.top, // y座標を合わせる
  child: LiquidGlass(
    ... 細かい引数は省略
    child: SizedBox(
      width: _cropRect!.width, // 幅を合わせる
      height: _cropRect!.height, // 高さを合わせる
    ),
  ),
),
```

こうすることで、切り抜き範囲にぴったりとガラスが乗り、ガラス風の UI で切り抜き範囲を選択できます。

この際切り抜きボタンも `LiquidGlass` で作ってしまいましょう。`Stack` にさらにボタンを重ねます。

```dart
Center(
  child: LiquidGlass(
    glassContainsChild: false, // child がガラスの上に乗る（ガラスのエフェクトを受けない）ようにする
    shape: LiquidOval(), // 円形のガラス
    child: GestureDetector(
      onTap: _controller.crop, // タップしたら切り抜き実行
      child: SizedBox(
        height: 120,
        width: 120,
        child: Center(
          child: Icon( // 切り抜きアイコン
            Icons.cut,
            size: 40,
            color: Colors.black87,
          ),
        ),
      ),
    ),
  ),
),
```

あとはもろもろの切り抜き前後の処理（画像を読み込む、切り抜いたら UI を変える、など）を整えると、冒頭のような一連のガラス風画像切り抜き画面が完成です。

https://x.com/tsuyoshi_chujo/status/1935999132260614298

できあがったコードはこちらで公開しました。`main.dart` の 200 行ほどでzですので、もし興味があれば見てみてください。

https://github.com/chooyan-eng/liquid_glass_crop_sample

## まとめや諸注意など

「Flutter は Liquid Glass に対応できない」とは言われていますが（誤解も含まれますが）、同じような表現を再現しようとする取り組みはすでにかなり進んでいます。

ただし、これがあればネイティブと同等の UI が作れるかどうかはまた別の話です。

端末の傾きを利用した反射表現など原理的に再現できない表現もあるかもしれませんし、エフェクトやコンポーネントが再現できたとしても Apple のデザイン思想を理解しないまま使うと「それっぽい別物」感がどうしても出てしまいます。（同じ問題は Liquid Glass 以前の Material や Cupertino でも発生しますが）

今回紹介した `liquid_glass_renderer` はあくまで「Liquid Glass と同じようなエフェクトを Widget に与える」パッケージであり、これを利用して Apple のコンポーネントを再現したければまたそれを研究した別パッケージが必要になりそうです。

ということで、これで Flutter もネイティブの Liquid Glass コンポーネント（と同等の Widget）が使える、と言えるまでにはまだいくつかハードルがありそうですが、とはいえ同じような表現が全くできないわけではないこともこの記事で伝わったのではないかと思います。

なお、この表現は Impeller に用意された機能を利用しているため、Impeller に対応していない Web やデスクトップではパッケージ自体が使えないことに注意してください。Android は Impeller に対応していますが、別途動作確認してみます。

ということで、Flutter で Liquid Glass 風の UI を作って遊んでみた、という記事でした。実際にプロダクトで取り入れるかどうかは置いておいて、こういう取り組みはシンプルに楽しいですね！

[^1]: crop_your_image の細かい使い方はこの記事では割愛します。詳しくは Readme 等をご参照ください。