3

我想知道如何制作一个看起来像 KAYAK 应用程序(这是一个搜索航班和酒店的旅行应用程序)中的进度视图,截图:

在此处输入图像描述

我在越狱的 iPhone 上挖掘了 KAYAK 应用程序的资源,发现以下 3 张图像构成了这个进度视图:

进度条-背景@2x.png

在此处输入图像描述

进度条渐变@2x.png

在此处输入图像描述

进度条覆盖@2x.png

在此处输入图像描述

假设进度视图有一个移动的覆盖图像,它与梯度图像一起重复移动。

任何想法或示例代码将不胜感激。

4

8 回答 8

7

I've made entire working package that will mimic this progress view you have posted. However, to make it more customizable, I have not used any images, but used CoreGraphics to draw it. The package can be found at lightdesign/LDProgressView. I'll also probably make it into a CocoaPod if you know what that is.

How To Draw A KAYAK-like Progress View

All of the inner workings of the progress view and therefore how to mimic the KAYAK progress view can be found in this file. I've added some comments here in the code blocks for easier understanding. Here's the drawRect method:

- (void)drawRect:(CGRect)rect {
    [self setAnimateIfNotSet];
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self drawProgressBackground:context inRect:rect];
    if (self.progress > 0) {
        [self drawProgress:context withFrame:rect];
    }
}

This is pretty self-explanatory. I set the animate property if it isn't set already and I draw the background. Then, if the progress is greater 0, I'll draw the progress within the total frame. Let's move on to the drawProgressBackground:inRect: method:

- (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);
}

Here, I create a rounded rectangle within the view with a radius of 10 (which I may later allow to be customizable) and fill it. Then the rest of the code is drawing the inner shadow, which I don't really need to go into detail about. Now, here's the code for drawing the progress in the method 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];
}

There are 4 primary parts to this method. First, I do the calculation of the frame that the progress will take up based on the self.progress property. Second, I draw either a solid color if the flat property is set, or I draw a calculated gradient (methods lighterColor and darkerColor are in a UIColor category). Third, I draw stripes and finally draw the percentage label. Let's cover those 2 methods quickly. Here's the drawStripes:inRect: method:

- (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);
}

This is where the animation "magic" happens. Essentially I draw these stripes based off of self.offset which is somewhere between -STRIPE_WIDTH and 0 as incremented by a timer. Then, I create a simple loop so that I only create enough stripes to completely fill the progress portion of the view. I also leave 25% of the STRIPE_WIDTH blank so that the stripes aren't bunched up against each other. Here's the final drawing method 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)];
}

In this method I create a label with text that is convert from a float (between 0.0 and 1.0) to a percentage (from 0% to 100%). I then either set the color to be dark or light depending on the darkness of the chosen progress color and draw the label in the CGContext.

Customizability

There are three properties that can be set either directly on an instance of LDProgressView or beforehand in a UIAppearance method.

  • Color

The color will obviously set the general look of the picker. The gradients, stripes, and/or outline colors are determined off of this. The UIAppearance method would be something like this:

 [[LDProgressView appearance] setColor:[UIColor colorWithRed:0.87f green:0.55f blue:0.09f alpha:1.00f]];
  • Flat

This will determine whether the background of the progress view will be a gradient or just the color property. This UIAppearance method would look something like this:

[[LDProgressView appearance] setFlat:@NO];
  • Animate

Finally, this will determine whether the stripes will be animated. This UIAppearance method can also be generically set for all instances of LDProgressView and looks like this:

[[LDProgressView appearance] setAnimate:@YES];

Conclusion

Whew! That was a long answer. I hope I didn't bore you guys too much. If you just skipped down to here, here's is the gist for drawing in code rather than with images. I think CoreGraphics is a superior way of drawing on iOS if you have the time/experience since it allows for more customization and I believe tends to be faster.

Here's a picture of the final, working product:

LDProgressView

于 2013-09-28T19:06:09.667 回答
1

The Closest related Progress Bar in your case is ADVProgressBar.

Here, you need to do a little bit modification. Like, you have to change the Back ground image and you can add those Rounded corners. After those changes, the progress bar will look exactly like what you mentioned in your Question.


Screenshot :

enter image description here

于 2013-09-28T12:46:05.200 回答
1

调整颜色和厚度,你就可以开始了! https://www.cocoacontrols.com/controls/ylprogressbar

这个好像也不错……不过我还没用。 https://github.com/JonasGessner/JGProgressView

祝你好运!

于 2013-09-26T19:48:22.087 回答
1

您可以使用NSTimer更新开始渲染叠加层的“偏移量”。你可以在这里找到一个例子。它是 OS X 控件,它使用自定义绘图,而不是图像,但原理是相同的。

于 2013-09-20T07:15:31.267 回答
1

这是一些示例,您可以根据需要修改一些代码并获取进度视图:检查Example1Example2

于 2013-09-27T08:40:33.567 回答
0

当您设置您的UIProgressView( self.progress) 我们以下设置以获得相同的外观:

        UIImage *mynd =[[UIImage imageNamed:@"KgcoJ.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(11, 13, 11, 13)];
        UIImage *bakMynd =[[UIImage imageNamed:@"UoS0A.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(11, 13, 11, 13)];
        [self.progress setProgressImage:mynd];
        [self.progress setTrackImage:bakMynd];

UIEdgeInsetsMake请注意,您可能必须根据您的图像尺寸更改图像名称和数字。这将为您提供以下信息: 带图像的进度条

于 2013-09-28T17:34:20.843 回答
0

您可以使用此自定义可可控件

进阶控制

于 2013-09-19T06:00:34.187 回答
0

为 ProgressView 尝试 Cocoa Controls ......至于覆盖的图像,您需要使用与 progressview 绝对同步的 NSTimer

于 2013-09-25T16:15:21.013 回答