Unverified Commit ee8754df authored by liyuqian's avatar liyuqian Committed by GitHub

Add customBorder to InkWell so it can clip ShapeBorder (#20751)

This fixes #20483 by letting InkWell do its own clipping.

PathOp.intersect is not used because we have too many unit tests that rely on clipping (e.g., paints..clipXXX()..drawCircle())

The goldens are updated due to small AA changes of the additional clipPath.
parent 363543fc
d0800cf4170e77682173c19703f605c6795e0ff8
cc98e28c974eea0bd9a8e24591857ae6b5479795
......@@ -189,6 +189,7 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
splashColor: widget.splashColor,
highlightColor: widget.highlightColor,
onTap: widget.onPressed,
customBorder: widget.shape,
child: IconTheme.merge(
data: new IconThemeData(color: widget.textStyle?.color),
child: new Container(
......
......@@ -41,12 +41,14 @@ class InkHighlight extends InteractiveInkFeature {
@required Color color,
BoxShape shape = BoxShape.rectangle,
BorderRadius borderRadius,
ShapeBorder customBorder,
RectCallback rectCallback,
VoidCallback onRemoved,
}) : assert(color != null),
assert(shape != null),
_shape = shape,
_borderRadius = borderRadius ?? BorderRadius.zero,
_customBorder = customBorder,
_rectCallback = rectCallback,
super(controller: controller, referenceBox: referenceBox, color: color, onRemoved: onRemoved) {
_alphaController = new AnimationController(duration: _kHighlightFadeDuration, vsync: controller.vsync)
......@@ -63,6 +65,7 @@ class InkHighlight extends InteractiveInkFeature {
final BoxShape _shape;
final BorderRadius _borderRadius;
final ShapeBorder _customBorder;
final RectCallback _rectCallback;
Animation<int> _alpha;
......@@ -97,6 +100,10 @@ class InkHighlight extends InteractiveInkFeature {
void _paintHighlight(Canvas canvas, Rect rect, Paint paint) {
assert(_shape != null);
canvas.save();
if (_customBorder != null) {
canvas.clipPath(_customBorder.getOuterPath(rect));
}
switch (_shape) {
case BoxShape.circle:
canvas.drawCircle(rect.center, Material.defaultSplashRadius, paint);
......@@ -114,6 +121,7 @@ class InkHighlight extends InteractiveInkFeature {
}
break;
}
canvas.restore();
}
@override
......
......@@ -48,6 +48,7 @@ class _InkRippleFactory extends InteractiveInkFeatureFactory {
bool containedInkWell = false,
RectCallback rectCallback,
BorderRadius borderRadius,
ShapeBorder customBorder,
double radius,
VoidCallback onRemoved,
}) {
......@@ -59,6 +60,7 @@ class _InkRippleFactory extends InteractiveInkFeatureFactory {
containedInkWell: containedInkWell,
rectCallback: rectCallback,
borderRadius: borderRadius,
customBorder: customBorder,
radius: radius,
onRemoved: onRemoved,
);
......@@ -115,12 +117,14 @@ class InkRipple extends InteractiveInkFeature {
bool containedInkWell = false,
RectCallback rectCallback,
BorderRadius borderRadius,
ShapeBorder customBorder,
double radius,
VoidCallback onRemoved,
}) : assert(color != null),
assert(position != null),
_position = position,
_borderRadius = borderRadius ?? BorderRadius.zero,
_customBorder = customBorder,
_targetRadius = radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position),
_clipCallback = _getClipCallback(referenceBox, containedInkWell, rectCallback),
super(controller: controller, referenceBox: referenceBox, color: color, onRemoved: onRemoved)
......@@ -172,6 +176,7 @@ class InkRipple extends InteractiveInkFeature {
final Offset _position;
final BorderRadius _borderRadius;
final ShapeBorder _customBorder;
final double _targetRadius;
final RectCallback _clipCallback;
......@@ -220,26 +225,6 @@ class InkRipple extends InteractiveInkFeature {
super.dispose();
}
RRect _clipRRectFromRect(Rect rect) {
return new RRect.fromRectAndCorners(
rect,
topLeft: _borderRadius.topLeft, topRight: _borderRadius.topRight,
bottomLeft: _borderRadius.bottomLeft, bottomRight: _borderRadius.bottomRight,
);
}
void _clipCanvasWithRect(Canvas canvas, Rect rect, {Offset offset}) {
Rect clipRect = rect;
if (offset != null) {
clipRect = clipRect.shift(offset);
}
if (_borderRadius != BorderRadius.zero) {
canvas.clipRRect(_clipRRectFromRect(clipRect));
} else {
canvas.clipRect(clipRect);
}
}
@override
void paintFeature(Canvas canvas, Matrix4 transform) {
final int alpha = _fadeInController.isAnimating ? _fadeIn.value : _fadeOut.value;
......@@ -251,22 +236,27 @@ class InkRipple extends InteractiveInkFeature {
Curves.ease.transform(_radiusController.value),
);
final Offset originOffset = MatrixUtils.getAsTranslation(transform);
canvas.save();
if (originOffset == null) {
canvas.save();
canvas.transform(transform.storage);
if (_clipCallback != null) {
_clipCanvasWithRect(canvas, _clipCallback());
}
canvas.drawCircle(center, _radius.value, paint);
canvas.restore();
} else {
if (_clipCallback != null) {
canvas.save();
_clipCanvasWithRect(canvas, _clipCallback(), offset: originOffset);
canvas.translate(originOffset.dx, originOffset.dy);
}
if (_clipCallback != null) {
final Rect rect = _clipCallback();
if (_customBorder != null) {
canvas.clipPath(_customBorder.getOuterPath(rect));
} else if (_borderRadius != BorderRadius.zero) {
canvas.clipRRect(new RRect.fromRectAndCorners(
rect,
topLeft: _borderRadius.topLeft, topRight: _borderRadius.topRight,
bottomLeft: _borderRadius.bottomLeft, bottomRight: _borderRadius.bottomRight,
));
} else {
canvas.clipRect(rect);
}
canvas.drawCircle(center + originOffset, _radius.value, paint);
if (_clipCallback != null)
canvas.restore();
}
canvas.drawCircle(center, _radius.value, paint);
canvas.restore();
}
}
......@@ -54,6 +54,7 @@ class _InkSplashFactory extends InteractiveInkFeatureFactory {
bool containedInkWell = false,
RectCallback rectCallback,
BorderRadius borderRadius,
ShapeBorder customBorder,
double radius,
VoidCallback onRemoved,
}) {
......@@ -65,6 +66,7 @@ class _InkSplashFactory extends InteractiveInkFeatureFactory {
containedInkWell: containedInkWell,
rectCallback: rectCallback,
borderRadius: borderRadius,
customBorder: customBorder,
radius: radius,
onRemoved: onRemoved,
);
......@@ -119,10 +121,12 @@ class InkSplash extends InteractiveInkFeature {
bool containedInkWell = false,
RectCallback rectCallback,
BorderRadius borderRadius,
ShapeBorder customBorder,
double radius,
VoidCallback onRemoved,
}) : _position = position,
_borderRadius = borderRadius ?? BorderRadius.zero,
_customBorder = customBorder,
_targetRadius = radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position),
_clipCallback = _getClipCallback(referenceBox, containedInkWell, rectCallback),
_repositionToReferenceBox = !containedInkWell,
......@@ -148,6 +152,7 @@ class InkSplash extends InteractiveInkFeature {
final Offset _position;
final BorderRadius _borderRadius;
final ShapeBorder _customBorder;
final double _targetRadius;
final RectCallback _clipCallback;
final bool _repositionToReferenceBox;
......@@ -185,26 +190,6 @@ class InkSplash extends InteractiveInkFeature {
super.dispose();
}
RRect _clipRRectFromRect(Rect rect) {
return new RRect.fromRectAndCorners(
rect,
topLeft: _borderRadius.topLeft, topRight: _borderRadius.topRight,
bottomLeft: _borderRadius.bottomLeft, bottomRight: _borderRadius.bottomRight,
);
}
void _clipCanvasWithRect(Canvas canvas, Rect rect, {Offset offset}) {
Rect clipRect = rect;
if (offset != null) {
clipRect = clipRect.shift(offset);
}
if (_borderRadius != BorderRadius.zero) {
canvas.clipRRect(_clipRRectFromRect(clipRect));
} else {
canvas.clipRect(clipRect);
}
}
@override
void paintFeature(Canvas canvas, Matrix4 transform) {
final Paint paint = new Paint()..color = color.withAlpha(_alpha.value);
......@@ -212,22 +197,27 @@ class InkSplash extends InteractiveInkFeature {
if (_repositionToReferenceBox)
center = Offset.lerp(center, referenceBox.size.center(Offset.zero), _radiusController.value);
final Offset originOffset = MatrixUtils.getAsTranslation(transform);
canvas.save();
if (originOffset == null) {
canvas.save();
canvas.transform(transform.storage);
if (_clipCallback != null) {
_clipCanvasWithRect(canvas, _clipCallback());
}
canvas.drawCircle(center, _radius.value, paint);
canvas.restore();
} else {
if (_clipCallback != null) {
canvas.save();
_clipCanvasWithRect(canvas, _clipCallback(), offset: originOffset);
canvas.translate(originOffset.dx, originOffset.dy);
}
if (_clipCallback != null) {
final Rect rect = _clipCallback();
if (_customBorder != null) {
canvas.clipPath(_customBorder.getOuterPath(rect));
} else if (_borderRadius != BorderRadius.zero) {
canvas.clipRRect(new RRect.fromRectAndCorners(
rect,
topLeft: _borderRadius.topLeft, topRight: _borderRadius.topRight,
bottomLeft: _borderRadius.bottomLeft, bottomRight: _borderRadius.bottomRight,
));
} else {
canvas.clipRect(rect);
}
canvas.drawCircle(center + originOffset, _radius.value, paint);
if (_clipCallback != null)
canvas.restore();
}
canvas.drawCircle(center, _radius.value, paint);
canvas.restore();
}
}
......@@ -95,6 +95,7 @@ abstract class InteractiveInkFeatureFactory {
bool containedInkWell = false,
RectCallback rectCallback,
BorderRadius borderRadius,
ShapeBorder customBorder,
double radius,
VoidCallback onRemoved,
});
......@@ -201,6 +202,7 @@ class InkResponse extends StatefulWidget {
this.highlightShape = BoxShape.circle,
this.radius,
this.borderRadius,
this.customBorder,
this.highlightColor,
this.splashColor,
this.splashFactory,
......@@ -285,11 +287,15 @@ class InkResponse extends StatefulWidget {
/// * [splashFactory], which defines the appearance of the splash.
final double radius;
/// The clipping radius of the containing rect.
/// The clipping radius of the containing rect. This is effective only if
/// [customBorder] is null.
///
/// If this is null, it is interpreted as [BorderRadius.zero].
final BorderRadius borderRadius;
/// The custom clip border which overrides [borderRadius].
final ShapeBorder customBorder;
/// The highlight color of the ink response. If this property is null then the
/// highlight color of the theme, [ThemeData.highlightColor], will be used.
///
......@@ -417,6 +423,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
color: widget.highlightColor ?? Theme.of(context).highlightColor,
shape: widget.highlightShape,
borderRadius: widget.borderRadius,
customBorder: widget.customBorder,
rectCallback: widget.getRectCallback(referenceBox),
onRemoved: _handleInkHighlightRemoval,
);
......@@ -445,6 +452,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
final Color color = widget.splashColor ?? Theme.of(context).splashColor;
final RectCallback rectCallback = widget.containedInkWell ? widget.getRectCallback(referenceBox) : null;
final BorderRadius borderRadius = widget.borderRadius;
final ShapeBorder customBorder = widget.customBorder;
InteractiveInkFeature splash;
void onRemoved() {
......@@ -466,6 +474,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
rectCallback: rectCallback,
radius: widget.radius,
borderRadius: borderRadius,
customBorder: customBorder,
onRemoved: onRemoved,
);
......@@ -626,6 +635,7 @@ class InkWell extends InkResponse {
InteractiveInkFeatureFactory splashFactory,
double radius,
BorderRadius borderRadius,
ShapeBorder customBorder,
bool enableFeedback = true,
bool excludeFromSemantics = false,
}) : super(
......@@ -644,6 +654,7 @@ class InkWell extends InkResponse {
splashFactory: splashFactory,
radius: radius,
borderRadius: borderRadius,
customBorder: customBorder,
enableFeedback: enableFeedback,
excludeFromSemantics: excludeFromSemantics,
);
......
......@@ -457,7 +457,7 @@ void main() {
await tester.tap(find.text('B'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 20));
expect(box, paints..circle(x: 200.0)..circle(x: 600.0));
expect(box, paints..circle(x: 200.0)..translate(x: 400.0)..circle(x: 200.0));
// Now we flip the directionality and verify that the circles switch positions.
await tester.pumpWidget(
......@@ -478,12 +478,23 @@ void main() {
),
);
expect(box, paints..circle(x: 600.0)..circle(x: 200.0));
expect(box, paints..translate()..save()..translate(x: 400.0)..circle(x: 200.0)..restore()..circle(x: 200.0));
await tester.tap(find.text('A'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 20));
expect(box, paints..circle(x: 600.0)..circle(x: 200.0)..circle(x: 600.0));
expect(
box,
paints
..translate(x: 0.0, y: 0.0)
..save()
..translate(x: 400.0)
..circle(x: 200.0)
..restore()
..circle(x: 200.0)
..translate(x: 400.0)
..circle(x: 200.0)
);
});
testWidgets('BottomNavigationBar inactiveIcon shown', (WidgetTester tester) async {
......
......@@ -40,8 +40,12 @@ void main() {
expect(
box,
paints
..clipRRect(rrect: new RRect.fromLTRBR(300.0, 270.0, 500.0, 330.0, const Radius.circular(6.0)))
..circle(x: 400.0, y: 300.0, radius: 21.0, color: splashColor)
..translate(x: 0.0, y: 0.0)
..save()
..translate(x: 300.0, y: 270.0)
..clipRRect(rrect: new RRect.fromLTRBR(0.0, 0.0, 200.0, 60.0, const Radius.circular(6.0)))
..circle(x: 100.0, y: 30.0, radius: 21.0, color: splashColor)
..restore()
..rrect(
rrect: new RRect.fromLTRBR(300.0, 270.0, 500.0, 330.0, const Radius.circular(6.0)),
color: highlightColor,
......@@ -86,93 +90,50 @@ void main() {
bool offsetsAreClose(Offset a, Offset b) => (a - b).distance < 1.0;
bool radiiAreClose(double a, double b) => (a - b).abs() < 1.0;
// Initially the ripple's center is where the tap occurred,
expect(box, paints..something((Symbol method, List<dynamic> arguments) {
if (method != #drawCircle)
return false;
final Offset center = arguments[0];
final double radius = arguments[1];
final Paint paint = arguments[2];
if (offsetsAreClose(center, tapDownOffset) && radius == 30.0 && paint.color.alpha == 0)
return true;
throw '''
Expected: center == $tapDownOffset, radius == 30.0, alpha == 0
Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
}));
PaintPattern ripplePattern(Offset expectedCenter, double expectedRadius, int expectedAlpha) {
return paints
..translate(x: 0.0, y: 0.0)
..translate(x: tapDownOffset.dx, y: tapDownOffset.dy)
..something((Symbol method, List<dynamic> arguments) {
if (method != #drawCircle)
return false;
final Offset center = arguments[0];
final double radius = arguments[1];
final Paint paint = arguments[2];
if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == expectedAlpha)
return true;
throw '''
Expected: center == $expectedCenter, radius == $expectedRadius, alpha == $expectedAlpha
Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
}
);
}
// Initially the ripple's center is where the tap occurred. Note that
// ripplePattern always add a translation of tapDownOffset.
expect(box, ripplePattern(Offset.zero, 30.0, 0));
// The ripple fades in for 75ms. During that time its alpha is eased from
// 0 to the splashColor's alpha value and its center moves towards the
// center of the ink well.
await tester.pump(const Duration(milliseconds: 50));
expect(box, paints..something((Symbol method, List<dynamic> arguments) {
if (method != #drawCircle)
return false;
final Offset center = arguments[0];
final double radius = arguments[1];
final Paint paint = arguments[2];
final Offset expectedCenter = tapDownOffset + const Offset(17.0, 17.0);
const double expectedRadius = 56.0;
if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == 120)
return true;
throw '''
Expected: center == $expectedCenter, radius == $expectedRadius, alpha == 120
Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
}));
expect(box, ripplePattern(const Offset(17.0, 17.0), 56.0, 120));
// At 75ms the ripple has fade in: it's alpha matches the splashColor's
// alpha and its center has moved closer to the ink well's center.
await tester.pump(const Duration(milliseconds: 25));
expect(box, paints..something((Symbol method, List<dynamic> arguments) {
if (method != #drawCircle)
return false;
final Offset center = arguments[0];
final double radius = arguments[1];
final Paint paint = arguments[2];
final Offset expectedCenter = tapDownOffset + const Offset(29.0, 29.0);
const double expectedRadius = 73.0;
if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == 180)
return true;
throw '''
Expected: center == $expectedCenter, radius == $expectedRadius, alpha == 180
Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
}));
expect(box, ripplePattern(const Offset(29.0, 29.0), 73.0, 180));
// At this point the splash radius has expanded to its limit: 5 past the
// ink well's radius parameter. The splash center has moved to its final
// location at the inkwell's center and the fade-out is about to start.
// The fade-out begins at 225ms = 50ms + 25ms + 150ms.
await tester.pump(const Duration(milliseconds: 150));
expect(box, paints..something((Symbol method, List<dynamic> arguments) {
if (method != #drawCircle)
return false;
final Offset center = arguments[0];
final double radius = arguments[1];
final Paint paint = arguments[2];
final Offset expectedCenter = inkWellCenter;
const double expectedRadius = 105.0;
if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == 180)
return true;
throw '''
Expected: center == $expectedCenter, radius == $expectedRadius, alpha == 180
Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
}));
expect(box, ripplePattern(inkWellCenter - tapDownOffset, 105.0, 180));
// After another 150ms the fade-out is complete.
await tester.pump(const Duration(milliseconds: 150));
expect(box, paints..something((Symbol method, List<dynamic> arguments) {
if (method != #drawCircle)
return false;
final Offset center = arguments[0];
final double radius = arguments[1];
final Paint paint = arguments[2];
final Offset expectedCenter = inkWellCenter;
const double expectedRadius = 105.0;
if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == 0)
return true;
throw '''
Expected: center == $expectedCenter, radius == $expectedRadius, alpha == 0
Found: center == $center radius == $radius alpha == ${paint.color.alpha}''';
}));
expect(box, ripplePattern(inkWellCenter - tapDownOffset, 105.0, 0));
});
testWidgets('Does the Ink widget render anything', (WidgetTester tester) async {
......
......@@ -19,6 +19,7 @@ class TestInkSplash extends InkSplash {
bool containedInkWell = false,
RectCallback rectCallback,
BorderRadius borderRadius,
ShapeBorder customBorder,
double radius,
VoidCallback onRemoved,
}) : super(
......@@ -29,6 +30,7 @@ class TestInkSplash extends InkSplash {
containedInkWell: containedInkWell,
rectCallback: rectCallback,
borderRadius: borderRadius,
customBorder: customBorder,
radius: radius,
onRemoved: onRemoved,
);
......@@ -58,6 +60,7 @@ class TestInkSplashFactory extends InteractiveInkFeatureFactory {
bool containedInkWell = false,
RectCallback rectCallback,
BorderRadius borderRadius,
ShapeBorder customBorder,
double radius,
VoidCallback onRemoved,
}) {
......@@ -69,6 +72,7 @@ class TestInkSplashFactory extends InteractiveInkFeatureFactory {
containedInkWell: containedInkWell,
rectCallback: rectCallback,
borderRadius: borderRadius,
customBorder: customBorder,
radius: radius,
onRemoved: onRemoved,
);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment