Support for ImageFiltered widget to apply ImageFilter to children. (#47489)
This commit is contained in:
parent
49c7845907
commit
fcaf9c4070
@ -1548,6 +1548,48 @@ class ColorFilterLayer extends ContainerLayer {
|
||||
}
|
||||
}
|
||||
|
||||
/// A composite layer that applies an [ImageFilter] to its children.
|
||||
class ImageFilterLayer extends ContainerLayer {
|
||||
/// Creates a layer that applies an [ImageFilter] to its children.
|
||||
///
|
||||
/// The [imageFilter] property must be non-null before the compositing phase
|
||||
/// of the pipeline.
|
||||
ImageFilterLayer({
|
||||
ui.ImageFilter imageFilter,
|
||||
}) : _imageFilter = imageFilter;
|
||||
|
||||
/// The image filter to apply to children.
|
||||
///
|
||||
/// The scene must be explicitly recomposited after this property is changed
|
||||
/// (as described at [Layer]).
|
||||
ui.ImageFilter get imageFilter => _imageFilter;
|
||||
ui.ImageFilter _imageFilter;
|
||||
set imageFilter(ui.ImageFilter value) {
|
||||
assert(value != null);
|
||||
if (value != _imageFilter) {
|
||||
_imageFilter = value;
|
||||
markNeedsAddToScene();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
|
||||
assert(imageFilter != null);
|
||||
engineLayer = builder.pushImageFilter(
|
||||
imageFilter,
|
||||
oldLayer: _engineLayer as ui.ImageFilterEngineLayer,
|
||||
);
|
||||
addChildrenToScene(builder, layerOffset);
|
||||
builder.pop();
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<ui.ImageFilter>('imageFilter', imageFilter));
|
||||
}
|
||||
}
|
||||
|
||||
/// A composited layer that applies a given transformation matrix to its
|
||||
/// children.
|
||||
///
|
||||
|
72
packages/flutter/lib/src/widgets/image_filter.dart
Normal file
72
packages/flutter/lib/src/widgets/image_filter.dart
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import 'framework.dart';
|
||||
|
||||
/// Applies an [ImageFilter] to its child.
|
||||
@immutable
|
||||
class ImageFiltered extends SingleChildRenderObjectWidget {
|
||||
/// Creates a widget that applies an [ImageFilter] to its child.
|
||||
///
|
||||
/// The [imageFilter] must not be null.
|
||||
const ImageFiltered({
|
||||
Key key,
|
||||
@required this.imageFilter,
|
||||
Widget child,
|
||||
}) : assert(imageFilter != null),
|
||||
super(key: key, child: child);
|
||||
|
||||
/// The image filter to apply to the child of this widget.
|
||||
final ImageFilter imageFilter;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) => _ImageFilterRenderObject(imageFilter);
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, _ImageFilterRenderObject renderObject) {
|
||||
renderObject..imageFilter = imageFilter;
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<ImageFilter>('imageFilter', imageFilter));
|
||||
}
|
||||
}
|
||||
|
||||
class _ImageFilterRenderObject extends RenderProxyBox {
|
||||
_ImageFilterRenderObject(this._imageFilter);
|
||||
|
||||
ImageFilter get imageFilter => _imageFilter;
|
||||
ImageFilter _imageFilter;
|
||||
set imageFilter(ImageFilter value) {
|
||||
assert(value != null);
|
||||
if (value != _imageFilter) {
|
||||
_imageFilter = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get alwaysNeedsCompositing => child != null;
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
assert(imageFilter != null);
|
||||
if (layer == null) {
|
||||
layer = ImageFilterLayer(imageFilter: imageFilter);
|
||||
} else {
|
||||
final ImageFilterLayer filterLayer = layer as ImageFilterLayer;
|
||||
filterLayer
|
||||
..imageFilter = imageFilter;
|
||||
}
|
||||
context.pushLayer(layer, super.paint, offset);
|
||||
assert(layer != null);
|
||||
}
|
||||
}
|
@ -48,6 +48,7 @@ export 'src/widgets/icon_data.dart';
|
||||
export 'src/widgets/icon_theme.dart';
|
||||
export 'src/widgets/icon_theme_data.dart';
|
||||
export 'src/widgets/image.dart';
|
||||
export 'src/widgets/image_filter.dart';
|
||||
export 'src/widgets/image_icon.dart';
|
||||
export 'src/widgets/implicit_animations.dart';
|
||||
export 'src/widgets/inherited_model.dart';
|
||||
|
86
packages/flutter/test/widgets/image_filter_test.dart
Normal file
86
packages/flutter/test/widgets/image_filter_test.dart
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Image filter - blur', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
RepaintBoundary(
|
||||
child: ImageFiltered(
|
||||
imageFilter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
|
||||
child: const Placeholder(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await expectLater(
|
||||
find.byType(ImageFiltered),
|
||||
matchesGoldenFile('image_filter_blur.png'),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Image filter - matrix', (WidgetTester tester) async {
|
||||
final ImageFilter matrix = ImageFilter.matrix(Float64List.fromList(<double>[
|
||||
0.5, 0.0, 0.0, 0.0, //
|
||||
0.0, 0.5, 0.0, 0.0, //
|
||||
0.0, 0.0, 1.0, 0.0, //
|
||||
0.0, 0.0, 0.0, 1.0, //
|
||||
]));
|
||||
await tester.pumpWidget(
|
||||
RepaintBoundary(
|
||||
child: ImageFiltered(
|
||||
imageFilter: matrix,
|
||||
child: MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(primarySwatch: Colors.blue),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Matrix ImageFilter Test'),
|
||||
),
|
||||
body: const Center(
|
||||
child:Text('Hooray!'),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () { },
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await expectLater(
|
||||
find.byType(ImageFiltered),
|
||||
matchesGoldenFile('image_filter_matrix.png'),
|
||||
);
|
||||
}, skip: isBrowser);
|
||||
|
||||
testWidgets('Image filter - reuses its layer', (WidgetTester tester) async {
|
||||
Future<void> pumpWithSigma(double sigma) async {
|
||||
await tester.pumpWidget(
|
||||
RepaintBoundary(
|
||||
child: ImageFiltered(
|
||||
imageFilter: ImageFilter.blur(sigmaX: sigma, sigmaY: sigma),
|
||||
child: const Placeholder(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await pumpWithSigma(5.0);
|
||||
final RenderObject renderObject = tester.firstRenderObject(find.byType(ImageFiltered));
|
||||
final ImageFilterLayer originalLayer = renderObject.debugLayer as ImageFilterLayer;
|
||||
expect(originalLayer, isNotNull);
|
||||
|
||||
// Change blur sigma to force a repaint.
|
||||
await pumpWithSigma(10.0);
|
||||
expect(renderObject.debugLayer, same(originalLayer));
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user