Created
August 1, 2019 20:40
-
-
Save liyuqian/560c3edefe2bce147147a49db0ec4e98 to your computer and use it in GitHub Desktop.
Cache BackdropFilter with Snapshot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:ui' as ui; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
import 'package:flutter/scheduler.dart'; | |
import 'package:flutter/services.dart'; | |
void main() => runApp(MyApp()); | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Demo', | |
home: Scaffold( | |
body: Stack( | |
fit: StackFit.expand, | |
children: <Widget>[ | |
CachedFrostedBox( | |
opaqueBackground: Container( | |
color: Colors.white, | |
child: Text('0' * 10000), | |
), | |
sigmaX: 2.0, | |
sigmaY: 2.0, | |
child: Container( | |
alignment: Alignment.center, | |
child: Text('Hello'), | |
), | |
), | |
Center( | |
child: Padding( | |
padding: EdgeInsets.fromLTRB(0, 20, 0, 0), | |
child: LinearProgressIndicator(), | |
), | |
), | |
], | |
), | |
), | |
showPerformanceOverlay: true, | |
); | |
} | |
} | |
class Foo extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return Stack( | |
fit: StackFit.expand, | |
children: <Widget>[ | |
CachedFrostedBox( | |
opaqueBackground: Container( | |
color: Colors.white, | |
child: Text('0' * 10000), | |
), | |
sigmaX: 2.0, | |
sigmaY: 2.0, | |
child: Container( | |
alignment: Alignment.center, | |
child: Text('Hello'), | |
), | |
), | |
Center( | |
child: Padding( | |
padding: EdgeInsets.fromLTRB(0, 20, 0, 0), | |
child: LinearProgressIndicator(), | |
), | |
), | |
], | |
); | |
} | |
} | |
class CachedFrostedBox extends StatefulWidget { | |
CachedFrostedBox({@required this.child, this.sigmaX = 8, this.sigmaY = 8, this.opaqueBackground}) | |
: this.frostBackground = Stack( | |
children: <Widget>[ | |
opaqueBackground, | |
ClipRect( | |
child: BackdropFilter( | |
filter: ui.ImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY), | |
child: new Container( | |
decoration: new BoxDecoration( | |
color: Colors.white.withOpacity(0.1), | |
) | |
), | |
) | |
), | |
], | |
); | |
final Widget child; | |
final double sigmaY; | |
final double sigmaX; | |
/// This must be opaque so the backdrop filter won't access any colors beneath this background. | |
final Widget opaqueBackground; | |
/// Blur applied to the opaqueBackground. See the constructor. | |
final Widget frostBackground; | |
@override | |
State<StatefulWidget> createState() { | |
return CachedFrostedBoxState(); | |
} | |
} | |
class CachedFrostedBoxState extends State<CachedFrostedBox> { | |
final GlobalKey _snapshotKey = GlobalKey(); | |
Image _backgroundSnapshot; | |
bool _snapshotLoaded = false; | |
bool _skipSnapshot = false; | |
void _snapshot(Duration _) async { | |
final RenderRepaintBoundary renderBackground = _snapshotKey.currentContext.findRenderObject(); | |
final ui.Image image = await renderBackground.toImage( | |
pixelRatio: WidgetsBinding.instance.window.devicePixelRatio, | |
); | |
// !!! The default encoding rawRgba will throw exceptions. This bug is introducing a lot | |
// of encoding/decoding work. | |
final ByteData imageByteData = await image.toByteData(format: ui.ImageByteFormat.png); | |
setState(() { | |
_backgroundSnapshot = Image.memory(imageByteData.buffer.asUint8List()); | |
}); | |
} | |
@override | |
Widget build(BuildContext context) { | |
Widget frostedBackground; | |
if (_backgroundSnapshot == null || _skipSnapshot) { | |
frostedBackground = RepaintBoundary( | |
key: _snapshotKey, | |
child: widget.frostBackground, | |
); | |
if (!_skipSnapshot) { | |
SchedulerBinding.instance.addPostFrameCallback(_snapshot); | |
} | |
} else { | |
// !!! We don't seem to have a way to know when IO thread | |
// decoded the image. | |
if (!_snapshotLoaded) { | |
frostedBackground = widget.frostBackground; | |
Future.delayed(Duration(seconds: 1), () { | |
setState(() { | |
_snapshotLoaded = true; | |
}); | |
}); | |
} else { | |
frostedBackground = Offstage(); | |
} | |
} | |
return Stack( | |
children: <Widget>[ | |
frostedBackground, | |
if (_backgroundSnapshot != null) _backgroundSnapshot, | |
widget.child, | |
GestureDetector( | |
onTap: () { | |
setState(() { _skipSnapshot = !_skipSnapshot; }); | |
} | |
), | |
], | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment