Add clip behaviour to Container (#44971)
This commit is contained in:
parent
c95dafc4c6
commit
f68cdacdd5
@ -213,6 +213,21 @@ class BoxDecoration extends Decoration {
|
|||||||
@override
|
@override
|
||||||
EdgeInsetsGeometry get padding => border?.dimensions;
|
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.
|
/// Returns a new box decoration that is scaled by the given factor.
|
||||||
BoxDecoration scale(double factor) {
|
BoxDecoration scale(double factor) {
|
||||||
return BoxDecoration(
|
return BoxDecoration(
|
||||||
|
@ -165,6 +165,9 @@ abstract class Decoration extends Diagnosticable {
|
|||||||
/// omitted if there is no chance that the painter will change (for example,
|
/// omitted if there is no chance that the painter will change (for example,
|
||||||
/// if it is a [BoxDecoration] with definitely no [DecorationImage]).
|
/// if it is a [BoxDecoration] with definitely no [DecorationImage]).
|
||||||
BoxPainter createBoxPainter([ VoidCallback onChanged ]);
|
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].
|
/// 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 to fill in the background of the shape.
|
||||||
///
|
///
|
||||||
/// The color is under the [image].
|
/// The color is under the [image].
|
||||||
|
@ -312,10 +312,12 @@ class Container extends StatelessWidget {
|
|||||||
this.margin,
|
this.margin,
|
||||||
this.transform,
|
this.transform,
|
||||||
this.child,
|
this.child,
|
||||||
|
this.clipBehavior = Clip.none,
|
||||||
}) : assert(margin == null || margin.isNonNegative),
|
}) : assert(margin == null || margin.isNonNegative),
|
||||||
assert(padding == null || padding.isNonNegative),
|
assert(padding == null || padding.isNonNegative),
|
||||||
assert(decoration == null || decoration.debugAssertIsValid()),
|
assert(decoration == null || decoration.debugAssertIsValid()),
|
||||||
assert(constraints == null || constraints.debugAssertIsValid()),
|
assert(constraints == null || constraints.debugAssertIsValid()),
|
||||||
|
assert(clipBehavior != null),
|
||||||
assert(color == null || decoration == null,
|
assert(color == null || decoration == null,
|
||||||
'Cannot provide both a color and a decoration\n'
|
'Cannot provide both a color and a decoration\n'
|
||||||
'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".'
|
'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.
|
/// The transformation matrix to apply before painting the container.
|
||||||
final Matrix4 transform;
|
final Matrix4 transform;
|
||||||
|
|
||||||
|
/// The clip behavior when [Container.decoration] has a clipPath.
|
||||||
|
///
|
||||||
|
/// Defaults to [Clip.none].
|
||||||
|
final Clip clipBehavior;
|
||||||
|
|
||||||
EdgeInsetsGeometry get _paddingIncludingDecoration {
|
EdgeInsetsGeometry get _paddingIncludingDecoration {
|
||||||
if (decoration == null || decoration.padding == null)
|
if (decoration == null || decoration.padding == null)
|
||||||
return padding;
|
return padding;
|
||||||
@ -436,6 +443,17 @@ class Container extends StatelessWidget {
|
|||||||
if (transform != null)
|
if (transform != null)
|
||||||
current = Transform(transform: transform, child: current);
|
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;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -444,6 +462,7 @@ class Container extends StatelessWidget {
|
|||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
|
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, 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>('bg', decoration, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
|
properties.add(DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, 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));
|
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),
|
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);
|
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> {
|
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 {}
|
class _MockPaintingContext extends Mock implements PaintingContext {}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user