我已经制作了完整的工作包,可以模仿您发布的这个进度视图。但是,为了使其更可定制,我没有使用任何图像,而是使用 CoreGraphics 来绘制它。可以在lightdesign/LDProgressView 找到该软件包。如果你知道那是什么,我也可能会将它变成 CocoaPod。
如何绘制类似 KAYAK 的进度视图
进度视图的所有内部工作原理以及如何模仿 KAYAK 进度视图可以在this file 中找到。为了便于理解,我在代码块中添加了一些 cmets。这是drawRect 方法:
- (void)drawRect:(CGRect)rect {
[self setAnimateIfNotSet];
CGContextRef context = UIGraphicsGetCurrentContext();
[self drawProgressBackground:context inRect:rect];
if (self.progress > 0) {
[self drawProgress:context withFrame:rect];
}
}
这是不言自明的。如果尚未设置动画属性,我将设置它并绘制背景。然后,如果进度大于0,我将在总帧内绘制进度。让我们继续 drawProgressBackground:inRect: 方法:
- (void)drawProgressBackground:(CGContextRef)context inRect:(CGRect)rect {
CGContextSaveGState(context);
// Draw the background with a gray color within a rounded rectangle
UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:10];
CGContextSetFillColorWithColor(context, [UIColor colorWithRed:0.51f green:0.51f blue:0.51f alpha:1.00f].CGColor);
[roundedRect fill];
// Create the inner shadow path
UIBezierPath *roundedRectangleNegativePath = [UIBezierPath bezierPathWithRect:CGRectMake(-10, -10, rect.size.width+10, rect.size.height+10)];
[roundedRectangleNegativePath appendPath:roundedRect];
roundedRectangleNegativePath.usesEvenOddFillRule = YES;
CGSize shadowOffset = CGSizeMake(0.5, 1);
CGContextSaveGState(context);
CGFloat xOffset = shadowOffset.width + round(rect.size.width);
CGFloat yOffset = shadowOffset.height;
CGContextSetShadowWithColor(context,
CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)), 5, [[UIColor blackColor] colorWithAlphaComponent:0.7].CGColor);
// Draw the inner shadow
[roundedRect addClip];
CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(rect.size.width), 0);
[roundedRectangleNegativePath applyTransform:transform];
[[UIColor grayColor] setFill];
[roundedRectangleNegativePath fill];
CGContextRestoreGState(context);
}
在这里,我在视图中创建一个圆角矩形,半径为10(我以后可能允许对其进行自定义)并填充它。然后剩下的代码是绘制内阴影,我真的不需要详细说明。现在,这是在方法drawProgress:withFrame:中绘制进度的代码:
- (void)drawProgress:(CGContextRef)context withFrame:(CGRect)frame {
CGRect rectToDrawIn = CGRectMake(0, 0, frame.size.width * self.progress, frame.size.height);
CGRect insetRect = CGRectInset(rectToDrawIn, 0.5, 0.5);
UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:insetRect cornerRadius:10];
if ([self.flat boolValue]) {
CGContextSetFillColorWithColor(context, self.color.CGColor);
[roundedRect fill];
} else {
CGContextSaveGState(context);
[roundedRect addClip];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[] = {0.0, 1.0};
NSArray *colors = @[(__bridge id)[self.color lighterColor].CGColor, (__bridge id)[self.color darkerColor].CGColor];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations);
CGContextDrawLinearGradient(context, gradient, CGPointMake(insetRect.size.width / 2, 0), CGPointMake(insetRect.size.width / 2, insetRect.size.height), 0);
CGContextRestoreGState(context);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
CGContextSetStrokeColorWithColor(context, [[self.color darkerColor] darkerColor].CGColor);
[self drawStripes:context inRect:insetRect];
[roundedRect stroke];
[self drawRightAlignedLabelInRect:insetRect];
}
此方法有 4 个主要部分。首先,我根据self.progress 属性计算进度将占用的帧。其次,如果设置了flat 属性,我绘制纯色,或者绘制计算渐变(方法lighterColor 和darkerColor 属于UIColor 类别)。第三,我画条纹,最后画百分比标签。让我们快速介绍这两种方法。这是drawStripes:inRect: 方法:
- (void)drawStripes:(CGContextRef)context inRect:(CGRect)rect {
CGContextSaveGState(context);
[[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:10] addClip];
CGContextSetFillColorWithColor(context, [[UIColor whiteColor] colorWithAlphaComponent:0.2].CGColor);
CGFloat xStart = self.offset, height = rect.size.height, width = STRIPE_WIDTH;
while (xStart < rect.size.width) {
CGContextSaveGState(context);
CGContextMoveToPoint(context, xStart, height);
CGContextAddLineToPoint(context, xStart + width * 0.25, 0);
CGContextAddLineToPoint(context, xStart + width * 0.75, 0);
CGContextAddLineToPoint(context, xStart + width * 0.50, height);
CGContextClosePath(context);
CGContextFillPath(context);
CGContextRestoreGState(context);
xStart += width;
}
CGContextRestoreGState(context);
}
这就是动画“魔法”发生的地方。本质上,我根据self.offset 绘制这些条纹,它介于-STRIPE_WIDTH 和0 之间,由计时器递增。然后,我创建了一个简单的循环,这样我只创建了足够的条纹来完全填充视图的进度部分。我还将 STRIPE_WIDTH 的 25% 留空,这样条纹就不会相互堆积。下面是最终的绘制方法drawRightAlignedLabelInRect::
- (void)drawRightAlignedLabelInRect:(CGRect)rect {
UILabel *label = [[UILabel alloc] initWithFrame:rect];
label.backgroundColor = [UIColor clearColor];
label.textAlignment = NSTextAlignmentRight;
label.text = [NSString stringWithFormat:@"%.0f%%", self.progress*100];
label.font = [UIFont boldSystemFontOfSize:17];
UIColor *baseLabelColor = [self.color isLighterColor] ? [UIColor blackColor] : [UIColor whiteColor];
label.textColor = [baseLabelColor colorWithAlphaComponent:0.6];
[label drawTextInRect:CGRectOffset(rect, -6, 0)];
}
在这种方法中,我创建了一个带有文本的标签,该标签从浮点数(在0.0 和1.0 之间)转换为百分比(从0% 到100%)。然后我根据所选进度颜色的暗度将颜色设置为暗或亮,并在CGContext 中绘制标签。
可定制性
可以直接在LDProgressView 的实例上或在UIAppearance 方法中预先设置三个属性。
颜色显然会设置选择器的总体外观。渐变、条纹和/或轮廓颜色由此确定。 UIAppearance 方法是这样的:
[[LDProgressView appearance] setColor:[UIColor colorWithRed:0.87f green:0.55f blue:0.09f alpha:1.00f]];
这将决定进度视图的背景是渐变还是color 属性。这个UIAppearance 方法看起来像这样:
[[LDProgressView appearance] setFlat:@NO];
最后,这将决定条纹是否会被动画化。这个UIAppearance 方法也可以为LDProgressView 的所有实例通用设置,如下所示:
[[LDProgressView appearance] setAnimate:@YES];
结论
哇!那是一个很长的答案。我希望我没有让你们太无聊。如果您只是跳到这里,这里是使用代码而不是图像绘制的要点。如果您有时间/经验,我认为 CoreGraphics 是一种在 iOS 上绘图的好方法,因为它允许更多的自定义,而且我相信往往会更快。
这是最终工作产品的图片: