Skip to content

Commit

Permalink
[ProgressView] Reimplement animation on indeterminate mode to meet sp…
Browse files Browse the repository at this point in the history
…ecification.

PiperOrigin-RevId: 318119106
  • Loading branch information
Wenyu Zhang authored and material-automation committed Jun 24, 2020
1 parent b205164 commit 989635b
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 43 deletions.
3 changes: 3 additions & 0 deletions components/ProgressView/src/MDCProgressView.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ IB_DESIGNABLE
This is not equivalent to configuring self.layer.cornerRadius; it instead configures the progress
and track views directly.
Under @c MDCProgressViewModeIndeterminate mode, the progress view is fully rounded if this value
is larger than 0.
The default is 0.
*/
@property(nonatomic) CGFloat cornerRadius;
Expand Down
139 changes: 96 additions & 43 deletions components/ProgressView/src/MDCProgressView.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,12 @@

static const NSTimeInterval MDCProgressViewAnimationDuration = 0.25;

static const NSTimeInterval kAnimationDuration = 1.8;

static const CGFloat MDCProgressViewBarIndeterminateWidthPercentage = (CGFloat)0.52;

// The Bundle for string resources.
static NSString *const kBundle = @"MaterialProgressView.bundle";

@interface MDCProgressView ()
@property(nonatomic, strong) MDCProgressGradientView *progressView;
@property(nonatomic, strong) MDCProgressGradientView *indeterminateProgressView;
@property(nonatomic, strong) UIView *trackView;
@property(nonatomic) BOOL animatingHide;
// A UIProgressView to return the same format for the accessibility value. For example, when
Expand Down Expand Up @@ -82,15 +79,19 @@ - (void)commonMDCProgressViewInit {
_trackView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self addSubview:_trackView];

CGFloat barWidth = [self indeterminateLoadingBarWidth];
CGRect progressBarFrame = CGRectMake(-barWidth, 0, barWidth, CGRectGetHeight(self.bounds));

_progressView = [[MDCProgressGradientView alloc] initWithFrame:progressBarFrame];
_progressView = [[MDCProgressGradientView alloc] initWithFrame:CGRectZero];
[self addSubview:_progressView];

_indeterminateProgressView = [[MDCProgressGradientView alloc] initWithFrame:CGRectZero];
_indeterminateProgressView.hidden = YES;
[self addSubview:_indeterminateProgressView];

_progressView.colors = @[
(id)MDCProgressViewDefaultTintColor().CGColor, (id)MDCProgressViewDefaultTintColor().CGColor
];
_indeterminateProgressView.colors = @[
(id)MDCProgressViewDefaultTintColor().CGColor, (id)MDCProgressViewDefaultTintColor().CGColor
];
_trackView.backgroundColor =
[[self class] defaultTrackTintColorForProgressTintColor:MDCProgressViewDefaultTintColor()];
}
Expand All @@ -106,13 +107,9 @@ - (void)layoutSubviews {
// Don't update the views when the hide animation is in progress.
if (!self.animatingHide) {
[self updateProgressView];
[self updateIndeterminateProgressView];
[self updateTrackView];
}

if (_mode == MDCProgressViewModeIndeterminate && _animating) {
[self stopAnimating];
[self startAnimating];
}
}

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
Expand All @@ -121,6 +118,8 @@ - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
if (self.progressTintColor) {
self.progressView.colors =
@[ (id)self.progressTintColor.CGColor, (id)self.progressTintColor.CGColor ];
self.indeterminateProgressView.colors =
@[ (id)self.progressTintColor.CGColor, (id)self.progressTintColor.CGColor ];
}

if (self.traitCollectionDidChangeBlock) {
Expand All @@ -133,15 +132,19 @@ - (void)setProgressTintColor:(UIColor *)progressTintColor {
_progressTintColors = nil;
if (progressTintColor != nil) {
self.progressView.colors = @[ (id)progressTintColor.CGColor, (id)progressTintColor.CGColor ];
self.indeterminateProgressView.colors =
@[ (id)progressTintColor.CGColor, (id)progressTintColor.CGColor ];
} else {
self.progressView.colors = nil;
self.indeterminateProgressView.colors = nil;
}
}

- (void)setProgressTintColors:(NSArray *)progressTintColors {
_progressTintColors = [progressTintColors copy];
_progressTintColor = nil;
self.progressView.colors = _progressTintColors;
self.indeterminateProgressView.colors = _progressTintColors;
}

- (UIColor *)trackTintColor {
Expand All @@ -158,21 +161,19 @@ - (void)setMode:(MDCProgressViewMode)mode {
}
_mode = mode;

// If the progress bar is animating in indeterminate mode, restart the animation.
if (_animating && _mode == MDCProgressViewModeIndeterminate) {
[self stopAnimating];
[self startAnimating];
}
self.indeterminateProgressView.hidden = (mode == MDCProgressViewModeDeterminate);
}

- (void)setCornerRadius:(CGFloat)cornerRadius {
_cornerRadius = cornerRadius;

_progressView.layer.cornerRadius = cornerRadius;
_indeterminateProgressView.layer.cornerRadius = cornerRadius;
_trackView.layer.cornerRadius = cornerRadius;

BOOL hasNonZeroCornerRadius = !MDCCGFloatIsExactlyZero(cornerRadius);
_progressView.clipsToBounds = hasNonZeroCornerRadius;
_indeterminateProgressView.clipsToBounds = hasNonZeroCornerRadius;
_trackView.clipsToBounds = hasNonZeroCornerRadius;
}

Expand Down Expand Up @@ -332,11 +333,16 @@ - (NSString *)defaultAccessibilityLabel {
- (void)startAnimating {
[self startAnimatingBar];
_animating = YES;

[self setNeedsLayout];
}

- (void)stopAnimating {
_animating = NO;
[self.progressView.layer removeAllAnimations];
[self.progressView.shapeLayer removeAllAnimations];
[self.indeterminateProgressView.shapeLayer removeAllAnimations];

[self setNeedsLayout];
}

#pragma mark - Resource Bundle
Expand Down Expand Up @@ -382,20 +388,28 @@ + (UIColor *)defaultTrackTintColorForProgressTintColor:(UIColor *)progressTintCo
}

- (void)updateProgressView {
if (_mode == MDCProgressViewModeIndeterminate) {
return;
CGRect progressFrame = self.bounds;
if (_mode == MDCProgressViewModeDeterminate) {
// Update progressView with the current progress value.
CGFloat scale = self.window.screen.scale > 0 ? self.window.screen.scale : 1;
CGFloat pointWidth = self.progress * CGRectGetWidth(self.bounds);
CGFloat pixelAlignedWidth = MDCRound(pointWidth * scale) / scale;
progressFrame = CGRectMake(0, 0, pixelAlignedWidth, CGRectGetHeight(self.bounds));
} else {
if (!self.animating) {
progressFrame = CGRectZero;
}
}
// Update progressView with the current progress value.
CGFloat scale = self.window.screen.scale > 0 ? self.window.screen.scale : 1;
CGFloat pointWidth = self.progress * CGRectGetWidth(self.bounds);
CGFloat pixelAlignedWidth = MDCRound(pointWidth * scale) / scale;
CGRect progressFrame = CGRectMake(0, 0, pixelAlignedWidth, CGRectGetHeight(self.bounds));
if (self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
progressFrame = MDFRectFlippedHorizontally(progressFrame, CGRectGetWidth(self.bounds));
}
self.progressView.frame = progressFrame;
}

- (void)updateIndeterminateProgressView {
self.indeterminateProgressView.frame = self.animating ? self.bounds : CGRectZero;
}

- (void)updateTrackView {
const CGSize size = self.bounds.size;
self.trackView.frame = self.hidden ? CGRectMake(0.0, size.height, size.width, 0.0) : self.bounds;
Expand All @@ -407,23 +421,62 @@ - (void)startAnimatingBar {
return;
}

CGFloat barWidth = [self indeterminateLoadingBarWidth];
CGRect progressBarStartFrame = CGRectMake(-barWidth, 0, barWidth, CGRectGetHeight(self.bounds));
self.progressView.frame = progressBarStartFrame;

CGPoint progressBarEndPoint =
CGPointMake(self.progressView.layer.position.x + CGRectGetWidth(self.bounds) + barWidth,
CGRectGetHeight(self.bounds) / 2);
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.fromValue = [NSValue valueWithCGPoint:self.progressView.layer.position];
animation.toValue = [NSValue valueWithCGPoint:progressBarEndPoint];
animation.duration = kAnimationDuration;
animation.repeatCount = HUGE_VALF;
[self.progressView.layer addAnimation:animation forKey:@"position"];
}

- (CGFloat)indeterminateLoadingBarWidth {
return CGRectGetWidth(self.bounds) * MDCProgressViewBarIndeterminateWidthPercentage;
[self.progressView.shapeLayer removeAllAnimations];
[self.indeterminateProgressView.shapeLayer removeAllAnimations];

// The numeric values used here conform to https://material.io/components/progress-indicators.
CABasicAnimation *progressViewHead = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
progressViewHead.fromValue = @1;
progressViewHead.toValue = @0;
progressViewHead.duration = 0.75;
progressViewHead.timingFunction =
[[CAMediaTimingFunction alloc] initWithControlPoints:0.20f:0.00f:0.80f:1.00f];
progressViewHead.fillMode = kCAFillModeForwards;

CABasicAnimation *progressViewTail = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
progressViewTail.beginTime = 0.333;
progressViewTail.fromValue = @1;
progressViewTail.toValue = @0;
progressViewTail.duration = 0.85;
progressViewTail.timingFunction =
[[CAMediaTimingFunction alloc] initWithControlPoints:0.40f:0.00f:1.00f:1.00f];
progressViewTail.fillMode = kCAFillModeForwards;

CAAnimationGroup *progressViewAnimationGroup = [[CAAnimationGroup alloc] init];
progressViewAnimationGroup.animations = @[ progressViewHead, progressViewTail ];
progressViewAnimationGroup.duration = 1.8;
progressViewAnimationGroup.repeatCount = HUGE_VALF;

[self.progressView.shapeLayer addAnimation:progressViewAnimationGroup
forKey:@"kProgressViewAnimation"];

CABasicAnimation *indeterminateProgressViewHead =
[CABasicAnimation animationWithKeyPath:@"strokeStart"];
indeterminateProgressViewHead.fromValue = @1;
indeterminateProgressViewHead.toValue = @0;
indeterminateProgressViewHead.duration = 0.567;
indeterminateProgressViewHead.beginTime = 1;
indeterminateProgressViewHead.timingFunction =
[[CAMediaTimingFunction alloc] initWithControlPoints:0.00f:0.00f:0.65f:1.00f];
indeterminateProgressViewHead.fillMode = kCAFillModeBackwards;

CABasicAnimation *indeterminateProgressViewTail =
[CABasicAnimation animationWithKeyPath:@"strokeEnd"];
indeterminateProgressViewTail.beginTime = 1.267;
indeterminateProgressViewTail.fromValue = @1;
indeterminateProgressViewTail.toValue = @0;
indeterminateProgressViewTail.duration = 0.533;
indeterminateProgressViewTail.timingFunction =
[[CAMediaTimingFunction alloc] initWithControlPoints:0.10f:0.00f:0.45f:1.00f];
indeterminateProgressViewTail.fillMode = kCAFillModeBackwards;

CAAnimationGroup *indeterminateProgressViewAnimationGroup = [[CAAnimationGroup alloc] init];
indeterminateProgressViewAnimationGroup.animations =
@[ indeterminateProgressViewHead, indeterminateProgressViewTail ];
indeterminateProgressViewAnimationGroup.duration = 1.8;
indeterminateProgressViewAnimationGroup.repeatCount = HUGE_VALF;
[self.indeterminateProgressView.shapeLayer addAnimation:indeterminateProgressViewAnimationGroup
forKey:@"kIndeterminateProgressViewAnimation"];
}

@end
5 changes: 5 additions & 0 deletions components/ProgressView/src/private/MDCProgressGradientView.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,9 @@ __attribute__((objc_subclassing_restricted)) @interface MDCProgressGradientView
*/
@property(nonatomic, nullable, copy) NSArray *colors;

/**
The shape layer used as the mask of @c MDCProgressGradientView.
*/
@property(nonatomic, nonnull, readonly) CAShapeLayer *shapeLayer;

@end
20 changes: 20 additions & 0 deletions components/ProgressView/src/private/MDCProgressGradientView.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
@interface MDCProgressGradientView ()

@property(nonatomic, readonly) CAGradientLayer *gradientLayer;
@property(nonatomic, readwrite) CAShapeLayer *shapeLayer;

@end

Expand All @@ -41,6 +42,25 @@ - (instancetype)initWithCoder:(NSCoder *)coder {
- (void)commonMDCProgressGradientViewInit {
self.gradientLayer.startPoint = CGPointMake(0.0f, 0.5f);
self.gradientLayer.endPoint = CGPointMake(1.0f, 0.5f);

self.shapeLayer = [CAShapeLayer layer];
self.gradientLayer.mask = self.shapeLayer;
}

- (void)layoutSubviews {
[super layoutSubviews];

UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(CGRectGetWidth(self.gradientLayer.bounds),
CGRectGetMidY(self.gradientLayer.bounds))];
[path addLineToPoint:CGPointMake(0, CGRectGetMidY(self.gradientLayer.bounds))];
self.shapeLayer.frame = self.gradientLayer.bounds;
self.shapeLayer.strokeColor = UIColor.blackColor.CGColor;
self.shapeLayer.lineWidth = CGRectGetHeight(self.gradientLayer.bounds);
if (self.gradientLayer.cornerRadius > 0) {
self.shapeLayer.lineCap = kCALineCapRound;
}
self.shapeLayer.path = path.CGPath;
}

- (void)setColors:(NSArray *)colors {
Expand Down

0 comments on commit 989635b

Please sign in to comment.