3

KAYAK アプリ (フライトやホテルを検索する旅行アプリ) のような進行状況ビューを作成する方法を知りたいのですが、スクリーンショット:

ここに画像の説明を入力

ジェイルブレイクされた iPhone で KAYAK アプリのリソースを調べたところ、この進行状況ビューを構成する次の 3 つの画像が見つかりました。

プログレスバーの背景@2x.png

ここに画像の説明を入力

プログレスバー-gradient@2x.png

ここに画像の説明を入力

プログレスバー-overlay@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

NSTimerオーバーレイのレンダリングを開始する「オフセット」を更新するために使用できます。ここで例を見つけることができます。これは OS X コントロールであり、画像ではなくカスタム描画を使用しますが、原理は同じです。

于 2013-09-20T07:15:31.267 に答える
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

ProgressView の Cocoa Controls を試してみてください....... オーバーレイ画像については、progressview と完全に同期してリレーする NSTimer を使用する必要があります

于 2013-09-25T16:15:21.013 に答える