ClipPath.shape and related fixes (#24816)
This commit is contained in:
parent
c5ad1067b7
commit
e239872561
@ -357,8 +357,14 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
|
|||||||
|
|
||||||
final ShapeBorder shape = _getShape();
|
final ShapeBorder shape = _getShape();
|
||||||
|
|
||||||
if (widget.type == MaterialType.transparency)
|
if (widget.type == MaterialType.transparency) {
|
||||||
return _transparentInterior(shape: shape, clipBehavior: widget.clipBehavior, contents: contents);
|
return _transparentInterior(
|
||||||
|
context: context,
|
||||||
|
shape: shape,
|
||||||
|
clipBehavior: widget.clipBehavior,
|
||||||
|
contents: contents,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return _MaterialInterior(
|
return _MaterialInterior(
|
||||||
curve: Curves.fastOutSlowIn,
|
curve: Curves.fastOutSlowIn,
|
||||||
@ -372,7 +378,12 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Widget _transparentInterior({ShapeBorder shape, Clip clipBehavior, Widget contents}) {
|
static Widget _transparentInterior({
|
||||||
|
@required BuildContext context,
|
||||||
|
@required ShapeBorder shape,
|
||||||
|
@required Clip clipBehavior,
|
||||||
|
@required Widget contents,
|
||||||
|
}) {
|
||||||
final _ShapeBorderPaint child = _ShapeBorderPaint(
|
final _ShapeBorderPaint child = _ShapeBorderPaint(
|
||||||
child: contents,
|
child: contents,
|
||||||
shape: shape,
|
shape: shape,
|
||||||
@ -382,7 +393,10 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
return ClipPath(
|
return ClipPath(
|
||||||
child: child,
|
child: child,
|
||||||
clipper: ShapeBorderClipper(shape: shape),
|
clipper: ShapeBorderClipper(
|
||||||
|
shape: shape,
|
||||||
|
textDirection: Directionality.of(context),
|
||||||
|
),
|
||||||
clipBehavior: clipBehavior,
|
clipBehavior: clipBehavior,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1053,7 +1053,7 @@ class RenderBackdropFilter extends RenderProxyBox {
|
|||||||
/// information.
|
/// information.
|
||||||
///
|
///
|
||||||
/// The most efficient way to update the clip provided by this class is to
|
/// The most efficient way to update the clip provided by this class is to
|
||||||
/// supply a reclip argument to the constructor of the [CustomClipper]. The
|
/// supply a `reclip` argument to the constructor of the [CustomClipper]. The
|
||||||
/// custom object will listen to this animation and update the clip whenever the
|
/// custom object will listen to this animation and update the clip whenever the
|
||||||
/// animation ticks, avoiding both the build and layout phases of the pipeline.
|
/// animation ticks, avoiding both the build and layout phases of the pipeline.
|
||||||
///
|
///
|
||||||
@ -1063,6 +1063,7 @@ class RenderBackdropFilter extends RenderProxyBox {
|
|||||||
/// * [ClipRRect], which can be customized with a [CustomClipper<RRect>].
|
/// * [ClipRRect], which can be customized with a [CustomClipper<RRect>].
|
||||||
/// * [ClipOval], which can be customized with a [CustomClipper<Rect>].
|
/// * [ClipOval], which can be customized with a [CustomClipper<Rect>].
|
||||||
/// * [ClipPath], which can be customized with a [CustomClipper<Path>].
|
/// * [ClipPath], which can be customized with a [CustomClipper<Path>].
|
||||||
|
/// * [ShapeBorderClipper], for specifying a clip path using a [ShapeBorder].
|
||||||
abstract class CustomClipper<T> {
|
abstract class CustomClipper<T> {
|
||||||
/// Creates a custom clipper.
|
/// Creates a custom clipper.
|
||||||
///
|
///
|
||||||
@ -1141,7 +1142,8 @@ class ShapeBorderClipper extends CustomClipper<Path> {
|
|||||||
if (oldClipper.runtimeType != ShapeBorderClipper)
|
if (oldClipper.runtimeType != ShapeBorderClipper)
|
||||||
return true;
|
return true;
|
||||||
final ShapeBorderClipper typedOldClipper = oldClipper;
|
final ShapeBorderClipper typedOldClipper = oldClipper;
|
||||||
return typedOldClipper.shape != shape;
|
return typedOldClipper.shape != shape
|
||||||
|
|| typedOldClipper.textDirection != textDirection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -690,6 +690,10 @@ class ClipOval extends SingleChildRenderObjectWidget {
|
|||||||
/// * To clip to a rectangle, consider [ClipRect].
|
/// * To clip to a rectangle, consider [ClipRect].
|
||||||
/// * To clip to an oval or circle, consider [ClipOval].
|
/// * To clip to an oval or circle, consider [ClipOval].
|
||||||
/// * To clip to a rounded rectangle, consider [ClipRRect].
|
/// * To clip to a rounded rectangle, consider [ClipRRect].
|
||||||
|
///
|
||||||
|
/// To clip to a particular [ShapeBorder], consider using either the
|
||||||
|
/// [ClipPath.shape] static method or the [ShapeBorderClipper] custom clipper
|
||||||
|
/// class.
|
||||||
class ClipPath extends SingleChildRenderObjectWidget {
|
class ClipPath extends SingleChildRenderObjectWidget {
|
||||||
/// Creates a path clip.
|
/// Creates a path clip.
|
||||||
///
|
///
|
||||||
@ -697,7 +701,38 @@ class ClipPath extends SingleChildRenderObjectWidget {
|
|||||||
/// size and location of the child. However, rather than use this default,
|
/// size and location of the child. However, rather than use this default,
|
||||||
/// consider using a [ClipRect], which can achieve the same effect more
|
/// consider using a [ClipRect], which can achieve the same effect more
|
||||||
/// efficiently.
|
/// efficiently.
|
||||||
const ClipPath({ Key key, this.clipper, this.clipBehavior = Clip.antiAlias, Widget child }) : super(key: key, child: child);
|
const ClipPath({
|
||||||
|
Key key,
|
||||||
|
this.clipper,
|
||||||
|
this.clipBehavior = Clip.antiAlias,
|
||||||
|
Widget child,
|
||||||
|
}) : super(key: key, child: child);
|
||||||
|
|
||||||
|
/// Creates a shape clip.
|
||||||
|
///
|
||||||
|
/// Uses a [ShapeBorderClipper] to configure the [ClipPath] to clip to the
|
||||||
|
/// given [ShapeBorder].
|
||||||
|
static Widget shape({
|
||||||
|
Key key,
|
||||||
|
@required ShapeBorder shape,
|
||||||
|
Clip clipBehavior = Clip.antiAlias,
|
||||||
|
Widget child,
|
||||||
|
}) {
|
||||||
|
assert(shape != null);
|
||||||
|
return Builder(
|
||||||
|
key: key,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return ClipPath(
|
||||||
|
clipper: ShapeBorderClipper(
|
||||||
|
shape: shape,
|
||||||
|
textDirection: Directionality.of(context),
|
||||||
|
),
|
||||||
|
clipBehavior: clipBehavior,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// If non-null, determines which clip to use.
|
/// If non-null, determines which clip to use.
|
||||||
///
|
///
|
||||||
|
@ -20,6 +20,9 @@ import 'image.dart';
|
|||||||
///
|
///
|
||||||
/// Commonly used with [BoxDecoration].
|
/// Commonly used with [BoxDecoration].
|
||||||
///
|
///
|
||||||
|
/// The [child] is not clipped. To clip a child to the shape of a particular
|
||||||
|
/// [ShapeDecoration], consider using a [ClipPath] widget.
|
||||||
|
///
|
||||||
/// {@tool sample}
|
/// {@tool sample}
|
||||||
///
|
///
|
||||||
/// This sample shows a radial gradient that draws a moon on a night sky:
|
/// This sample shows a radial gradient that draws a moon on a night sky:
|
||||||
@ -313,6 +316,9 @@ class Container extends StatelessWidget {
|
|||||||
/// A shorthand for specifying just a solid color is available in the
|
/// A shorthand for specifying just a solid color is available in the
|
||||||
/// constructor: set the `color` argument instead of the `decoration`
|
/// constructor: set the `color` argument instead of the `decoration`
|
||||||
/// argument.
|
/// argument.
|
||||||
|
///
|
||||||
|
/// The [child] is not clipped to the decoration. To clip a child to the shape
|
||||||
|
/// of a particular [ShapeDecoration], consider using a [ClipPath] widget.
|
||||||
final Decoration decoration;
|
final Decoration decoration;
|
||||||
|
|
||||||
/// The decoration to paint in front of the [child].
|
/// The decoration to paint in front of the [child].
|
||||||
|
@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import '../rendering/mock_canvas.dart';
|
import '../rendering/mock_canvas.dart';
|
||||||
|
import '../widgets/shape_decoration_test.dart' show TestBorder;
|
||||||
|
|
||||||
class NotifyMaterial extends StatelessWidget {
|
class NotifyMaterial extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
@ -237,6 +238,69 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('supports directional clips', (WidgetTester tester) async {
|
||||||
|
final List<String> logs = <String>[];
|
||||||
|
final ShapeBorder shape = TestBorder((String message) { logs.add(message); });
|
||||||
|
Widget buildMaterial() {
|
||||||
|
return Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
shape: shape,
|
||||||
|
child: const SizedBox(width: 100.0, height: 100.0),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final Widget material = buildMaterial();
|
||||||
|
// verify that a regular clip works as one would expect
|
||||||
|
logs.add('--0');
|
||||||
|
await tester.pumpWidget(material);
|
||||||
|
// verify that pumping again doesn't recompute the clip
|
||||||
|
// even though the widget itself is new (the shape doesn't change identity)
|
||||||
|
logs.add('--1');
|
||||||
|
await tester.pumpWidget(buildMaterial());
|
||||||
|
// verify that Material passes the TextDirection on to its shape when it's transparent
|
||||||
|
logs.add('--2');
|
||||||
|
await tester.pumpWidget(Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: material,
|
||||||
|
));
|
||||||
|
// verify that changing the text direction from LTR to RTL has an effect
|
||||||
|
// even though the widget itself is identical
|
||||||
|
logs.add('--3');
|
||||||
|
await tester.pumpWidget(Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: material,
|
||||||
|
));
|
||||||
|
// verify that pumping again with a text direction has no effect
|
||||||
|
logs.add('--4');
|
||||||
|
await tester.pumpWidget(Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: buildMaterial(),
|
||||||
|
));
|
||||||
|
logs.add('--5');
|
||||||
|
// verify that changing the text direction and the widget at the same time
|
||||||
|
// works as expected
|
||||||
|
await tester.pumpWidget(Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: material,
|
||||||
|
));
|
||||||
|
expect(logs, <String>[
|
||||||
|
'--0',
|
||||||
|
'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) null',
|
||||||
|
'paint Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) null',
|
||||||
|
'--1',
|
||||||
|
'--2',
|
||||||
|
'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
|
||||||
|
'paint Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
|
||||||
|
'--3',
|
||||||
|
'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.rtl',
|
||||||
|
'paint Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.rtl',
|
||||||
|
'--4',
|
||||||
|
'--5',
|
||||||
|
'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
|
||||||
|
'paint Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('PhysicalModels', () {
|
group('PhysicalModels', () {
|
||||||
|
@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
import '../rendering/mock_canvas.dart';
|
import '../rendering/mock_canvas.dart';
|
||||||
|
import 'shape_decoration_test.dart' show TestBorder;
|
||||||
|
|
||||||
final List<String> log = <String>[];
|
final List<String> log = <String>[];
|
||||||
|
|
||||||
@ -686,4 +687,61 @@ void main() {
|
|||||||
matchesGoldenFile('clip.PhysicalShape.default.png'),
|
matchesGoldenFile('clip.PhysicalShape.default.png'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('ClipPath.shape', (WidgetTester tester) async {
|
||||||
|
final List<String> logs = <String>[];
|
||||||
|
final ShapeBorder shape = TestBorder((String message) { logs.add(message); });
|
||||||
|
Widget buildClipPath() {
|
||||||
|
return ClipPath.shape(
|
||||||
|
shape: shape,
|
||||||
|
child: const SizedBox(width: 100.0, height: 100.0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final Widget clipPath = buildClipPath();
|
||||||
|
// verify that a regular clip works as one would expect
|
||||||
|
logs.add('--0');
|
||||||
|
await tester.pumpWidget(clipPath);
|
||||||
|
// verify that pumping again doesn't recompute the clip
|
||||||
|
// even though the widget itself is new (the shape doesn't change identity)
|
||||||
|
logs.add('--1');
|
||||||
|
await tester.pumpWidget(buildClipPath());
|
||||||
|
// verify that ClipPath passes the TextDirection on to its shape
|
||||||
|
logs.add('--2');
|
||||||
|
await tester.pumpWidget(Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: clipPath,
|
||||||
|
));
|
||||||
|
// verify that changing the text direction from LTR to RTL has an effect
|
||||||
|
// even though the widget itself is identical
|
||||||
|
logs.add('--3');
|
||||||
|
await tester.pumpWidget(Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: clipPath,
|
||||||
|
));
|
||||||
|
// verify that pumping again with a text direction has no effect
|
||||||
|
logs.add('--4');
|
||||||
|
await tester.pumpWidget(Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: buildClipPath(),
|
||||||
|
));
|
||||||
|
logs.add('--5');
|
||||||
|
// verify that changing the text direction and the widget at the same time
|
||||||
|
// works as expected
|
||||||
|
await tester.pumpWidget(Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: clipPath,
|
||||||
|
));
|
||||||
|
expect(logs, <String>[
|
||||||
|
'--0',
|
||||||
|
'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) null',
|
||||||
|
'--1',
|
||||||
|
'--2',
|
||||||
|
'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
|
||||||
|
'--3',
|
||||||
|
'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.rtl',
|
||||||
|
'--4',
|
||||||
|
'--5',
|
||||||
|
'getOuterPath Rect.fromLTRB(0.0, 0.0, 800.0, 600.0) TextDirection.ltr',
|
||||||
|
]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user