Flutter の BackdropFliter で背景にブラーをかけたり変形させる

Flutter

Flutter で背景にブラーをかけたり背景を変形させる BackdropFilter ウィジェットについて解説します。このウィジェットを置けばその下にあるウィジェットに簡単にブラーをかけたり変形させることができます。

スポンサーリンク

背景にブラーをかける

任意の場所に BackdropFilter を置いて、filter: に ImageFilter.blur を指定します。

※背景画像は「iPhoneが透けて見える壁紙」でお馴染み iFixit さんの iPhone 11 Pro Max スケルトン壁紙画像を使わせていただきました。

// ImageFilterクラスを利用するので以下をインポートします
import 'dart:ui';

// スタックでウィジェットを重ねる
Stack(
  children: <Widget>[
    // 背景画像
    Image.network(
      'https://valkyrie.cdn.ifixit.com/media/2019/09/22165935/iPhone_11_Pro_Max-internals.jpg',
    ),
    // ブラー
    BackdropFilter(
      filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
      child: Container(color: Colors.black.withOpacity(0)),
    ),
  ],
),

これで背景画像全体にブラーがかかります。sigmaX, sigmaY がブラーのサイズです。

BackdropFilter の child: にブラーがかかるのではなく、Stack で重なった BackdropFilter より下に配置されているウィジェットにブラーがかかります。

BackdropFilter の child: には、ブラーの上に置きたいウィジェットを配置しましょう。もし不要なら上の例のように透明なコンテナを置けばブラーだけがかかります。

ブラーの領域を指定する

先の例では背景画像全体にブラーがかかりますが、PositionedClip系ウィジェット(ClipRect、ClipOvalなど) を組み合わせてブラーがかかる領域を指定することができます。

// スタックでウィジェットを重ねる
Stack(
  children: <Widget>[
    // 背景画像
    Image.network(
      'https://valkyrie.cdn.ifixit.com/media/2019/09/22165935/iPhone_11_Pro_Max-internals.jpg',
    ),
    // ブラー
    Positioned(
      left: 50,
      top: 50,
      width: 200,
      height: 200,
      child: ClipRect(
        child: BackdropFilter(
          filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
          child: Container(color: Colors.black.withOpacity(0)),
        ),
      ),
    ),
  ],
),

Positioned で領域を指定して、ClipRect(矩形)で領域をクリップしています。(ClipRect が無いと全体にブラーがかかってしまうのでご注意を)

ちなみに円形でClipする場合は ClipOval、角丸なら ClipRRect を使います。

例えば背景画像を拡大して虫眼鏡的な機能を実装するなら円形の ClipOval を使えば虫眼鏡(ルーペ)ぽくて見栄えが良いかもしれません。(虫眼鏡機能はサンプルコードで示してますので参考にしてみてください)

背景を変形させる

ImageFilter にはブラーの他に ImageFilter.matrix があり、こちらでは背景を変形させることができます。

// スタックでウィジェットを重ねる
Stack(
  children: <Widget>[
    // 背景画像
    Image.network(
      'https://valkyrie.cdn.ifixit.com/media/2019/09/22165935/iPhone_11_Pro_Max-internals.jpg',
    ),
    // 変形
    BackdropFilter(
      filter: ImageFilter.matrix(Matrix4.diagonal3Values(3.0, 3.0, 1.0).storage), //スケール3倍
      child: Container(color: Colors.black.withOpacity(0)),
    ),
  ],
),

上の例では、背景画像を3倍の大きさに拡大しています。

matrix には List<double>(Float64List) を渡しますが、Matrix4 の .storage を渡せばOKです。

ブラーと変形(虫眼鏡機能)のサンプルコード

領域を指定したブラーと、同じく領域を指定した変形のサンプルコードです。それぞれのボックスをドラッグで動かせます。変形の方は、虫眼鏡(ルーペ)的な機能として実装してみました。

(以下のツイートの動画と同じものです)

import 'package:flutter/material.dart';
import 'dart:ui'; // use ImageFilter

void main() => runApp(MyApp());

// MyAppウィジェットクラス
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(title: 'BackdropFilter'),
    );
  }
}

// MyHomePageウィジェットクラス
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

// MyHomePageステートクラス
class _MyHomePageState extends State<MyHomePage> {
  Size _size = Size(180,180); // ボックスのサイズ
  Offset _offsetBlur = Offset(20,100); // Blurのポジション
  Offset _offsetMatrix = Offset(220,100); // Matrixのポジション
  double _scale = 3.0; // Matrixの拡大倍率

  // Matrixの計算
  List<double> _calcMatrix() {
    double w = MediaQuery.of(context).size.width;
    double h = MediaQuery.of(context).size.height;
    double x = (w * _scale - _size.width) / (w - _size.width);
    double y = (h * _scale - _size.height) / (h - _size.height);
    x = -_offsetMatrix.dx * x + _offsetMatrix.dx;
    y = -_offsetMatrix.dy * y + _offsetMatrix.dy;
    Matrix4 mat = Matrix4.diagonal3Values(_scale, _scale, 1.0);
    mat += Matrix4.translationValues(x, y, 0);
    return mat.storage;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          // 背景画像
          Image.network(
            'https://valkyrie.cdn.ifixit.com/media/2019/09/22165935/iPhone_11_Pro_Max-internals.jpg',
          ),
          // ブラー用のボックス
          Positioned(
            left: _offsetBlur.dx,
            top: _offsetBlur.dy,
            width: _size.width,
            height: _size.height,
            child: ClipRect(
              child: GestureDetector(
                // ドラッグの移動を更新
                onPanUpdate: (DragUpdateDetails details) {
                  setState(() {
                    _offsetBlur = Offset(_offsetBlur.dx+details.delta.dx, _offsetBlur.dy+details.delta.dy);
                  });
                },
                child: BackdropFilter(
                  filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
                  child: Container(
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.white,width: 3),
                    ),
                    child: Center(child: Text('Blur',style: TextStyle(color: Colors.white,fontSize: 20),),),
                  ),
                ),
              ),
            ),
          ),
          // Matrix用のボックス
          Positioned(
            left: _offsetMatrix.dx,
            top: _offsetMatrix.dy,
            width: _size.width,
            height: _size.height,
            child: ClipRect(
              child: GestureDetector(
                // ドラッグの移動を更新
                onPanUpdate: (DragUpdateDetails details) {
                  setState(() {
                    _offsetMatrix = Offset(_offsetMatrix.dx+details.delta.dx, _offsetMatrix.dy+details.delta.dy);
                  });
                },
                child: BackdropFilter(
                  filter: ImageFilter.matrix(_calcMatrix()),
                  child: Container(
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.white,width: 3),
                    ),
                    child: Center(child: Text('Matrix',style: TextStyle(color: Colors.white,fontSize: 20),),),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

コメント

タイトルとURLをコピーしました