iphone - 创建动画,如用户位置蓝色大理石滴

Tuy*_*yen 4 iphone core-animation objective-c

您知道如何在MKMapView中创建像Blue Marble drop User-Location这样的动画吗?

Sam*_*Sam 11

虽然我不确定Apple如何实现这种效果,但这对我来说是一个使用CoreAnimation和自定义动画属性的绝佳机会. 这篇文章提供了一些关于这个主题的好背景.我假设通过"Blue Marble drop"动画你指的是以下顺序:

  1. 大浅蓝色圆圈放大框架
  2. 当计算位置时,大的浅蓝色圆圈在两个相对大的半径之间振荡
  3. 大浅蓝色圆圈在用户所在位置缩放成较暗的深蓝色圆圈

虽然这可能会略微简化这个过程,但我认为它是一个很好的起点,可以相对轻松地添加更复杂/更详细的功能(即较小的黑色圆形脉冲,因为较大的圆形会聚在其上.)

我们需要的第一件事是自定义CALayer子类,它具有我们外部大的浅蓝色圆圈半径的自定义属性:

#import <QuartzCore/QuartzCore.h>

@interface CustomLayer : CALayer
@property (nonatomic, assign) CGFloat circleRadius;
@end
Run Code Online (Sandbox Code Playgroud)

和实施:

#import "CustomLayer.h"

@implementation CustomLayer
@dynamic circleRadius; // Linked post tells us to let CA implement our accessors for us.
                       // Whether this is necessary or not is unclear to me and one 
                       // commenter on the linked post claims success only when using
                       // @synthesize for the animatable property.

+ (BOOL)needsDisplayForKey:(NSString*)key {
    // Let our layer know it has to redraw when circleRadius is changed
    if ([key isEqualToString:@"circleRadius"]) {
        return YES;
    } else {
        return [super needsDisplayForKey:key];
    }
}

- (void)drawInContext:(CGContextRef)ctx {

    // This call is probably unnecessary as super's implementation does nothing
    [super drawInContext:ctx];

    CGRect rect = CGContextGetClipBoundingBox(ctx);

    // Fill the circle with a light blue
    CGContextSetRGBFillColor(ctx, 0, 0, 255, 0.1);
    // Stoke a dark blue border
    CGContextSetRGBStrokeColor(ctx, 0, 0, 255, 0.5);

    // Construct a CGMutablePath to draw the light blue circle
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddArc(path, NULL, rect.size.width / 2, 
                             rect.size.height / 2, 
                             self.circleRadius, 0, 2 * M_PI, NO);
    // Fill the circle
    CGContextAddPath(ctx, path);
    CGContextFillPath(ctx);

    // Stroke the circle's border
    CGContextAddPath(ctx, path);
    CGContextStrokePath(ctx);

    // Release the path
    CGPathRelease(path);

    // Set a dark blue color for the small inner circle
    CGContextSetRGBFillColor(ctx, 0, 0, 255, 1.0f);

    // Draw the center dot
    CGContextBeginPath (ctx);
    CGContextAddArc(ctx, rect.size.width / 2, 
                         rect.size.height / 2, 
                         5, 0, 2 * M_PI, NO);
    CGContextFillPath(ctx);
    CGContextStrokePath(ctx);

}

@end
Run Code Online (Sandbox Code Playgroud)

有了这个基础设施,我们现在可以轻松地为外圈的半径设置动画b/c CoreAnimation将处理值插值以及重绘调用.我们只需要为图层添加动画.作为一个简单的概念证明,我选择了一个简单的CAKeyframeAnimation经历3阶段动画:

// In some controller class...
- (void)addLayerAndAnimate {

    CustomLayer *customLayer = [[CustomLayer alloc] init];

    // Make layer big enough for the initial radius
    // EDIT: You may want to shrink the layer when it reacehes it's final size
    [customLayer setFrame:CGRectMake(0, 0, 205, 205)];
    [self.view.layer addSublayer:customLayer];


    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"circleRadius"];

    // Zoom in, oscillate a couple times, zoom in further
    animation.values = [NSArray arrayWithObjects:[NSNumber numberWithFloat:100], 
                                                 [NSNumber numberWithFloat:45], 
                                                 [NSNumber numberWithFloat:50], 
                                                 [NSNumber numberWithFloat:45], 
                                                 [NSNumber numberWithFloat:50], 
                                                 [NSNumber numberWithFloat:45], 
                                                 [NSNumber numberWithFloat:20], 
                                                  nil];
    // We want the radii to be 20 in the end
    customLayer.circleRadius = 20;

    // Rather arbitrary values.  I thought the cubic pacing w/ a 2.5 second pacing
    // looked decent enough but you'd probably want to play with them to get a more
    // accurate imitation of the Maps app.  You could also define a keyTimes array for 
    // a more discrete control of the times per step.
    animation.duration = 2.5;
    animation.calculationMode = kCAAnimationCubicPaced;

    [customLayer addAnimation:animation forKey:nil];

}
Run Code Online (Sandbox Code Playgroud)

以上是一个相当"hacky"的概念证明,因为我不确定你打算使用这种效果的具体方式.例如,如果你想在数据准备好之前振荡圆圈,那么上面的内容就不会有多大意义,因为它总会振荡两次.

一些结束说明:

  • 同样,我不确定你对这种效果的意图.例如,如果您将其添加到a MKMapView,则上面可能需要进行一些调整才能与MapKit集成.
  • 链接帖子表明上述方法需要iOS 3.0+和OS X 10.6+中的CoreAnimation版本
  • 说到链接的帖子(正如我经常做的那样),非常感谢Ole Begemann写这篇文章,并在CoreAnimation中解释了自定义属性.

编辑:此外,出于性能原因,您可能希望确保该层只有它需要的大.也就是说,在完成较大尺寸的动画制作后,您可能需要缩小尺寸,这样您就可以根据需要使用/绘制尽可能多的空间.一个很好的方法就是找到一种方法来动画bounds(而不是circleRadius)并根据大小插值执行这个动画但是我在实现它时遇到了一些麻烦(也许有人可以在这个主题上添加一些见解).

希望这有帮助,山姆