Add clip behaviour to Container (#44971)
This commit is contained in:
parent
c95dafc4c6
commit
f68cdacdd5
@ -213,6 +213,21 @@ class BoxDecoration extends Decoration {
|
||||
@override
|
||||
EdgeInsetsGeometry get padding => border?.dimensions;
|
||||
|
||||
@override
|
||||
Path getClipPath(Rect rect, TextDirection textDirection) {
|
||||
Path clipPath;
|
||||
switch (shape) {
|
||||
case BoxShape.circle:
|
||||
clipPath = Path()..addOval(rect);
|
||||
break;
|
||||
case BoxShape.rectangle:
|
||||
if (borderRadius != null)
|
||||
clipPath = Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
|
||||
break;
|
||||
}
|
||||
return clipPath;
|
||||
}
|
||||
|
||||
/// Returns a new box decoration that is scaled by the given factor.
|
||||
BoxDecoration scale(double factor) {
|
||||
return BoxDecoration(
|
||||
|
@ -165,6 +165,9 @@ abstract class Decoration extends Diagnosticable {
|
||||
/// omitted if there is no chance that the painter will change (for example,
|
||||
/// if it is a [BoxDecoration] with definitely no [DecorationImage]).
|
||||
BoxPainter createBoxPainter([ VoidCallback onChanged ]);
|
||||
|
||||
/// Returns a closed [Path] that describes the outer edge of this decoration.
|
||||
Path getClipPath(Rect rect, TextDirection textDirection) => null;
|
||||
}
|
||||
|
||||
/// A stateful class that can paint a particular [Decoration].
|
||||
|
@ -122,6 +122,11 @@ class ShapeDecoration extends Decoration {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Path getClipPath(Rect rect, TextDirection textDirection) {
|
||||
return shape.getOuterPath(rect, textDirection: textDirection);
|
||||
}
|
||||
|
||||
/// The color to fill in the background of the shape.
|
||||
///
|
||||
/// The color is under the [image].
|
||||
|
@ -312,10 +312,12 @@ class Container extends StatelessWidget {
|
||||
this.margin,
|
||||
this.transform,
|
||||
this.child,
|
||||
this.clipBehavior = Clip.none,
|
||||
}) : assert(margin == null || margin.isNonNegative),
|
||||
assert(padding == null || padding.isNonNegative),
|
||||
assert(decoration == null || decoration.debugAssertIsValid()),
|
||||
assert(constraints == null || constraints.debugAssertIsValid()),
|
||||
assert(clipBehavior != null),
|
||||
assert(color == null || decoration == null,
|
||||
'Cannot provide both a color and a decoration\n'
|
||||
'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".'
|
||||
@ -388,6 +390,11 @@ class Container extends StatelessWidget {
|
||||
/// The transformation matrix to apply before painting the container.
|
||||
final Matrix4 transform;
|
||||
|
||||
/// The clip behavior when [Container.decoration] has a clipPath.
|
||||
///
|
||||
/// Defaults to [Clip.none].
|
||||
final Clip clipBehavior;
|
||||
|
||||
EdgeInsetsGeometry get _paddingIncludingDecoration {
|
||||
if (decoration == null || decoration.padding == null)
|
||||
return padding;
|
||||
@ -436,6 +443,17 @@ class Container extends StatelessWidget {
|
||||
if (transform != null)
|
||||
current = Transform(transform: transform, child: current);
|
||||
|
||||
if (clipBehavior != Clip.none) {
|
||||
current = ClipPath(
|
||||
clipper: _DecorationClipper(
|
||||
textDirection: Directionality.of(context),
|
||||
decoration: decoration
|
||||
),
|
||||
clipBehavior: clipBehavior,
|
||||
child: current,
|
||||
);
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
@ -444,6 +462,7 @@ class Container extends StatelessWidget {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.none));
|
||||
properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
|
||||
@ -451,3 +470,26 @@ class Container extends StatelessWidget {
|
||||
properties.add(ObjectFlagProperty<Matrix4>.has('transform', transform));
|
||||
}
|
||||
}
|
||||
|
||||
/// A clipper that uses [Decoration.getClipPath] to clip.
|
||||
class _DecorationClipper extends CustomClipper<Path> {
|
||||
_DecorationClipper({
|
||||
TextDirection textDirection,
|
||||
@required this.decoration
|
||||
}) : assert (decoration != null),
|
||||
textDirection = textDirection ?? TextDirection.ltr;
|
||||
|
||||
final TextDirection textDirection;
|
||||
final Decoration decoration;
|
||||
|
||||
@override
|
||||
Path getClip(Size size) {
|
||||
return decoration.getClipPath(Offset.zero & size, textDirection);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(_DecorationClipper oldClipper) {
|
||||
return oldClipper.decoration != decoration
|
||||
|| oldClipper.textDirection != textDirection;
|
||||
}
|
||||
}
|
||||
|
@ -68,4 +68,18 @@ void main() {
|
||||
paints..rect(rect: Offset.zero & size),
|
||||
);
|
||||
});
|
||||
|
||||
test('BoxDecoration.getClipPath', () {
|
||||
const double radius = 10;
|
||||
final BoxDecoration decoration = BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
);
|
||||
const Rect rect = Rect.fromLTWH(0.0, 0.0, 100.0, 20.0);
|
||||
final Path clipPath = decoration.getClipPath(rect, TextDirection.ltr);
|
||||
final Matcher isLookLikeExpectedPath = isPathThat(
|
||||
includes: const <Offset>[ Offset(30.0, 10.0), Offset(50.0, 10.0), ],
|
||||
excludes: const <Offset>[ Offset(1.0, 1.0), Offset(99.0, 19.0), ],
|
||||
);
|
||||
expect(clipPath, isLookLikeExpectedPath);
|
||||
});
|
||||
}
|
||||
|
@ -98,6 +98,17 @@ void main() {
|
||||
);
|
||||
expect(log, isEmpty);
|
||||
});
|
||||
|
||||
test('ShapeDecoration.getClipPath', () {
|
||||
const ShapeDecoration decoration = ShapeDecoration(shape: CircleBorder(side: BorderSide.none));
|
||||
const Rect rect = Rect.fromLTWH(0.0, 0.0, 100.0, 20.0);
|
||||
final Path clipPath = decoration.getClipPath(rect, TextDirection.ltr);
|
||||
final Matcher isLookLikeExpectedPath = isPathThat(
|
||||
includes: const <Offset>[ Offset(50.0, 10.0), ],
|
||||
excludes: const <Offset>[ Offset(1.0, 1.0), Offset(30.0, 10.0), Offset(99.0, 19.0), ],
|
||||
);
|
||||
expect(clipPath, isLookLikeExpectedPath);
|
||||
});
|
||||
}
|
||||
|
||||
class TestImageProvider extends ImageProvider<TestImageProvider> {
|
||||
|
@ -498,6 +498,38 @@ void main() {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('giving clipBehaviour Clip.None, will not add a ClipPath to the tree', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(Container(
|
||||
clipBehavior: Clip.none,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(1),
|
||||
),
|
||||
child: const SizedBox(),
|
||||
));
|
||||
|
||||
expect(
|
||||
find.byType(ClipPath),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('giving clipBehaviour not a Clip.None, will add a ClipPath to the tree', (WidgetTester tester) async {
|
||||
final Container container = Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(1),
|
||||
),
|
||||
child: const SizedBox(),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(container);
|
||||
|
||||
expect(
|
||||
find.byType(ClipPath),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class _MockPaintingContext extends Mock implements PaintingContext {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user