Хотя я не уверен в специфике того, как Apple достигла этого эффекта, мне кажется, что это отличная возможность использовать CoreAnimation и настраиваемые анимируемые свойства. В этом посте приведен хороший фон по этому вопросу. Я предполагаю, что по анимации "Blue Marble drop" вы имеете в виду следующую последовательность:
- Большой светло-голубой круг увеличивает рамку
- Большой голубой круг колеблется между двумя относительно большими радиусами, поскольку местоположение
вычисляется
- Большой светло-синий круг увеличивает маленький темно-синий круг на месте пользователя
Хотя это может немного упростить процесс, я думаю, что это хорошее место для начала, и более сложные / подробные функции могут быть добавлены с относительной легкостью (т. Е. Маленький темный круг пульсирует, когда на нем сходится больший круг).
Первое, что нам нужно, это пользовательский подкласс CALayer с пользовательским свойством для нашего большого радиуса больших голубых кругов:
#import <QuartzCore/QuartzCore.h>
@interface CustomLayer : CALayer
@property (nonatomic, assign) CGFloat circleRadius;
@end
и реализация:
#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
Имея эту инфраструктуру, мы можем с легкостью анимировать радиус внешнего круга, так как 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];
}
Вышесказанное является довольно "хакерским" доказательством концепции, поскольку я не уверен в конкретном способе использования этого эффекта. Например, если вы хотите колебаться по кругу до тех пор, пока данные не будут готовы, вышеприведенное не имеет большого смысла, поскольку оно всегда будет колебаться дважды.
Некоторые заключительные замечания:
- Опять же, я не уверен в вашем намерении добиться этого эффекта. Если для
Например, вы добавляете его в
MKMapView
, выше может потребоваться
некоторые настройки для интеграции с MapKit.
- В связанном посте предлагается, что для вышеуказанного метода требуется версия CoreAnimation для iOS 3.0+ и OS X 10.6 +
- Говоря о связанном посте (как я это часто делал), большое спасибо и спасибо Оле Бегеманну, который написал it и проделал замечательную работу по объяснению пользовательских свойств в CoreAnimation.
РЕДАКТИРОВАТЬ: Кроме того, по соображениям производительности, вы, вероятно, захотите убедиться, что слой только настолько большой, насколько это необходимо. То есть после того, как вы закончили анимацию с большего размера, вы можете захотеть уменьшить размер, чтобы использовать / рисовать столько места, сколько необходимо. Хороший способ сделать это - просто найти способ анимировать bounds
(в отличие от circleRadius
) и выполнить эту анимацию на основе интерполяции размера, но у меня возникли некоторые проблемы с реализацией этого (возможно, кто-то мог бы добавить некоторое понимание на эту тему).
Надеюсь, это поможет,
Sam