Skip to content

Commit

Permalink
Add BackdropFilter blend mode (#80129)
Browse files Browse the repository at this point in the history
  • Loading branch information
flar committed Apr 27, 2021
1 parent 3155972 commit 6738462
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 4 deletions.
26 changes: 25 additions & 1 deletion packages/flutter/lib/src/rendering/layer.dart
Expand Up @@ -1803,7 +1803,13 @@ class BackdropFilterLayer extends ContainerLayer {
///
/// The [filter] property must be non-null before the compositing phase of the
/// pipeline.
BackdropFilterLayer({ ui.ImageFilter? filter }) : _filter = filter;
///
/// The [blendMode] property defaults to [BlendMode.srcOver].
BackdropFilterLayer({
ui.ImageFilter? filter,
BlendMode blendMode = BlendMode.srcOver,
}) : _filter = filter,
_blendMode = blendMode;

/// The filter to apply to the existing contents of the scene.
///
Expand All @@ -1818,11 +1824,29 @@ class BackdropFilterLayer extends ContainerLayer {
}
}

/// The blend mode to use to apply the filtered background content onto the background
/// surface.
///
/// The default value of this property is [BlendMode.srcOver].
/// {@macro flutter.widgets.BackdropFilter.blendMode}
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
BlendMode get blendMode => _blendMode;
BlendMode _blendMode;
set blendMode(BlendMode value) {
if (value != _blendMode) {
_blendMode = value;
markNeedsAddToScene();
}
}

@override
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
assert(filter != null);
engineLayer = builder.pushBackdropFilter(
filter!,
blendMode: blendMode,
oldLayer: _engineLayer as ui.BackdropFilterEngineLayer?,
);
addChildrenToScene(builder, layerOffset);
Expand Down
21 changes: 20 additions & 1 deletion packages/flutter/lib/src/rendering/proxy_box.dart
Expand Up @@ -1141,9 +1141,13 @@ class RenderBackdropFilter extends RenderProxyBox {
/// Creates a backdrop filter.
///
/// The [filter] argument must not be null.
RenderBackdropFilter({ RenderBox? child, required ui.ImageFilter filter })
/// The [blendMode] argument, if provided, must not be null
/// and will default to [BlendMode.srcOver].
RenderBackdropFilter({ RenderBox? child, required ui.ImageFilter filter, BlendMode blendMode = BlendMode.srcOver })
: assert(filter != null),
assert(blendMode != null),
_filter = filter,
_blendMode = blendMode,
super(child);

@override
Expand All @@ -1164,6 +1168,20 @@ class RenderBackdropFilter extends RenderProxyBox {
markNeedsPaint();
}

/// The blend mode to use to apply the filtered background content onto the background
/// surface.
///
/// {@macro flutter.widgets.BackdropFilter.blendMode}
BlendMode get blendMode => _blendMode;
BlendMode _blendMode;
set blendMode(BlendMode value) {
assert(value != null);
if (_blendMode == value)
return;
_blendMode = value;
markNeedsPaint();
}

@override
bool get alwaysNeedsCompositing => child != null;

Expand All @@ -1173,6 +1191,7 @@ class RenderBackdropFilter extends RenderProxyBox {
assert(needsCompositing);
layer ??= BackdropFilterLayer();
layer!.filter = _filter;
layer!.blendMode = _blendMode;
context.pushLayer(layer!, super.paint, offset);
} else {
layer = null;
Expand Down
33 changes: 31 additions & 2 deletions packages/flutter/lib/src/widgets/basic.dart
Expand Up @@ -166,6 +166,12 @@ class Directionality extends InheritedWidget {
/// buffer. For the value 0.0, the child is simply not painted at all. For the
/// value 1.0, the child is painted immediately without an intermediate buffer.
///
/// The presence of the intermediate buffer which has a transparent background
/// by default may cause some child widgets to behave differently. For example
/// a [BackdropFilter] child will only be able to apply its filter to the content
/// between this widget and the backdrop child and may require adjusting the
/// [BackdropFilter.blendMode] property to produce the desired results.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=9hltevOHQBw}
///
/// {@tool snippet}
Expand Down Expand Up @@ -377,6 +383,18 @@ class ShaderMask extends SingleChildRenderObjectWidget {
/// widget's clip. If there's no clip, the filter will be applied to the full
/// screen.
///
/// The results of the filter will be blended back into the background using
/// the [blendMode] parameter.
/// {@template flutter.widgets.BackdropFilter.blendMode}
/// The only value for [blendMode] that is supported on all platforms is
/// [BlendMode.srcOver] which works well for most scenes. But that value may
/// produce surprising results when a parent of the [BackdropFilter] uses a
/// temporary buffer, or save layer, as does an [Opacity] widget. In that
/// situation, a value of [BlendMode.src] can produce more pleasing results,
/// but at the cost of incompatibility with some platforms, most notably the
/// html renderer for web applications.
/// {@endtemplate}
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=dYRs7Q1vfYI}
///
/// {@tool snippet}
Expand Down Expand Up @@ -427,10 +445,13 @@ class BackdropFilter extends SingleChildRenderObjectWidget {
/// Creates a backdrop filter.
///
/// The [filter] argument must not be null.
/// The [blendMode] argument will default to [BlendMode.srcOver] and must not be
/// null if provided.
const BackdropFilter({
Key? key,
required this.filter,
Widget? child,
this.blendMode = BlendMode.srcOver,
}) : assert(filter != null),
super(key: key, child: child);

Expand All @@ -440,14 +461,22 @@ class BackdropFilter extends SingleChildRenderObjectWidget {
/// blur effect.
final ui.ImageFilter filter;

/// The blend mode to use to apply the filtered background content onto the background
/// surface.
///
/// {@macro flutter.widgets.BackdropFilter.blendMode}
final BlendMode blendMode;

@override
RenderBackdropFilter createRenderObject(BuildContext context) {
return RenderBackdropFilter(filter: filter);
return RenderBackdropFilter(filter: filter, blendMode: blendMode);
}

@override
void updateRenderObject(BuildContext context, RenderBackdropFilter renderObject) {
renderObject.filter = filter;
renderObject
..filter = filter
..blendMode = blendMode;
}
}

Expand Down
59 changes: 59 additions & 0 deletions packages/flutter/test/widgets/backdrop_filter_test.dart
Expand Up @@ -45,4 +45,63 @@ void main() {
matchesGoldenFile('backdrop_filter_test.cull_rect.png'),
);
});

testWidgets('BackdropFilter blendMode on saveLayer', (WidgetTester tester) async {
tester.binding.addTime(const Duration(seconds: 15));
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Opacity(
opacity: 0.9,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Text('0 0 ' * 10000),
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// ClipRect needed for filtering the 200x200 area instead of the
// whole screen.
children: <Widget>[
ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 5.0,
sigmaY: 5.0,
),
child: Container(
alignment: Alignment.center,
width: 200.0,
height: 200.0,
color: Colors.yellow.withAlpha(0x7),
),
),
),
ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 5.0,
sigmaY: 5.0,
),
blendMode: BlendMode.src,
child: Container(
alignment: Alignment.center,
width: 200.0,
height: 200.0,
color: Colors.yellow.withAlpha(0x7),
),
),
),
],
),
],
),
),
),
),
);
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('backdrop_filter_test.saveLayer.blendMode.png'),
);
});
}

0 comments on commit 6738462

Please sign in to comment.