diff --git a/components/ProgressView/src/MDCProgressView.h b/components/ProgressView/src/MDCProgressView.h index 10a98479009..a5bbb1d7528 100644 --- a/components/ProgressView/src/MDCProgressView.h +++ b/components/ProgressView/src/MDCProgressView.h @@ -151,6 +151,24 @@ IB_DESIGNABLE @property(nonatomic, copy, nullable) void (^traitCollectionDidChangeBlock) (MDCProgressView *_Nonnull progressView, UITraitCollection *_Nullable previousTraitCollection); +/** + The property that gates the NTC Determinate progress view changes. This enables the stop mark, + rounded corners, and gap. + + For accessibility, this should be enabled for most determinate progress indicators. However, it may + be disabled when the determinate track extends the full width of the screen. + + The default value is NO. + */ +@property(nonatomic, assign) BOOL enableDeterminateStopMark; + +/** + The color shown for the gap(s) between the progress bar(s) and the track. + + The default is clearColor, which results in an appearance with no visible gaps. + */ +@property(nonatomic, strong, nullable) UIColor *gapColor UI_APPEARANCE_SELECTOR; + @end NS_ASSUME_NONNULL_END diff --git a/components/ProgressView/src/MDCProgressView.m b/components/ProgressView/src/MDCProgressView.m index 779d9a39288..b13b6d89748 100644 --- a/components/ProgressView/src/MDCProgressView.m +++ b/components/ProgressView/src/MDCProgressView.m @@ -14,6 +14,9 @@ #import "MDCProgressView.h" +#import +#import + #include #import "MaterialPalettes.h" @@ -36,15 +39,47 @@ static const NSTimeInterval MDCProgressViewAnimationDuration = 0.25; +// Used by the NTC Determinate branch. +static const CGFloat MDCProgressViewGapWidth = 4.0; + +// This is the total duration from 0 to 1. A proportional amount of this duration is calculated +// based on the difference between two progress intervals. Used by the NTC Determinate branch. +static const CGFloat MDCProgressViewDeterminateDuration = 1.6; + +static NSString *const kProgressViewLayerAnimationKey = @"kProgressViewLayerAnimation"; +static NSString *const kProgressViewGapAnimationKey = @"kProgressViewGapAnimation"; + // The Bundle for string resources. static NSString *const kBundle = @"MaterialProgressView.bundle"; -@interface MDCProgressView () +@interface MDCProgressView () +// `progressView` and `indeterminateProgressView` are both used to create animations for +// Indeterminate mode. `progressView` is used for the first animation in the sequence, and +// `indeterminateProgressView` is used for the second animation. +// When `progressLayerView` is enabled, it is the only view used for Indeterminate animation. @property(nonatomic, strong) MDCProgressGradientView *progressView; @property(nonatomic, strong) MDCProgressGradientView *indeterminateProgressView; @property(nonatomic, strong) MDCProgressLayerView *progressLayerView; @property(nonatomic, strong) UIView *trackView; @property(nonatomic) BOOL animatingHide; + +// These properties are used for the NTC Determinate branch of the Progress view. +// The NTC Determinate branch is gated by the `enableDeterminateStopMark` property. +// In this branch, `progressView`, `indeterminateProgressView`, and `progressLayerView` are hidden +// when in Determinate mode. +@property(nonatomic, strong) UIView *determinateProgressView; +@property(nonatomic, strong) UIView *determinateStopMark; +@property(nonatomic, strong, nullable) CAShapeLayer *determinateProgressBarLayer; +@property(nonatomic, strong, nullable) CAShapeLayer *determinateProgressViewGapLayer; +@property(nonatomic, copy, nullable) void (^userCompletion)(BOOL finished); + +// `queuedProgress` represents a unary queue that stores the most recent value from `setProgress`. +// In Determinate mode, when a new `progress` value is set while the Determinate progress view is +// animating, this value is used for a follow-up animation. +// This is a nullable NSNumber instead of a CGFloat so that its unset state can be differentiated +// from a value of 0. +@property(nonatomic, strong, nullable) NSNumber *queuedProgress; + // A UIProgressView to return the same format for the accessibility value. For example, when // progress is 0.497, it reports "fifty per cent". @property(nonatomic, readonly) UIProgressView *accessibilityProgressView; @@ -110,12 +145,73 @@ - (void)willMoveToSuperview:(nullable UIView *)superview { - (void)layoutSubviews { [super layoutSubviews]; - // Don't update the views when the hide animation is in progress. - if (!self.animatingHide) { - [self updateProgressView]; - [self updateIndeterminateProgressView]; - [self updateTrackView]; - [self updateProgressLayerView]; + // NTC Determinate Branch + if (_enableDeterminateStopMark && _mode == MDCProgressViewModeDeterminate) { + // Set an initial width and height so that the stopmark can be drawn. + if (_determinateProgressView == nil) { + _determinateProgressView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 4, 4)]; + _determinateProgressView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _determinateProgressView.backgroundColor = _trackTintColor; + _determinateProgressView.translatesAutoresizingMaskIntoConstraints = NO; + _determinateProgressView.clipsToBounds = YES; + + if (self.effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { + _determinateProgressView.transform = CGAffineTransformMakeScale(-1, 1); + } + + [self addSubview:_determinateProgressView]; + + [NSLayoutConstraint activateConstraints:@[ + [_determinateProgressView.heightAnchor constraintEqualToAnchor:self.heightAnchor], + [_determinateProgressView.widthAnchor constraintEqualToAnchor:self.widthAnchor], + [_determinateProgressView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor], + [_determinateProgressView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor] + ]]; + } + if (_determinateProgressBarLayer == nil) { + _determinateProgressBarLayer = [self makeDeterminateProgressBarLayer]; + + if (self.cornerRadius > 0) { + _determinateProgressBarLayer.lineCap = kCALineCapButt; + _determinateProgressView.layer.cornerRadius = _cornerRadius; + _determinateProgressBarLayer.cornerRadius = _cornerRadius; + } + + [_determinateProgressView.layer addSublayer:_determinateProgressBarLayer]; + } + + if (_determinateProgressViewGapLayer == nil) { + [self configureDeterminateProgressGapLayer]; + } + + // StopMark must be configured after DeterminateProgressBar. + // StopMark is DeterminateProgressBar's subview, and is constrained against it. + if (_determinateStopMark == nil) { + _determinateStopMark = [self makeCircularStopMarkView]; + [_determinateStopMark.layer addSublayer:[self makeCircularStopMarkLayer]]; + _determinateStopMark.hidden = (_mode == MDCProgressViewModeIndeterminate); + } + + // Hide determinate progress view when in indeterminate mode. + _determinateProgressView.hidden = NO; + + // Hide non-determinate progress views when in determinate mode. + _progressView.hidden = YES; + _indeterminateProgressView.hidden = YES; + _progressLayerView.hidden = YES; + + if (!self.animatingHide) { + _determinateProgressView.frame = self.bounds; + } + } else { + _determinateProgressView.hidden = YES; + // Don't update the views when the hide animation is in progress. + if (!self.animatingHide) { + [self updateProgressView]; + [self updateIndeterminateProgressView]; + [self updateTrackView]; + [self updateProgressLayerView]; + } } } @@ -130,6 +226,30 @@ - (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitColl if (self.traitCollectionDidChangeBlock) { self.traitCollectionDidChangeBlock(self, previousTraitCollection); } + + // Reconfigure layers when trait collection changes in order to get the correct color. + if ([self.traitCollection + hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) { + if (_enableDeterminateStopMark && _mode == MDCProgressViewModeDeterminate) { + if (_determinateProgressBarLayer) { + [_determinateProgressBarLayer removeFromSuperlayer]; + _determinateProgressBarLayer = nil; + _determinateProgressBarLayer = [self makeDeterminateProgressBarLayer]; + [_determinateProgressView.layer addSublayer:_determinateProgressBarLayer]; + } + + if (_determinateProgressViewGapLayer) { + [_determinateProgressViewGapLayer removeFromSuperlayer]; + _determinateProgressViewGapLayer = nil; + [self configureDeterminateProgressGapLayer]; + } + + if (_determinateStopMark) { + _determinateStopMark.layer.sublayers = nil; + [_determinateStopMark.layer addSublayer:[self makeCircularStopMarkLayer]]; + } + } + } } - (void)setProgressTintColor:(nullable UIColor *)progressTintColor { @@ -138,29 +258,36 @@ - (void)setProgressTintColor:(nullable UIColor *)progressTintColor { if (progressTintColor != nil) { self.progressView.colors = @[ progressTintColor, progressTintColor ]; self.indeterminateProgressView.colors = @[ progressTintColor, progressTintColor ]; + _determinateProgressBarLayer.fillColor = progressTintColor.CGColor; } else { self.progressView.colors = nil; self.indeterminateProgressView.colors = nil; + _determinateProgressBarLayer.fillColor = nil; } } - (void)setProgressTintColors:(nullable NSArray *)progressTintColors { _progressTintColors = [progressTintColors copy]; - _progressTintColor = nil; + self.progressView.colors = _progressTintColors; self.indeterminateProgressView.colors = _progressTintColors; self.progressLayerView.frame = self.bounds; self.progressLayerView.colors = _progressTintColors; -} -- (nullable UIColor *)trackTintColor { - return self.trackView.backgroundColor; + // progressTintColors is not used in NTC Determinate branch. + // Since some clients set the tint color with this setter, use the first value if there is one + // for Determinate mode. + if (_enableDeterminateStopMark) { + if (_mode == MDCProgressViewModeDeterminate && progressTintColors.count > 0) { + _progressTintColor = progressTintColors[0]; + _determinateProgressBarLayer.fillColor = _progressTintColor.CGColor; + } + } else { + _progressTintColor = nil; + } } -- (void)setTrackTintColor:(nullable UIColor *)trackTintColor { - self.trackView.backgroundColor = trackTintColor; -} - (void)setMode:(MDCProgressViewMode)mode { if (_mode == mode) { @@ -169,6 +296,8 @@ - (void)setMode:(MDCProgressViewMode)mode { _mode = mode; self.indeterminateProgressView.hidden = (mode == MDCProgressViewModeDeterminate); + self.determinateStopMark.hidden = + (_mode == MDCProgressViewModeIndeterminate || !_enableDeterminateStopMark); } - (void)setCornerRadius:(CGFloat)cornerRadius { @@ -177,11 +306,21 @@ - (void)setCornerRadius:(CGFloat)cornerRadius { _progressView.layer.cornerRadius = cornerRadius; _indeterminateProgressView.layer.cornerRadius = cornerRadius; _trackView.layer.cornerRadius = cornerRadius; + _determinateProgressView.layer.cornerRadius = cornerRadius; + _determinateProgressBarLayer.cornerRadius = cornerRadius; BOOL hasNonZeroCornerRadius = !MDCCGFloatIsExactlyZero(cornerRadius); _progressView.clipsToBounds = hasNonZeroCornerRadius; _indeterminateProgressView.clipsToBounds = hasNonZeroCornerRadius; _trackView.clipsToBounds = hasNonZeroCornerRadius; + _determinateProgressView.clipsToBounds = hasNonZeroCornerRadius; +} + +- (void)setEnableDeterminateStopMark:(BOOL)enableDeterminateStopMark { + _enableDeterminateStopMark = enableDeterminateStopMark; + _determinateStopMark.hidden = + (_mode == MDCProgressViewModeIndeterminate || !enableDeterminateStopMark); + [self setNeedsLayout]; } - (void)setProgress:(float)progress { @@ -189,11 +328,18 @@ - (void)setProgress:(float)progress { progress = 1; if (progress < 0) progress = 0; - _progress = progress; - // Indeterminate mode ignores the progress. + if (!_enableDeterminateStopMark) { + _progress = progress; + } + + // Indeterminate mode ignores the progress property. if (_mode == MDCProgressViewModeIndeterminate) { return; } + + if (_enableDeterminateStopMark) { + [self setProgress:progress animated:NO completion:nil]; + } [self accessibilityValueDidChange]; [self setNeedsLayout]; } @@ -201,6 +347,8 @@ - (void)setProgress:(float)progress { - (void)setProgress:(float)progress animated:(BOOL)animated completion:(void (^__nullable)(BOOL finished))userCompletion { + // Indeterminate progress animation branch, with an early return. + // Indeterminate animation is handled in `startAnimatingBar`. if (_mode == MDCProgressViewModeIndeterminate) { self.progress = progress; if (userCompletion) { @@ -208,20 +356,48 @@ - (void)setProgress:(float)progress } return; } - if (progress < self.progress && - self.backwardProgressAnimationMode == MDCProgressViewBackwardAnimationModeReset) { - self.progress = 0; - [self updateProgressView]; - } - self.progress = progress; - [UIView animateWithDuration:animated ? [[self class] animationDuration] : 0 - delay:0 - options:[[self class] animationOptions] - animations:^{ - [self updateProgressView]; - } - completion:userCompletion]; + // NTC Determinate Branch - setProgress Animation + if (_enableDeterminateStopMark && _mode == MDCProgressViewModeDeterminate) { + // Completion block is called in `animationDidStop`. + _userCompletion = userCompletion; + + if (animated) { + if ([_determinateProgressBarLayer animationForKey:kProgressViewLayerAnimationKey]) { + _queuedProgress = @(progress); + return; + } + [self configureDeterminateAnimationsForProgress:progress]; + } else { + _queuedProgress = nil; + UIBezierPath *toPathForProgressLayer = [self makeToPathForBarWithProgress:progress]; + UIBezierPath *toPathForProgressGapLayer = [self makeToPathForGapWithProgress:progress]; + + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + // Use `setDisableActions` to disable implicit animations. + _determinateProgressBarLayer.path = toPathForProgressLayer.CGPath; + _determinateProgressViewGapLayer.path = toPathForProgressGapLayer.CGPath; + [CATransaction commit]; + } + + _progress = progress; + } else { + if (progress < self.progress && + self.backwardProgressAnimationMode == MDCProgressViewBackwardAnimationModeReset) { + self.progress = 0; + [self updateProgressView]; + } + + self.progress = progress; + [UIView animateWithDuration:animated ? [[self class] animationDuration] : 0 + delay:0 + options:[[self class] animationOptions] + animations:^{ + [self updateProgressView]; + } + completion:userCompletion]; + } } - (void)setHidden:(BOOL)hidden @@ -434,6 +610,15 @@ - (void)startAnimatingBar { // Determinate Monochromatic, Determinate Multichromatic, and Indeterminate Monochromatic // will continue to use MDCProgressGradientView. + // NTC Determinate branch is handled in `setProgress`, which uses `determinateProgressView`. + // The code path for indeterminate animations uses `progressView` and `indeterminateProgressView`, + // which are MDCProgressGradientViews. + // Note that this code path is for the Indeterminate animation, but it uses both `_progressView` + // and `_indeterminateProgressView` to create the Indeterminate animations. + if (_mode == MDCProgressViewModeDeterminate && _enableDeterminateStopMark) { + return; + } + if ([self isIndeterminateAndMultichromatic]) { _progressView.hidden = YES; [_progressView.shapeLayer removeAllAnimations]; @@ -520,6 +705,199 @@ - (void)startAnimatingBar { forKey:@"kIndeterminateProgressViewAnimation"]; } +#pragma mark - NTC Determinate Branch + +- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag { + if (!_enableDeterminateStopMark) { + return; + } + + // `animationDidStop` is necessary because, without a delegate call, we can only tell when a + // CAAnimation has completed successfully. `animationDidStop` notifies when an animation is + // stopped early or fails. + if (_userCompletion) { + _userCompletion(flag); + _userCompletion = nil; + } + + if (_queuedProgress != nil) { + [self configureDeterminateAnimationsForProgress:[_queuedProgress floatValue]]; + _queuedProgress = nil; + } +} + +- (void)setTrackTintColor:(nullable UIColor *)trackTintColor { + _trackTintColor = trackTintColor; + _trackView.backgroundColor = trackTintColor; + _determinateProgressView.backgroundColor = trackTintColor; +} + +- (void)setGapColor:(nullable UIColor *)gapColor { + _gapColor = gapColor; + _determinateProgressViewGapLayer.fillColor = gapColor.CGColor; +} + +- (UIView *)makeCircularStopMarkView { + UIView *determinateStopMark = [[UIView alloc] + initWithFrame:CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.height)]; + determinateStopMark.translatesAutoresizingMaskIntoConstraints = NO; + + [_determinateProgressView addSubview:determinateStopMark]; + + NSLayoutConstraint *xAnchor = + (self.effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft + ? [_determinateProgressView.leadingAnchor + constraintEqualToAnchor:determinateStopMark.leadingAnchor] + : [_determinateProgressView.trailingAnchor + constraintEqualToAnchor:determinateStopMark.trailingAnchor]); + + [NSLayoutConstraint activateConstraints:@[ + [determinateStopMark.heightAnchor constraintEqualToConstant:self.bounds.size.height], + [determinateStopMark.widthAnchor constraintEqualToConstant:self.bounds.size.height], + xAnchor, + [determinateStopMark.centerYAnchor + constraintEqualToAnchor:_determinateProgressView.centerYAnchor], + ]]; + + return determinateStopMark; +} + +- (CAShapeLayer *)makeCircularStopMarkLayer { + CGFloat circleDimension = self.bounds.size.height; + CAShapeLayer *circleLayer = [[CAShapeLayer alloc] init]; + circleLayer.frame = CGRectMake(0, 0, circleDimension, circleDimension); + circleLayer.path = [UIBezierPath bezierPathWithOvalInRect:circleLayer.bounds].CGPath; + circleLayer.fillColor = self.progressTintColor.CGColor; + return circleLayer; +} + +- (CAShapeLayer *)makeDeterminateProgressBarLayer { + CAShapeLayer *determinateProgressBarLayer = [CAShapeLayer layer]; + determinateProgressBarLayer.cornerRadius = _cornerRadius; + determinateProgressBarLayer.lineCap = kCALineCapButt; + determinateProgressBarLayer.frame = CGRectMake(0, 0, 0, self.bounds.size.height); + determinateProgressBarLayer.fillColor = _progressTintColor.CGColor; + + CGRect pathRect = CGRectMake(0, 0, 0, self.bounds.size.height); + + if (determinateProgressBarLayer.cornerRadius > 0) { + determinateProgressBarLayer.path = + [UIBezierPath bezierPathWithRoundedRect:pathRect cornerRadius:_cornerRadius].CGPath; + } else { + determinateProgressBarLayer.path = [UIBezierPath bezierPathWithRect:pathRect].CGPath; + } + + return determinateProgressBarLayer; +} + +- (UIBezierPath *)makeToPathForGapWithProgress:(CGFloat)progress { + CGRect rect = CGRectMake(self.bounds.size.width * progress + progress, 0, MDCProgressViewGapWidth, + self.bounds.size.height); + + CGFloat x = (progress == 0) ? rect.origin.x - MDCProgressViewGapWidth - 3 : rect.origin.x - 3; + CGFloat y = rect.origin.y - 1; + + // Draws a square shape with concave sides that looks like this: + // --- + // ) ( + // --- + // The sides are to account for rounded corners. + // This shape is used for the progress gap. + // It is based on a bar height of 4, and a corner radius of 2. + UIBezierPath *path = [UIBezierPath bezierPath]; + [path moveToPoint:CGPointMake(x + 8, y)]; + [path addLineToPoint:CGPointMake(x, y)]; + [path addLineToPoint:CGPointMake(x, y + 1)]; + [path addCurveToPoint:CGPointMake(x + 2, y + 3) + controlPoint1:CGPointMake(x + 1, y + 1) + controlPoint2:CGPointMake(x + 2, y + 2)]; + [path addCurveToPoint:CGPointMake(x, y + 5) + controlPoint1:CGPointMake(x + 2, y + 4) + controlPoint2:CGPointMake(x + 1, y + 5)]; + [path addLineToPoint:CGPointMake(x, y + 6)]; + [path addLineToPoint:CGPointMake(x + 8, y + 6)]; + [path addLineToPoint:CGPointMake(x + 8, y + 5)]; + [path addCurveToPoint:CGPointMake(x + 6, y + 3) + controlPoint1:CGPointMake(x + 7, y + 5) + controlPoint2:CGPointMake(x + 6, y + 4)]; + [path addCurveToPoint:CGPointMake(x + 8, y + 1) + controlPoint1:CGPointMake(x + 6, y + 2) + controlPoint2:CGPointMake(x + 7, y + 1)]; + [path addLineToPoint:CGPointMake(x + 8, y)]; + [path closePath]; + return path; +} + +- (UIBezierPath *)makeToPathForBarWithProgress:(CGFloat)progress { + CGFloat cornerRadiusOffset = (_cornerRadius > 0 ? _cornerRadius / 2 : 0); + CGRect rect = CGRectMake(0, 0, self.bounds.size.width * progress - cornerRadiusOffset, + self.bounds.size.height); + UIBezierPath *path; + + if (_cornerRadius > 0) { + path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:_cornerRadius]; + } else { + path = [UIBezierPath bezierPathWithRect:rect]; + } + return path; +} + +- (void)configureDeterminateProgressGapLayer { + CAShapeLayer *determinateProgressViewGapLayer = [CAShapeLayer layer]; + determinateProgressViewGapLayer.frame = CGRectMake(0, 0, 0, self.bounds.size.height); + determinateProgressViewGapLayer.fillColor = _gapColor.CGColor; + [_determinateProgressView.layer addSublayer:determinateProgressViewGapLayer]; + + UIBezierPath *path = [self makeToPathForGapWithProgress:_progress]; + determinateProgressViewGapLayer.path = path.CGPath; + _determinateProgressViewGapLayer = determinateProgressViewGapLayer; +} + +- (CGFloat)calculateProportionalDeterminateAnimationDurationFrom:(CGFloat)fromProgressPercent + to:(CGFloat)toProgressPercent { + CGFloat difference = ABS(toProgressPercent - fromProgressPercent); + CGFloat duration = difference * MDCProgressViewDeterminateDuration; + return duration; +} + +- (void)configureDeterminateAnimationsForProgress:(CGFloat)progress { + UIBezierPath *toPathForProgressLayer = [self makeToPathForBarWithProgress:progress]; + UIBezierPath *toPathForProgressGapLayer = [self makeToPathForGapWithProgress:progress]; + + CGPathRef originalProgressBarPath = _determinateProgressBarLayer.path; + NSValue *originalProgressBarPathValue = [NSValue valueWithPointer:originalProgressBarPath]; + + CGPathRef originalProgressGapPath = _determinateProgressViewGapLayer.path; + NSValue *originalProgressGapPathValue = [NSValue valueWithPointer:originalProgressGapPath]; + + CGFloat duration = [self calculateProportionalDeterminateAnimationDurationFrom:_progress + to:progress]; + + _determinateProgressBarLayer.path = toPathForProgressLayer.CGPath; + + CABasicAnimation *progressBarLayerPathAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; + progressBarLayerPathAnimation.duration = duration; + progressBarLayerPathAnimation.delegate = self; + progressBarLayerPathAnimation.fromValue = (id)([originalProgressBarPathValue pointerValue]); + progressBarLayerPathAnimation.fillMode = kCAFillModeForwards; + progressBarLayerPathAnimation.timingFunction = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + [_determinateProgressBarLayer addAnimation:progressBarLayerPathAnimation + forKey:kProgressViewLayerAnimationKey]; + + _determinateProgressViewGapLayer.path = toPathForProgressGapLayer.CGPath; + + CABasicAnimation *progressGapLayerPathAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; + // Add a slight duration increase to account for track bar overhang near animation end. + progressGapLayerPathAnimation.duration = duration + 0.01; + progressGapLayerPathAnimation.fromValue = (id)([originalProgressGapPathValue pointerValue]); + progressGapLayerPathAnimation.fillMode = kCAFillModeForwards; + progressGapLayerPathAnimation.timingFunction = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + [_determinateProgressViewGapLayer addAnimation:progressGapLayerPathAnimation + forKey:kProgressViewGapAnimationKey]; +} + @end NS_ASSUME_NONNULL_END diff --git a/components/ProgressView/src/private/MDCProgressGradientView.m b/components/ProgressView/src/private/MDCProgressGradientView.m index a94299cfbd7..e2fbda86d77 100644 --- a/components/ProgressView/src/private/MDCProgressGradientView.m +++ b/components/ProgressView/src/private/MDCProgressGradientView.m @@ -14,6 +14,8 @@ #import "MDCProgressGradientView.h" +#import + @interface MDCProgressGradientView () @property(nonatomic, readonly) CAGradientLayer *gradientLayer; @@ -65,7 +67,7 @@ - (void)layoutSubviews { self.shapeLayer.strokeColor = UIColor.blackColor.CGColor; self.shapeLayer.lineWidth = CGRectGetHeight(self.gradientLayer.bounds); if (self.gradientLayer.cornerRadius > 0) { - self.shapeLayer.lineCap = kCALineCapRound; + self.shapeLayer.lineCap = kCALineCapButt; } self.shapeLayer.path = path.CGPath; }