Skip to content
This repository was archived by the owner on Dec 11, 2025. It is now read-only.

Commit 266bcd2

Browse files
Nobodymaterial-automation
authored andcommitted
There are two separate systems trying to manage touch events on the MDCButton. First, the button itself uses its standard UIControl event infrastructure to trigger ink ripples. Second, an external MDCInkTouchController attaches its own gesture recognizers to do the same thing. This creates a conflict where both systems respond to the same touch, causing two ink animations to be created and displayed simultaneously, which results in incorrect visual feedback.
PiperOrigin-RevId: 770704081
1 parent 72aeb25 commit 266bcd2

File tree

4 files changed

+46
-1
lines changed

4 files changed

+46
-1
lines changed

components/Ink/src/MDCInkView.m

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ @interface MDCInkView () <CALayerDelegate, MDCInkLayerDelegate, MDCLegacyInkLaye
4343

4444
@end
4545

46-
@implementation MDCInkView
46+
@implementation MDCInkView {
47+
BOOL _isActiveInkLayerAnimationRunning;
48+
}
4749

4850
+ (Class)layerClass {
4951
return [MDCLegacyInkLayer class];
@@ -179,6 +181,13 @@ - (void)startTouchBeganAtPoint:(CGPoint)point
179181
if (self.usesLegacyInkRipple) {
180182
[self.inkLayer spreadFromPoint:point completion:completionBlock];
181183
} else {
184+
@synchronized(self) {
185+
if (animated && _isActiveInkLayerAnimationRunning) {
186+
// Only one ink layer animation can be running at a time.
187+
return;
188+
}
189+
_isActiveInkLayerAnimationRunning = YES;
190+
}
182191
self.startInkRippleCompletionBlock = completionBlock;
183192
MDCInkLayer *inkLayer = [MDCInkLayer layer];
184193
inkLayer.inkColor = self.inkColor;
@@ -277,6 +286,14 @@ - (void)inkLayerAnimationDidStart:(MDCInkLayer *)inkLayer {
277286
}
278287
}
279288

289+
- (void)inkLayerStartAnimationDidFinish:(MDCInkLayer *)inkLayer {
290+
@synchronized(self) {
291+
if (self.activeInkLayer == inkLayer && _isActiveInkLayerAnimationRunning) {
292+
_isActiveInkLayerAnimationRunning = NO;
293+
}
294+
}
295+
}
296+
280297
- (void)inkLayerAnimationDidEnd:(MDCInkLayer *)inkLayer {
281298
if (self.activeInkLayer == inkLayer && self.endInkRippleCompletionBlock) {
282299
self.endInkRippleCompletionBlock();

components/Ink/src/private/MDCInkLayer.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ - (void)startInkAtPoint:(CGPoint)point animated:(BOOL)animated {
165165
animGroup.removedOnCompletion = NO;
166166
[CATransaction setCompletionBlock:^{
167167
self->_startAnimationActive = NO;
168+
if ([self.animationDelegate respondsToSelector:@selector(inkLayerStartAnimationDidFinish:)]) {
169+
[self.animationDelegate inkLayerStartAnimationDidFinish:self];
170+
}
168171
}];
169172
[self addAnimation:animGroup forKey:nil];
170173
[CATransaction commit];

components/Ink/src/private/MDCInkLayerDelegate.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ NS_SWIFT_UI_ACTOR
3838
*/
3939
- (void)inkLayerAnimationDidStart:(nonnull MDCInkLayer *)inkLayer;
4040

41+
/**
42+
Called when the ink ripple appearing animation: scale up, reposition and fade in animation,
43+
finishes.
44+
*/
45+
- (void)inkLayerStartAnimationDidFinish:(nonnull MDCInkLayer *)inkLayer;
46+
4147
/**
4248
Called when the ink ripple animation ends.
4349

components/Ink/tests/unit/MDCInkViewTests.m

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,23 @@ - (void)testInkViewLayerDoesntMaskToBoundsWithInkStyleUnbounded {
164164
XCTAssertFalse(masksToBoundsWhenUnbounded);
165165
}
166166

167+
- (void)testStartTouchBeganAtPointAddsInkLayerWithAnimationIsIdempotent {
168+
// Given
169+
MDCInkView *testInkView = [[MDCInkView alloc] init];
170+
testInkView.usesLegacyInkRipple = NO;
171+
172+
// Verifies that only one ink animation layer is added, even with concurrent triggers.
173+
// The initial sublayer count is 1 due to the button's default shape-drawing layer.
174+
// After adding the ink layer via startTouchBeganAtPoint:, the count must be 2.
175+
// This assertion ensures subsequent calls do not erroneously add more layers.
176+
[testInkView startTouchBeganAtPoint:CGPointZero animated:YES withCompletion:nil];
177+
[testInkView startTouchBeganAtPoint:CGPointZero animated:YES withCompletion:nil];
178+
179+
XCTAssertEqual(testInkView.layer.sublayers.count, 2);
180+
181+
// Verifies that the ink layer is removed when cancelAllAnimationsAnimated:NO is called.
182+
[testInkView cancelAllAnimationsAnimated:NO];
183+
XCTAssertEqual(testInkView.layer.sublayers.count, 1);
184+
}
185+
167186
@end

0 commit comments

Comments
 (0)