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 がブラーのサイズです。
ブラーの領域を指定する
先の例では背景画像全体にブラーがかかりますが、Positioned と Clip系ウィジェット(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 が無いと全体にブラーがかかってしまうのでご注意を)
背景を変形させる
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),),),
),
),
),
),
),
],
),
);
}
}
コメント
[…] ブラーと変形(虫眼鏡機能)のサンプルコード 機能を知っても、どのように使うか、という発想に欠けるので勉強になる。 […]