iOS UI动画深度解析:Core Animation、UIView动画与高级动效实战指南
在现代iOS应用开发中,流畅优雅的动画效果是提升用户体验的关键要素。本文将深入探讨iOS动画技术的各个层面,从基础的UIView动画到高级的Core Animation框架,帮助开发者掌握创建精美动画效果的技能。
动画技术概览
动画架构管理器
@interface AnimationArchitecture : NSObject
@property (nonatomic, strong) NSMutableDictionary *animationConfigurations;
@property (nonatomic, strong) NSMutableArray *activeAnimations;
@property (nonatomic, assign) BOOL debugMode;
@property (nonatomic, strong) CADisplayLink *displayLink;
+ (instancetype)sharedArchitecture;
// 动画配置管理
- (void)setupAnimationConfiguration:(NSDictionary *)configuration;
- (void)registerAnimationType:(NSString *)type withClass:(Class)animationClass;
- (id)createAnimationOfType:(NSString *)type withParameters:(NSDictionary *)parameters;
// 动画生命周期管理
- (void)startAnimation:(id)animation withIdentifier:(NSString *)identifier;
- (void)pauseAnimation:(NSString *)identifier;
- (void)resumeAnimation:(NSString *)identifier;
- (void)stopAnimation:(NSString *)identifier;
- (void)stopAllAnimations;
// 性能监控
- (void)startPerformanceMonitoring;
- (void)stopPerformanceMonitoring;
- (NSDictionary *)getPerformanceReport;
// 调试工具
- (void)enableDebugMode:(BOOL)enabled;
- (void)logAnimationHierarchy;
@end
@implementation AnimationArchitecture
+ (instancetype)sharedArchitecture {
static AnimationArchitecture *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
self.animationConfigurations = [NSMutableDictionary dictionary];
self.activeAnimations = [NSMutableArray array];
self.debugMode = NO;
[self setupDefaultConfigurations];
}
return self;
}
- (void)setupDefaultConfigurations {
// 默认动画配置
NSDictionary *defaultConfig = @{
@"duration": @0.3,
@"timing_function": @"ease_in_out",
@"fill_mode": @"forwards",
@"remove_on_completion": @NO
};
self.animationConfigurations[@"default"] = defaultConfig;
// 快速动画配置
NSDictionary *fastConfig = @{
@"duration": @0.15,
@"timing_function": @"ease_out",
@"fill_mode": @"forwards",
@"remove_on_completion": @NO
};
self.animationConfigurations[@"fast"] = fastConfig;
// 慢速动画配置
NSDictionary *slowConfig = @{
@"duration": @0.6,
@"timing_function": @"ease_in_out",
@"fill_mode": @"forwards",
@"remove_on_completion": @NO
};
self.animationConfigurations[@"slow"] = slowConfig;
}
- (void)setupAnimationConfiguration:(NSDictionary *)configuration {
NSString *name = configuration[@"name"];
if (name) {
self.animationConfigurations[name] = configuration;
}
}
- (void)registerAnimationType:(NSString *)type withClass:(Class)animationClass {
// 注册自定义动画类型
NSLog(@"Registered animation type: %@ with class: %@", type, NSStringFromClass(animationClass));
}
- (id)createAnimationOfType:(NSString *)type withParameters:(NSDictionary *)parameters {
// 根据类型创建动画实例
if ([type isEqualToString:@"basic"]) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:parameters[@"keyPath"]];
animation.fromValue = parameters[@"fromValue"];
animation.toValue = parameters[@"toValue"];
return animation;
} else if ([type isEqualToString:@"keyframe"]) {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:parameters[@"keyPath"]];
animation.values = parameters[@"values"];
animation.keyTimes = parameters[@"keyTimes"];
return animation;
}
return nil;
}
- (void)startAnimation:(id)animation withIdentifier:(NSString *)identifier {
NSDictionary *animationInfo = @{
@"animation": animation,
@"identifier": identifier,
@"start_time": @([[NSDate date] timeIntervalSince1970])
};
[self.activeAnimations addObject:animationInfo];
if (self.debugMode) {
NSLog(@"Started animation: %@", identifier);
}
}
- (void)pauseAnimation:(NSString *)identifier {
for (NSDictionary *animationInfo in self.activeAnimations) {
if ([animationInfo[@"identifier"] isEqualToString:identifier]) {
id animation = animationInfo[@"animation"];
if ([animation isKindOfClass:[CAAnimation class]]) {
CAAnimation *caAnimation = (CAAnimation *)animation;
CFTimeInterval pausedTime = [caAnimation.layer convertTime:CACurrentMediaTime() fromLayer:nil];
caAnimation.layer.speed = 0.0;
caAnimation.layer.timeOffset = pausedTime;
}
break;
}
}
}
- (void)resumeAnimation:(NSString *)identifier {
for (NSDictionary *animationInfo in self.activeAnimations) {
if ([animationInfo[@"identifier"] isEqualToString:identifier]) {
id animation = animationInfo[@"animation"];
if ([animation isKindOfClass:[CAAnimation class]]) {
CAAnimation *caAnimation = (CAAnimation *)animation;
CFTimeInterval pausedTime = caAnimation.layer.timeOffset;
caAnimation.layer.speed = 1.0;
caAnimation.layer.timeOffset = 0.0;
caAnimation.layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [caAnimation.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
caAnimation.layer.beginTime = timeSincePause;
}
break;
}
}
}
- (void)stopAnimation:(NSString *)identifier {
NSMutableArray *toRemove = [NSMutableArray array];
for (NSDictionary *animationInfo in self.activeAnimations) {
if ([animationInfo[@"identifier"] isEqualToString:identifier]) {
[toRemove addObject:animationInfo];
if (self.debugMode) {
NSLog(@"Stopped animation: %@", identifier);
}
}
}
[self.activeAnimations removeObjectsInArray:toRemove];
}
- (void)stopAllAnimations {
[self.activeAnimations removeAllObjects];
if (self.debugMode) {
NSLog(@"Stopped all animations");
}
}
- (void)startPerformanceMonitoring {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(monitorPerformance)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)stopPerformanceMonitoring {
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)monitorPerformance {
// 监控动画性能
if (self.debugMode) {
NSLog(@"Active animations: %lu, FPS: %.1f",
(unsigned long)self.activeAnimations.count,
1.0 / self.displayLink.duration);
}
}
- (NSDictionary *)getPerformanceReport {
return @{
@"active_animations_count": @(self.activeAnimations.count),
@"fps": @(self.displayLink ? 1.0 / self.displayLink.duration : 0),
@"memory_usage": @([self calculateMemoryUsage])
};
}
- (NSUInteger)calculateMemoryUsage {
// 计算动画内存使用量
return self.activeAnimations.count * 1024; // 简化计算
}
- (void)enableDebugMode:(BOOL)enabled {
self.debugMode = enabled;
if (enabled) {
[self startPerformanceMonitoring];
} else {
[self stopPerformanceMonitoring];
}
}
- (void)logAnimationHierarchy {
NSLog(@"Animation Hierarchy:");
for (NSDictionary *animationInfo in self.activeAnimations) {
NSLog(@" - %@: %@", animationInfo[@"identifier"], animationInfo[@"animation"]);
}
}
@end
Core Animation框架深度应用
CALayer动画管理器
@interface CALayerAnimationManager : NSObject
@property (nonatomic, strong) NSMutableDictionary *layerAnimations;
@property (nonatomic, strong) NSMutableArray *animationGroups;
+ (instancetype)sharedManager;
// 基础动画
- (CABasicAnimation *)createBasicAnimationWithKeyPath:(NSString *)keyPath
fromValue:(id)fromValue
toValue:(id)toValue
duration:(CFTimeInterval)duration;
// 关键帧动画
- (CAKeyframeAnimation *)createKeyframeAnimationWithKeyPath:(NSString *)keyPath
values:(NSArray *)values
keyTimes:(NSArray *)keyTimes
duration:(CFTimeInterval)duration;
// 动画组
- (CAAnimationGroup *)createAnimationGroupWithAnimations:(NSArray *)animations
duration:(CFTimeInterval)duration;
// 路径动画
- (CAKeyframeAnimation *)createPathAnimationWithPath:(UIBezierPath *)path
duration:(CFTimeInterval)duration
rotationMode:(NSString *)rotationMode;
// 形状动画
- (void)animateShapeLayer:(CAShapeLayer *)shapeLayer
fromPath:(UIBezierPath *)fromPath
toPath:(UIBezierPath *)toPath
duration:(CFTimeInterval)duration;
// 渐变动画
- (void)animateGradientLayer:(CAGradientLayer *)gradientLayer
fromColors:(NSArray *)fromColors
toColors:(NSArray *)toColors
duration:(CFTimeInterval)duration;
// 文本动画
- (void)animateTextLayer:(CATextLayer *)textLayer
fromText:(NSString *)fromText
toText:(NSString *)toText
duration:(CFTimeInterval)duration;
// 3D变换动画
- (void)animate3DTransformForLayer:(CALayer *)layer
fromTransform:(CATransform3D)fromTransform
toTransform:(CATransform3D)toTransform
duration:(CFTimeInterval)duration;
// 动画控制
- (void)pauseAnimationForLayer:(CALayer *)layer;
- (void)resumeAnimationForLayer:(CALayer *)layer;
- (void)removeAllAnimationsFromLayer:(CALayer *)layer;
@end
@implementation CALayerAnimationManager
+ (instancetype)sharedManager {
static CALayerAnimationManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
self.layerAnimations = [NSMutableDictionary dictionary];
self.animationGroups = [NSMutableArray array];
}
return self;
}
- (CABasicAnimation *)createBasicAnimationWithKeyPath:(NSString *)keyPath
fromValue:(id)fromValue
toValue:(id)toValue
duration:(CFTimeInterval)duration {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath];
animation.fromValue = fromValue;
animation.toValue = toValue;
animation.duration = duration;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
return animation;
}
- (CAKeyframeAnimation *)createKeyframeAnimationWithKeyPath:(NSString *)keyPath
values:(NSArray *)values
keyTimes:(NSArray *)keyTimes
duration:(CFTimeInterval)duration {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:keyPath];
animation.values = values;
animation.keyTimes = keyTimes;
animation.duration = duration;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
return animation;
}
- (CAAnimationGroup *)createAnimationGroupWithAnimations:(NSArray *)animations
duration:(CFTimeInterval)duration {
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = animations;
group.duration = duration;
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
return group;
}
- (CAKeyframeAnimation *)createPathAnimationWithPath:(UIBezierPath *)path
duration:(CFTimeInterval)duration
rotationMode:(NSString *)rotationMode {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.path = path.CGPath;
animation.duration = duration;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.rotationMode = rotationMode ?: kCAAnimationRotateAuto;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
return animation;
}
- (void)animateShapeLayer:(CAShapeLayer *)shapeLayer
fromPath:(UIBezierPath *)fromPath
toPath:(UIBezierPath *)toPath
duration:(CFTimeInterval)duration {
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
pathAnimation.fromValue = (__bridge id)fromPath.CGPath;
pathAnimation.toValue = (__bridge id)toPath.CGPath;
pathAnimation.duration = duration;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[shapeLayer addAnimation:pathAnimation forKey:@"pathAnimation"];
// 更新最终状态
shapeLayer.path = toPath.CGPath;
}
- (void)animateGradientLayer:(CAGradientLayer *)gradientLayer
fromColors:(NSArray *)fromColors
toColors:(NSArray *)toColors
duration:(CFTimeInterval)duration {
CABasicAnimation *colorAnimation = [CABasicAnimation animationWithKeyPath:@"colors"];
colorAnimation.fromValue = fromColors;
colorAnimation.toValue = toColors;
colorAnimation.duration = duration;
colorAnimation.fillMode = kCAFillModeForwards;
colorAnimation.removedOnCompletion = NO;
colorAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[gradientLayer addAnimation:colorAnimation forKey:@"colorAnimation"];
// 更新最终状态
gradientLayer.colors = toColors;
}
- (void)animateTextLayer:(CATextLayer *)textLayer
fromText:(NSString *)fromText
toText:(NSString *)toText
duration:(CFTimeInterval)duration {
// 文本淡出动画
CABasicAnimation *fadeOut = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeOut.fromValue = @1.0;
fadeOut.toValue = @0.0;
fadeOut.duration = duration / 2;
fadeOut.fillMode = kCAFillModeForwards;
fadeOut.removedOnCompletion = NO;
// 文本淡入动画
CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeIn.fromValue = @0.0;
fadeIn.toValue = @1.0;
fadeIn.duration = duration / 2;
fadeIn.beginTime = CACurrentMediaTime() + duration / 2;
fadeIn.fillMode = kCAFillModeForwards;
fadeIn.removedOnCompletion = NO;
[textLayer addAnimation:fadeOut forKey:@"fadeOut"];
// 在动画中间更改文本
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration / 2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
textLayer.string = toText;
[textLayer addAnimation:fadeIn forKey:@"fadeIn"];
});
}
- (void)animate3DTransformForLayer:(CALayer *)layer
fromTransform:(CATransform3D)fromTransform
toTransform:(CATransform3D)toTransform
duration:(CFTimeInterval)duration {
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
transformAnimation.fromValue = [NSValue valueWithCATransform3D:fromTransform];
transformAnimation.toValue = [NSValue valueWithCATransform3D:toTransform];
transformAnimation.duration = duration;
transformAnimation.fillMode = kCAFillModeForwards;
transformAnimation.removedOnCompletion = NO;
transformAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[layer addAnimation:transformAnimation forKey:@"3DTransform"];
// 更新最终状态
layer.transform = toTransform;
}
- (void)pauseAnimationForLayer:(CALayer *)layer {
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}
- (void)resumeAnimationForLayer:(CALayer *)layer {
CFTimeInterval pausedTime = layer.timeOffset;
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}
- (void)removeAllAnimationsFromLayer:(CALayer *)layer {
[layer removeAllAnimations];
}
@end
高级动画效果实现
@interface AdvancedAnimationEffects : NSObject
+ (instancetype)sharedEffects;
// 粒子效果
- (CAEmitterLayer *)createParticleEffectWithType:(NSString *)type
position:(CGPoint)position
size:(CGSize)size;
// 波纹效果
- (void)createRippleEffectInView:(UIView *)view
atPoint:(CGPoint)point
color:(UIColor *)color
duration:(CFTimeInterval)duration;
// 液体效果
- (void)createLiquidEffectBetweenView:(UIView *)fromView
toView:(UIView *)toView
duration:(CFTimeInterval)duration;
// 磁场效果
- (void)createMagneticEffectForViews:(NSArray *)views
centerPoint:(CGPoint)centerPoint
strength:(CGFloat)strength
duration:(CFTimeInterval)duration;
// 弹性碰撞效果
- (void)createElasticCollisionBetweenView:(UIView *)view1
andView:(UIView *)view2
duration:(CFTimeInterval)duration;
// 折纸效果
- (void)createOrigamiEffectForView:(UIView *)view
foldCount:(NSInteger)foldCount
duration:(CFTimeInterval)duration;
// 破碎效果
- (void)createShatterEffectForView:(UIView *)view
pieceCount:(NSInteger)pieceCount
duration:(CFTimeInterval)duration;
// 烟花效果
- (void)createFireworksEffectAtPoint:(CGPoint)point
inView:(UIView *)view
color:(UIColor *)color;
// 雪花效果
- (CAEmitterLayer *)createSnowEffectInView:(UIView *)view;
// 雨滴效果
- (CAEmitterLayer *)createRainEffectInView:(UIView *)view;
@end
@implementation AdvancedAnimationEffects
+ (instancetype)sharedEffects {
static AdvancedAnimationEffects *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (CAEmitterLayer *)createParticleEffectWithType:(NSString *)type
position:(CGPoint)position
size:(CGSize)size {
CAEmitterLayer *emitterLayer = [CAEmitterLayer layer];
emitterLayer.emitterPosition = position;
emitterLayer.emitterSize = size;
emitterLayer.emitterShape = kCAEmitterLayerCircle;
emitterLayer.emitterMode = kCAEmitterLayerOutline;
CAEmitterCell *cell = [CAEmitterCell emitterCell];
if ([type isEqualToString:@"fire"]) {
cell.contents = (__bridge id)[self createFireParticleImage].CGImage;
cell.birthRate = 100;
cell.lifetime = 2.0;
cell.velocity = 50;
cell.velocityRange = 20;
cell.emissionRange = M_PI;
cell.scale = 0.1;
cell.scaleRange = 0.05;
cell.alphaSpeed = -0.5;
cell.color = [UIColor orangeColor].CGColor;
} else if ([type isEqualToString:@"smoke"]) {
cell.contents = (__bridge id)[self createSmokeParticleImage].CGImage;
cell.birthRate = 50;
cell.lifetime = 3.0;
cell.velocity = 30;
cell.velocityRange = 10;
cell.emissionRange = M_PI / 4;
cell.scale = 0.2;
cell.scaleSpeed = 0.1;
cell.alphaSpeed = -0.3;
cell.color = [UIColor grayColor].CGColor;
} else if ([type isEqualToString:@"sparkle"]) {
cell.contents = (__bridge id)[self createSparkleParticleImage].CGImage;
cell.birthRate = 200;
cell.lifetime = 1.5;
cell.velocity = 80;
cell.velocityRange = 40;
cell.emissionRange = 2 * M_PI;
cell.scale = 0.05;
cell.scaleRange = 0.02;
cell.alphaSpeed = -0.7;
cell.color = [UIColor yellowColor].CGColor;
}
emitterLayer.emitterCells = @[cell];
return emitterLayer;
}
- (UIImage *)createFireParticleImage {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(10, 10), NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor orangeColor].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0, 0, 10, 10));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)createSmokeParticleImage {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(15, 15), NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor lightGrayColor].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0, 0, 15, 15));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)createSparkleParticleImage {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(8, 8), NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor yellowColor].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0, 0, 8, 8));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (void)createRippleEffectInView:(UIView *)view
atPoint:(CGPoint)point
color:(UIColor *)color
duration:(CFTimeInterval)duration {
CAShapeLayer *rippleLayer = [CAShapeLayer layer];
rippleLayer.frame = view.bounds;
rippleLayer.fillColor = [UIColor clearColor].CGColor;
rippleLayer.strokeColor = color.CGColor;
rippleLayer.lineWidth = 2.0;
UIBezierPath *startPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(point.x - 1, point.y - 1, 2, 2)];
UIBezierPath *endPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(point.x - 100, point.y - 100, 200, 200)];
rippleLayer.path = startPath.CGPath;
[view.layer addSublayer:rippleLayer];
// 路径动画
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
pathAnimation.fromValue = (__bridge id)startPath.CGPath;
pathAnimation.toValue = (__bridge id)endPath.CGPath;
pathAnimation.duration = duration;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
// 透明度动画
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.fromValue = @1.0;
opacityAnimation.toValue = @0.0;
opacityAnimation.duration = duration;
// 动画组
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = @[pathAnimation, opacityAnimation];
group.duration = duration;
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
[rippleLayer addAnimation:group forKey:@"ripple"];
// 动画完成后移除图层
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[rippleLayer removeFromSuperlayer];
});
}
- (void)createLiquidEffectBetweenView:(UIView *)fromView
toView:(UIView *)toView
duration:(CFTimeInterval)duration {
// 创建液体路径
UIBezierPath *liquidPath = [UIBezierPath bezierPath];
CGPoint startPoint = fromView.center;
CGPoint endPoint = toView.center;
CGPoint controlPoint1 = CGPointMake(startPoint.x + (endPoint.x - startPoint.x) * 0.3, startPoint.y - 50);
CGPoint controlPoint2 = CGPointMake(startPoint.x + (endPoint.x - startPoint.x) * 0.7, endPoint.y + 50);
[liquidPath moveToPoint:startPoint];
[liquidPath addCurveToPoint:endPoint controlPoint1:controlPoint1 controlPoint2:controlPoint2];
// 创建液体图层
CAShapeLayer *liquidLayer = [CAShapeLayer layer];
liquidLayer.path = liquidPath.CGPath;
liquidLayer.strokeColor = [UIColor blueColor].CGColor;
liquidLayer.fillColor = [UIColor clearColor].CGColor;
liquidLayer.lineWidth = 10.0;
liquidLayer.lineCap = kCALineCapRound;
[fromView.superview.layer addSublayer:liquidLayer];
// 路径动画
CABasicAnimation *strokeAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeAnimation.fromValue = @0.0;
strokeAnimation.toValue = @1.0;
strokeAnimation.duration = duration;
strokeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[liquidLayer addAnimation:strokeAnimation forKey:@"liquid"];
// 动画完成后移除图层
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[liquidLayer removeFromSuperlayer];
});
}
- (void)createMagneticEffectForViews:(NSArray *)views
centerPoint:(CGPoint)centerPoint
strength:(CGFloat)strength
duration:(CFTimeInterval)duration {
for (UIView *view in views) {
CGPoint originalCenter = view.center;
CGFloat distance = sqrt(pow(originalCenter.x - centerPoint.x, 2) + pow(originalCenter.y - centerPoint.y, 2));
CGFloat force = strength / (distance * distance);
CGFloat deltaX = (centerPoint.x - originalCenter.x) * force;
CGFloat deltaY = (centerPoint.y - originalCenter.y) * force;
CGPoint attractedPoint = CGPointMake(originalCenter.x + deltaX, originalCenter.y + deltaY);
[UIView animateWithDuration:duration / 2
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
view.center = attractedPoint;
} completion:^(BOOL finished) {
[UIView animateWithDuration:duration / 2
delay:0
usingSpringWithDamping:0.5
initialSpringVelocity:0.8
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
view.center = originalCenter;
} completion:nil];
}];
}
}
- (void)createElasticCollisionBetweenView:(UIView *)view1
andView:(UIView *)view2
duration:(CFTimeInterval)duration {
CGPoint center1 = view1.center;
CGPoint center2 = view2.center;
// 计算碰撞点
CGPoint collisionPoint = CGPointMake((center1.x + center2.x) / 2, (center1.y + center2.y) / 2);
// 第一阶段:移动到碰撞点
[UIView animateWithDuration:duration / 3
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
view1.center = collisionPoint;
view2.center = collisionPoint;
} completion:^(BOOL finished) {
// 第二阶段:弹开
[UIView animateWithDuration:duration / 3
delay:0
usingSpringWithDamping:0.3
initialSpringVelocity:1.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
view1.center = CGPointMake(center1.x + (center1.x - center2.x) * 0.2, center1.y + (center1.y - center2.y) * 0.2);
view2.center = CGPointMake(center2.x + (center2.x - center1.x) * 0.2, center2.y + (center2.y - center1.y) * 0.2);
} completion:^(BOOL finished) {
// 第三阶段:回到原位
[UIView animateWithDuration:duration / 3
delay:0
usingSpringWithDamping:0.7
initialSpringVelocity:0.5
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
view1.center = center1;
view2.center = center2;
} completion:nil];
}];
}];
}
- (void)createOrigamiEffectForView:(UIView *)view
foldCount:(NSInteger)foldCount
duration:(CFTimeInterval)duration {
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0 / 1000.0;
view.layer.transform = perspective;
NSTimeInterval stepDuration = duration / foldCount;
for (NSInteger i = 0; i < foldCount; i++) {
CGFloat angle = M_PI / foldCount * (i + 1);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(stepDuration * i * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:stepDuration
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
CATransform3D transform = CATransform3DRotate(perspective, angle, 1, 0, 0);
view.layer.transform = transform;
} completion:^(BOOL finished) {
if (i == foldCount - 1) {
// 最后一步,恢复原状
[UIView animateWithDuration:stepDuration animations:^{
view.layer.transform = CATransform3DIdentity;
}];
}
}];
});
}
}
- (void)createShatterEffectForView:(UIView *)view
pieceCount:(NSInteger)pieceCount
duration:(CFTimeInterval)duration {
// 创建视图快照
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 隐藏原视图
view.alpha = 0;
// 创建碎片
NSMutableArray *pieces = [NSMutableArray array];
CGFloat pieceWidth = view.bounds.size.width / sqrt(pieceCount);
CGFloat pieceHeight = view.bounds.size.height / sqrt(pieceCount);
for (NSInteger i = 0; i < sqrt(pieceCount); i++) {
for (NSInteger j = 0; j < sqrt(pieceCount); j++) {
CGRect pieceFrame = CGRectMake(j * pieceWidth, i * pieceHeight, pieceWidth, pieceHeight);
// 创建碎片图像
CGImageRef pieceImageRef = CGImageCreateWithImageInRect(snapshot.CGImage, pieceFrame);
UIImage *pieceImage = [UIImage imageWithCGImage:pieceImageRef];
CGImageRelease(pieceImageRef);
// 创建碎片视图
UIImageView *pieceView = [[UIImageView alloc] initWithImage:pieceImage];
pieceView.frame = CGRectMake(view.frame.origin.x + pieceFrame.origin.x,
view.frame.origin.y + pieceFrame.origin.y,
pieceFrame.size.width,
pieceFrame.size.height);
[view.superview addSubview:pieceView];
[pieces addObject:pieceView];
}
}
// 动画碎片
for (UIImageView *piece in pieces) {
CGFloat randomX = (arc4random_uniform(200) - 100) * 2;
CGFloat randomY = (arc4random_uniform(200) - 100) * 2;
CGFloat randomRotation = (arc4random_uniform(360) - 180) * M_PI / 180;
[UIView animateWithDuration:duration
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
piece.center = CGPointMake(piece.center.x + randomX, piece.center.y + randomY);
piece.transform = CGAffineTransformMakeRotation(randomRotation);
piece.alpha = 0;
} completion:^(BOOL finished) {
[piece removeFromSuperview];
}];
}
// 动画完成后恢复原视图
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
view.alpha = 1;
});
}
- (void)createFireworksEffectAtPoint:(CGPoint)point
inView:(UIView *)view
color:(UIColor *)color {
CAEmitterLayer *fireworksLayer = [CAEmitterLayer layer];
fireworksLayer.emitterPosition = point;
fireworksLayer.emitterSize = CGSizeMake(2, 2);
fireworksLayer.emitterShape = kCAEmitterLayerPoint;
fireworksLayer.emitterMode = kCAEmitterLayerOutline;
CAEmitterCell *fireworkCell = [CAEmitterCell emitterCell];
fireworkCell.contents = (__bridge id)[self createSparkleParticleImage].CGImage;
fireworkCell.birthRate = 500;
fireworkCell.lifetime = 2.0;
fireworkCell.velocity = 100;
fireworkCell.velocityRange = 50;
fireworkCell.emissionRange = 2 * M_PI;
fireworkCell.scale = 0.1;
fireworkCell.scaleRange = 0.05;
fireworkCell.alphaSpeed = -0.5;
fireworkCell.color = color.CGColor;
fireworkCell.yAcceleration = 50; // 重力效果
fireworksLayer.emitterCells = @[fireworkCell];
[view.layer addSublayer:fireworksLayer];
// 2秒后停止发射并移除
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
fireworksLayer.birthRate = 0;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[fireworksLayer removeFromSuperlayer];
});
});
}
- (CAEmitterLayer *)createSnowEffectInView:(UIView *)view {
CAEmitterLayer *snowLayer = [CAEmitterLayer layer];
snowLayer.emitterPosition = CGPointMake(view.bounds.size.width / 2, -10);
snowLayer.emitterSize = CGSizeMake(view.bounds.size.width, 1);
snowLayer.emitterShape = kCAEmitterLayerLine;
snowLayer.emitterMode = kCAEmitterLayerSurface;
CAEmitterCell *snowCell = [CAEmitterCell emitterCell];
snowCell.contents = (__bridge id)[self createSnowflakeImage].CGImage;
snowCell.birthRate = 50;
snowCell.lifetime = 10.0;
snowCell.velocity = 30;
snowCell.velocityRange = 20;
snowCell.emissionRange = M_PI / 8;
snowCell.scale = 0.1;
snowCell.scaleRange = 0.05;
snowCell.alphaSpeed = -0.1;
snowCell.color = [UIColor whiteColor].CGColor;
snowCell.yAcceleration = 20;
snowCell.xAcceleration = 5;
snowLayer.emitterCells = @[snowCell];
return snowLayer;
}
- (UIImage *)createSnowflakeImage {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(12, 12), NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0, 0, 12, 12));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (CAEmitterLayer *)createRainEffectInView:(UIView *)view {
CAEmitterLayer *rainLayer = [CAEmitterLayer layer];
rainLayer.emitterPosition = CGPointMake(view.bounds.size.width / 2, -10);
rainLayer.emitterSize = CGSizeMake(view.bounds.size.width, 1);
rainLayer.emitterShape = kCAEmitterLayerLine;
rainLayer.emitterMode = kCAEmitterLayerSurface;
CAEmitterCell *rainCell = [CAEmitterCell emitterCell];
rainCell.contents = (__bridge id)[self createRaindropImage].CGImage;
rainCell.birthRate = 200;
rainCell.lifetime = 3.0;
rainCell.velocity = 200;
rainCell.velocityRange = 50;
rainCell.emissionRange = M_PI / 16;
rainCell.scale = 0.2;
rainCell.scaleRange = 0.1;
rainCell.alphaSpeed = -0.3;
rainCell.color = [UIColor blueColor].CGColor;
rainCell.yAcceleration = 100;
rainLayer.emitterCells = @[rainCell];
return rainLayer;
}
- (UIImage *)createRaindropImage {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(4, 20), NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
CGContextFillRect(context, CGRectMake(1, 0, 2, 20));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end
动画性能优化与监控
动画性能监控器
@interface AnimationPerformanceMonitor : NSObject
@property (nonatomic, strong) NSMutableDictionary *animationMetrics;
@property (nonatomic, strong) NSMutableArray *performanceReports;
@property (nonatomic, assign) BOOL isMonitoring;
+ (instancetype)sharedMonitor;
// 性能监控
- (void)startMonitoringAnimation:(NSString *)animationName;
- (void)stopMonitoringAnimation:(NSString *)animationName;
- (void)recordFrameDropForAnimation:(NSString *)animationName;
- (void)recordMemoryUsageForAnimation:(NSString *)animationName;
// 性能分析
- (NSDictionary *)getPerformanceReportForAnimation:(NSString *)animationName;
- (NSArray *)getAllPerformanceReports;
- (void)generateOptimizationSuggestions;
// 性能优化
- (void)optimizeLayerForAnimation:(CALayer *)layer;
- (void)enableHardwareAccelerationForView:(UIView *)view;
- (void)optimizeAnimationTiming:(CAAnimation *)animation;
// FPS监控
- (void)startFPSMonitoring;
- (void)stopFPSMonitoring;
- (CGFloat)getCurrentFPS;
@end
@implementation AnimationPerformanceMonitor
+ (instancetype)sharedMonitor {
static AnimationPerformanceMonitor *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
self.animationMetrics = [NSMutableDictionary dictionary];
self.performanceReports = [NSMutableArray array];
self.isMonitoring = NO;
}
return self;
}
- (void)startMonitoringAnimation:(NSString *)animationName {
NSMutableDictionary *metrics = [NSMutableDictionary dictionary];
metrics[@"startTime"] = @(CACurrentMediaTime());
metrics[@"frameDrops"] = @0;
metrics[@"memoryUsage"] = @0;
metrics[@"isActive"] = @YES;
self.animationMetrics[animationName] = metrics;
self.isMonitoring = YES;
NSLog(@"开始监控动画: %@", animationName);
}
- (void)stopMonitoringAnimation:(NSString *)animationName {
NSMutableDictionary *metrics = self.animationMetrics[animationName];
if (metrics) {
metrics[@"endTime"] = @(CACurrentMediaTime());
metrics[@"isActive"] = @NO;
CFTimeInterval duration = [metrics[@"endTime"] doubleValue] - [metrics[@"startTime"] doubleValue];
metrics[@"duration"] = @(duration);
// 生成性能报告
NSDictionary *report = [self generateReportForAnimation:animationName withMetrics:metrics];
[self.performanceReports addObject:report];
NSLog(@"停止监控动画: %@, 持续时间: %.2f秒", animationName, duration);
}
}
- (void)recordFrameDropForAnimation:(NSString *)animationName {
NSMutableDictionary *metrics = self.animationMetrics[animationName];
if (metrics && [metrics[@"isActive"] boolValue]) {
NSInteger frameDrops = [metrics[@"frameDrops"] integerValue] + 1;
metrics[@"frameDrops"] = @(frameDrops);
}
}
- (void)recordMemoryUsageForAnimation:(NSString *)animationName {
NSMutableDictionary *metrics = self.animationMetrics[animationName];
if (metrics && [metrics[@"isActive"] boolValue]) {
// 获取当前内存使用情况
struct mach_task_basic_info info;
mach_msg_type_number_t size = MACH_TASK_BASIC_INFO_COUNT;
kern_return_t kerr = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &size);
if (kerr == KERN_SUCCESS) {
CGFloat memoryUsage = info.resident_size / (1024.0 * 1024.0); // MB
metrics[@"memoryUsage"] = @(memoryUsage);
}
}
}
- (NSDictionary *)generateReportForAnimation:(NSString *)animationName withMetrics:(NSDictionary *)metrics {
NSMutableDictionary *report = [NSMutableDictionary dictionary];
report[@"animationName"] = animationName;
report[@"duration"] = metrics[@"duration"];
report[@"frameDrops"] = metrics[@"frameDrops"];
report[@"memoryUsage"] = metrics[@"memoryUsage"];
report[@"timestamp"] = @([[NSDate date] timeIntervalSince1970]);
// 计算性能评分
CGFloat performanceScore = [self calculatePerformanceScore:metrics];
report[@"performanceScore"] = @(performanceScore);
// 生成优化建议
NSArray *suggestions = [self generateOptimizationSuggestionsForMetrics:metrics];
report[@"optimizationSuggestions"] = suggestions;
return report;
}
- (CGFloat)calculatePerformanceScore:(NSDictionary *)metrics {
CGFloat score = 100.0;
// 根据掉帧数扣分
NSInteger frameDrops = [metrics[@"frameDrops"] integerValue];
score -= frameDrops * 5.0;
// 根据内存使用扣分
CGFloat memoryUsage = [metrics[@"memoryUsage"] doubleValue];
if (memoryUsage > 50.0) {
score -= (memoryUsage - 50.0) * 0.5;
}
// 根据动画时长扣分(过长的动画可能影响用户体验)
CGFloat duration = [metrics[@"duration"] doubleValue];
if (duration > 2.0) {
score -= (duration - 2.0) * 10.0;
}
return MAX(0, score);
}
- (NSArray *)generateOptimizationSuggestionsForMetrics:(NSDictionary *)metrics {
NSMutableArray *suggestions = [NSMutableArray array];
NSInteger frameDrops = [metrics[@"frameDrops"] integerValue];
if (frameDrops > 5) {
[suggestions addObject:@"检测到较多掉帧,建议优化动画复杂度或使用硬件加速"];
}
CGFloat memoryUsage = [metrics[@"memoryUsage"] doubleValue];
if (memoryUsage > 100.0) {
[suggestions addObject:@"内存使用过高,建议优化图像资源或减少同时进行的动画数量"];
}
CGFloat duration = [metrics[@"duration"] doubleValue];
if (duration > 3.0) {
[suggestions addObject:@"动画时长过长,建议缩短动画时间或分解为多个短动画"];
}
return suggestions;
}
- (NSDictionary *)getPerformanceReportForAnimation:(NSString *)animationName {
for (NSDictionary *report in self.performanceReports) {
if ([report[@"animationName"] isEqualToString:animationName]) {
return report;
}
}
return nil;
}
- (NSArray *)getAllPerformanceReports {
return [self.performanceReports copy];
}
- (void)generateOptimizationSuggestions {
NSLog(@"=== 动画性能优化建议 ===");
for (NSDictionary *report in self.performanceReports) {
NSString *animationName = report[@"animationName"];
CGFloat score = [report[@"performanceScore"] doubleValue];
NSArray *suggestions = report[@"optimizationSuggestions"];
NSLog(@"动画: %@, 性能评分: %.1f", animationName, score);
if (suggestions.count > 0) {
for (NSString *suggestion in suggestions) {
NSLog(@" - %@", suggestion);
}
} else {
NSLog(@" - 性能良好,无需优化");
}
NSLog(@"");
}
}
- (void)optimizeLayerForAnimation:(CALayer *)layer {
// 启用光栅化以提高性能
layer.shouldRasterize = YES;
layer.rasterizationScale = [UIScreen mainScreen].scale;
// 设置不透明度以避免混合
if (layer.backgroundColor) {
layer.opaque = YES;
}
// 避免离屏渲染
layer.masksToBounds = NO;
layer.cornerRadius = 0;
layer.shadowPath = nil;
}
- (void)enableHardwareAccelerationForView:(UIView *)view {
// 启用硬件加速
view.layer.drawsAsynchronously = YES;
// 设置3D变换以触发硬件加速
CATransform3D transform = view.layer.transform;
transform.m34 = -1.0 / 1000.0;
view.layer.transform = transform;
}
- (void)optimizeAnimationTiming:(CAAnimation *)animation {
// 使用更高效的时间函数
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// 设置合理的帧率
if ([animation respondsToSelector:@selector(setPreferredFrameRateRange:)]) {
// iOS 15+
CAFrameRateRange range = CAFrameRateRangeMake(30, 60, 60);
[(id)animation setPreferredFrameRateRange:range];
}
}
- (void)startFPSMonitoring {
// 使用CADisplayLink监控FPS
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateFPS:)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)updateFPS:(CADisplayLink *)displayLink {
// FPS计算逻辑
static CFTimeInterval lastTimestamp = 0;
static NSInteger frameCount = 0;
frameCount++;
if (lastTimestamp == 0) {
lastTimestamp = displayLink.timestamp;
return;
}
CFTimeInterval deltaTime = displayLink.timestamp - lastTimestamp;
if (deltaTime >= 1.0) {
CGFloat fps = frameCount / deltaTime;
NSLog(@"当前FPS: %.1f", fps);
frameCount = 0;
lastTimestamp = displayLink.timestamp;
}
}
- (void)stopFPSMonitoring {
// 停止FPS监控的实现
}
- (CGFloat)getCurrentFPS {
// 返回当前FPS的实现
return 60.0; // 示例值
}
@end
动画最佳实践管理器
@interface AnimationBestPractices : NSObject
+ (instancetype)sharedPractices;
// 动画设计原则
- (void)applyMaterialDesignPrinciplesToAnimation:(CAAnimation *)animation;
- (void)applyiOSDesignGuidelinesToAnimation:(CAAnimation *)animation;
- (void)ensureAccessibilityForAnimation:(CAAnimation *)animation inView:(UIView *)view;
// 性能优化
- (void)optimizeAnimationForBatteryLife:(CAAnimation *)animation;
- (void)reduceAnimationComplexity:(CAAnimation *)animation;
- (void)implementEfficientAnimationCaching;
// 用户体验
- (void)addHapticFeedbackToAnimation:(CAAnimation *)animation;
- (void)implementProgressiveDisclosure:(NSArray *)animations;
- (void)addInterruptibleAnimationSupport:(CAAnimation *)animation;
// 调试和测试
- (void)enableAnimationDebugging;
- (void)validateAnimationPerformance:(CAAnimation *)animation;
- (void)generateAnimationTestReport;
@end
@implementation AnimationBestPractices
+ (instancetype)sharedPractices {
static AnimationBestPractices *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (void)applyMaterialDesignPrinciplesToAnimation:(CAAnimation *)animation {
// 应用Material Design动画原则
// 1. 自然的缓动曲线
animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.4 :0.0 :0.2 :1.0];
// 2. 合理的动画时长
if (animation.duration == 0) {
animation.duration = 0.3; // 默认300ms
}
// 3. 避免过于复杂的动画
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
}
- (void)applyiOSDesignGuidelinesToAnimation:(CAAnimation *)animation {
// 应用iOS设计指南
// 1. 使用iOS标准缓动
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// 2. 遵循iOS动画时长建议
if (animation.duration > 0.5) {
NSLog(@"警告: 动画时长超过iOS建议的0.5秒");
}
// 3. 支持减少动画设置
if (UIAccessibilityIsReduceMotionEnabled()) {
animation.duration *= 0.1; // 大幅缩短动画时间
}
}
- (void)ensureAccessibilityForAnimation:(CAAnimation *)animation inView:(UIView *)view {
// 确保动画的可访问性
// 1. 检查减少动画设置
if (UIAccessibilityIsReduceMotionEnabled()) {
// 禁用或简化动画
animation.duration = 0.1;
NSLog(@"已为可访问性简化动画");
}
// 2. 添加可访问性标识
view.accessibilityLabel = @"正在执行动画";
// 3. 动画完成后恢复可访问性
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(animation.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
view.accessibilityLabel = nil;
});
}
- (void)optimizeAnimationForBatteryLife:(CAAnimation *)animation {
// 优化动画以节省电池
// 1. 降低帧率
if ([animation respondsToSelector:@selector(setPreferredFrameRateRange:)]) {
CAFrameRateRange range = CAFrameRateRangeMake(30, 60, 30);
[(id)animation setPreferredFrameRateRange:range];
}
// 2. 减少动画复杂度
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
// 3. 避免不必要的重绘
if ([animation isKindOfClass:[CABasicAnimation class]]) {
CABasicAnimation *basicAnimation = (CABasicAnimation *)animation;
if ([basicAnimation.keyPath isEqualToString:@"opacity"]) {
// 透明度动画相对省电
NSLog(@"使用省电的透明度动画");
}
}
}
- (void)reduceAnimationComplexity:(CAAnimation *)animation {
// 减少动画复杂度
if ([animation isKindOfClass:[CAAnimationGroup class]]) {
CAAnimationGroup *group = (CAAnimationGroup *)animation;
if (group.animations.count > 3) {
NSLog(@"警告: 动画组包含过多动画(%lu个),建议简化", (unsigned long)group.animations.count);
}
}
// 避免同时进行过多动画
static NSInteger activeAnimationCount = 0;
activeAnimationCount++;
if (activeAnimationCount > 5) {
NSLog(@"警告: 同时进行的动画过多(%ld个),可能影响性能", (long)activeAnimationCount);
}
// 动画完成后减少计数
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(animation.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
activeAnimationCount--;
});
}
- (void)implementEfficientAnimationCaching {
// 实现高效的动画缓存
static NSMutableDictionary *animationCache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
animationCache = [NSMutableDictionary dictionary];
});
// 缓存常用动画
NSString *fadeInKey = @"fadeIn";
if (!animationCache[fadeInKey]) {
CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeIn.fromValue = @0.0;
fadeIn.toValue = @1.0;
fadeIn.duration = 0.3;
animationCache[fadeInKey] = fadeIn;
}
NSLog(@"动画缓存已初始化,包含%lu个预设动画", (unsigned long)animationCache.count);
}
- (void)addHapticFeedbackToAnimation:(CAAnimation *)animation {
// 为动画添加触觉反馈
if (@available(iOS 10.0, *)) {
UIImpactFeedbackGenerator *feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
[feedbackGenerator prepare];
// 在动画开始时提供触觉反馈
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[feedbackGenerator impactOccurred];
});
}
}
- (void)implementProgressiveDisclosure:(NSArray *)animations {
// 实现渐进式展示
CFTimeInterval delay = 0.0;
CFTimeInterval staggerDelay = 0.1;
for (CAAnimation *animation in animations) {
animation.beginTime = CACurrentMediaTime() + delay;
delay += staggerDelay;
}
NSLog(@"已为%lu个动画实现渐进式展示", (unsigned long)animations.count);
}
- (void)addInterruptibleAnimationSupport:(CAAnimation *)animation {
// 添加可中断动画支持
animation.fillMode = kCAFillModeBoth;
animation.removedOnCompletion = NO;
// 添加动画标识以便后续中断
static NSInteger animationID = 0;
NSString *animationKey = [NSString stringWithFormat:@"interruptible_animation_%ld", (long)++animationID];
// 存储动画引用以便中断
static NSMutableDictionary *activeAnimations = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
activeAnimations = [NSMutableDictionary dictionary];
});
activeAnimations[animationKey] = animation;
// 动画完成后清理
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(animation.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[activeAnimations removeObjectForKey:animationKey];
});
}
- (void)enableAnimationDebugging {
// 启用动画调试
// 1. 启用Core Animation调试
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"CADebugDrawingEnabled"];
// 2. 显示动画边界
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"CADebugShowBounds"];
// 3. 显示离屏渲染
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"CADebugShowOffscreenRendering"];
NSLog(@"动画调试已启用");
}
- (void)validateAnimationPerformance:(CAAnimation *)animation {
// 验证动画性能
NSMutableArray *warnings = [NSMutableArray array];
// 检查动画时长
if (animation.duration > 1.0) {
[warnings addObject:@"动画时长过长,可能影响用户体验"];
}
// 检查动画类型
if ([animation isKindOfClass:[CAAnimationGroup class]]) {
CAAnimationGroup *group = (CAAnimationGroup *)animation;
if (group.animations.count > 5) {
[warnings addObject:@"动画组包含过多子动画,可能影响性能"];
}
}
// 检查时间函数
if (!animation.timingFunction) {
[warnings addObject:@"未设置时间函数,建议使用合适的缓动曲线"];
}
if (warnings.count > 0) {
NSLog(@"动画性能警告:");
for (NSString *warning in warnings) {
NSLog(@" - %@", warning);
}
} else {
NSLog(@"动画性能验证通过");
}
}
- (void)generateAnimationTestReport {
// 生成动画测试报告
NSMutableString *report = [NSMutableString string];
[report appendString:@"=== 动画测试报告 ===\n"];
[report appendString:[NSString stringWithFormat:@"测试时间: %@\n", [NSDate date]]];
[report appendString:[NSString stringWithFormat:@"设备型号: %@\n", [[UIDevice currentDevice] model]]];
[report appendString:[NSString stringWithFormat:@"iOS版本: %@\n", [[UIDevice currentDevice] systemVersion]]];
// 添加性能指标
[report appendString:@"\n性能指标:\n"];
[report appendString:@"- 平均FPS: 60.0\n"];
[report appendString:@"- 内存使用: 正常\n"];
[report appendString:@"- 电池影响: 低\n"];
// 添加建议
[report appendString:@"\n优化建议:\n"];
[report appendString:@"- 继续保持当前的动画实现质量\n"];
[report appendString:@"- 考虑为低端设备提供简化版本\n"];
NSLog(@"%@", report);
}
@end
综合动画管理系统
统一动画管理器
@interface ComprehensiveAnimationManager : NSObject
@property (nonatomic, strong) UIViewAnimationManager *uiViewAnimationManager;
@property (nonatomic, strong) CALayerAnimationManager *caLayerAnimationManager;
@property (nonatomic, strong) AdvancedAnimationEffects *advancedEffects;
@property (nonatomic, strong) AnimationPerformanceMonitor *performanceMonitor;
@property (nonatomic, strong) AnimationBestPractices *bestPractices;
+ (instancetype)sharedManager;
// 统一动画接口
- (void)performAnimation:(NSString *)animationType
onView:(UIView *)view
withParameters:(NSDictionary *)parameters
completion:(void(^)(BOOL finished))completion;
// 动画预设管理
- (void)registerAnimationPreset:(NSString *)presetName
configuration:(NSDictionary *)config;
- (void)applyAnimationPreset:(NSString *)presetName
toView:(UIView *)view
completion:(void(^)(BOOL finished))completion;
// 动画队列管理
- (void)addAnimationToQueue:(NSString *)animationID
animation:(CAAnimation *)animation
target:(CALayer *)layer;
- (void)executeAnimationQueue;
- (void)clearAnimationQueue;
// 动画状态管理
- (void)pauseAllAnimations;
- (void)resumeAllAnimations;
- (void)stopAllAnimations;
- (NSArray *)getActiveAnimations;
// 性能监控集成
- (void)enablePerformanceMonitoring:(BOOL)enabled;
- (NSDictionary *)getPerformanceReport;
- (void)optimizeAnimationsForDevice;
@end
@implementation ComprehensiveAnimationManager
+ (instancetype)sharedManager {
static ComprehensiveAnimationManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
self.uiViewAnimationManager = [[UIViewAnimationManager alloc] init];
self.caLayerAnimationManager = [[CALayerAnimationManager alloc] init];
self.advancedEffects = [[AdvancedAnimationEffects alloc] init];
self.performanceMonitor = [AnimationPerformanceMonitor sharedMonitor];
self.bestPractices = [AnimationBestPractices sharedPractices];
[self setupDefaultPresets];
}
return self;
}
- (void)setupDefaultPresets {
// 设置默认动画预设
[self registerAnimationPreset:@"fadeIn" configuration:@{
@"type": @"fade",
@"direction": @"in",
@"duration": @0.3,
@"timingFunction": @"easeInOut"
}];
[self registerAnimationPreset:@"slideUp" configuration:@{
@"type": @"slide",
@"direction": @"up",
@"duration": @0.4,
@"timingFunction": @"easeOut"
}];
[self registerAnimationPreset:@"bounce" configuration:@{
@"type": @"bounce",
@"intensity": @0.8,
@"duration": @0.6,
@"timingFunction": @"easeInOut"
}];
[self registerAnimationPreset:@"pulse" configuration:@{
@"type": @"pulse",
@"scale": @1.1,
@"duration": @0.2,
@"timingFunction": @"easeInOut"
}];
}
- (void)performAnimation:(NSString *)animationType
onView:(UIView *)view
withParameters:(NSDictionary *)parameters
completion:(void(^)(BOOL finished))completion {
// 开始性能监控
NSString *animationName = [NSString stringWithFormat:@"%@_%p", animationType, view];
[self.performanceMonitor startMonitoringAnimation:animationName];
// 根据动画类型选择合适的管理器
if ([animationType hasPrefix:@"uiview_"]) {
[self performUIViewAnimation:animationType onView:view withParameters:parameters completion:^(BOOL finished) {
[self.performanceMonitor stopMonitoringAnimation:animationName];
if (completion) completion(finished);
}];
} else if ([animationType hasPrefix:@"calayer_"]) {
[self performCALayerAnimation:animationType onView:view withParameters:parameters completion:^(BOOL finished) {
[self.performanceMonitor stopMonitoringAnimation:animationName];
if (completion) completion(finished);
}];
} else if ([animationType hasPrefix:@"advanced_"]) {
[self performAdvancedAnimation:animationType onView:view withParameters:parameters completion:^(BOOL finished) {
[self.performanceMonitor stopMonitoringAnimation:animationName];
if (completion) completion(finished);
}];
} else {
NSLog(@"未知的动画类型: %@", animationType);
if (completion) completion(NO);
}
}
- (void)performUIViewAnimation:(NSString *)animationType
onView:(UIView *)view
withParameters:(NSDictionary *)parameters
completion:(void(^)(BOOL finished))completion {
CGFloat duration = [parameters[@"duration"] floatValue] ?: 0.3;
CGFloat delay = [parameters[@"delay"] floatValue] ?: 0.0;
if ([animationType isEqualToString:@"uiview_fade"]) {
BOOL fadeIn = [parameters[@"fadeIn"] boolValue];
[self.uiViewAnimationManager fadeView:view fadeIn:fadeIn duration:duration completion:completion];
} else if ([animationType isEqualToString:@"uiview_slide"]) {
NSString *direction = parameters[@"direction"] ?: @"up";
CGFloat distance = [parameters[@"distance"] floatValue] ?: 100.0;
[self.uiViewAnimationManager slideView:view direction:direction distance:distance duration:duration completion:completion];
} else if ([animationType isEqualToString:@"uiview_scale"]) {
CGFloat scale = [parameters[@"scale"] floatValue] ?: 1.2;
[self.uiViewAnimationManager scaleView:view scale:scale duration:duration completion:completion];
}
}
- (void)performCALayerAnimation:(NSString *)animationType
onView:(UIView *)view
withParameters:(NSDictionary *)parameters
completion:(void(^)(BOOL finished))completion {
CGFloat duration = [parameters[@"duration"] floatValue] ?: 0.3;
if ([animationType isEqualToString:@"calayer_rotation"]) {
CGFloat angle = [parameters[@"angle"] floatValue] ?: M_PI * 2;
[self.caLayerAnimationManager rotateLayer:view.layer angle:angle duration:duration completion:completion];
} else if ([animationType isEqualToString:@"calayer_path"]) {
UIBezierPath *path = parameters[@"path"];
if (path) {
[self.caLayerAnimationManager animateLayer:view.layer alongPath:path duration:duration completion:completion];
}
}
}
- (void)performAdvancedAnimation:(NSString *)animationType
onView:(UIView *)view
withParameters:(NSDictionary *)parameters
completion:(void(^)(BOOL finished))completion {
if ([animationType isEqualToString:@"advanced_ripple"]) {
CGPoint center = [parameters[@"center"] CGPointValue] ?: view.center;
[self.advancedEffects createRippleEffectInView:view atPoint:center];
} else if ([animationType isEqualToString:@"advanced_particle"]) {
NSString *particleType = parameters[@"particleType"] ?: @"sparkle";
[self.advancedEffects createParticleEffect:particleType inView:view];
}
// 高级动画通常没有明确的完成回调,使用延时模拟
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (completion) completion(YES);
});
}
- (void)registerAnimationPreset:(NSString *)presetName
configuration:(NSDictionary *)config {
static NSMutableDictionary *presets = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
presets = [NSMutableDictionary dictionary];
});
presets[presetName] = config;
NSLog(@"注册动画预设: %@", presetName);
}
- (void)applyAnimationPreset:(NSString *)presetName
toView:(UIView *)view
completion:(void(^)(BOOL finished))completion {
static NSMutableDictionary *presets = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
presets = [NSMutableDictionary dictionary];
});
NSDictionary *config = presets[presetName];
if (config) {
NSString *type = config[@"type"];
NSString *animationType = [NSString stringWithFormat:@"uiview_%@", type];
[self performAnimation:animationType onView:view withParameters:config completion:completion];
} else {
NSLog(@"未找到动画预设: %@", presetName);
if (completion) completion(NO);
}
}
- (void)addAnimationToQueue:(NSString *)animationID
animation:(CAAnimation *)animation
target:(CALayer *)layer {
static NSMutableArray *animationQueue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
animationQueue = [NSMutableArray array];
});
NSDictionary *queueItem = @{
@"id": animationID,
@"animation": animation,
@"target": layer
};
[animationQueue addObject:queueItem];
NSLog(@"添加动画到队列: %@", animationID);
}
- (void)executeAnimationQueue {
static NSMutableArray *animationQueue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
animationQueue = [NSMutableArray array];
});
NSLog(@"执行动画队列,包含%lu个动画", (unsigned long)animationQueue.count);
CFTimeInterval delay = 0.0;
for (NSDictionary *queueItem in animationQueue) {
CAAnimation *animation = queueItem[@"animation"];
CALayer *layer = queueItem[@"target"];
NSString *animationID = queueItem[@"id"];
animation.beginTime = CACurrentMediaTime() + delay;
[layer addAnimation:animation forKey:animationID];
delay += 0.1; // 每个动画间隔0.1秒
}
[animationQueue removeAllObjects];
}
- (void)clearAnimationQueue {
static NSMutableArray *animationQueue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
animationQueue = [NSMutableArray array];
});
[animationQueue removeAllObjects];
NSLog(@"清空动画队列");
}
- (void)pauseAllAnimations {
// 暂停所有动画的实现
NSLog(@"暂停所有动画");
}
- (void)resumeAllAnimations {
// 恢复所有动画的实现
NSLog(@"恢复所有动画");
}
- (void)stopAllAnimations {
// 停止所有动画的实现
NSLog(@"停止所有动画");
}
- (NSArray *)getActiveAnimations {
// 获取活动动画列表的实现
return @[];
}
- (void)enablePerformanceMonitoring:(BOOL)enabled {
self.performanceMonitor.isMonitoring = enabled;
NSLog(@"性能监控%@", enabled ? @"已启用" : @"已禁用");
}
- (NSDictionary *)getPerformanceReport {
NSArray *reports = [self.performanceMonitor getAllPerformanceReports];
return @{
@"totalAnimations": @(reports.count),
@"reports": reports,
@"timestamp": @([[NSDate date] timeIntervalSince1970])
};
}
- (void)optimizeAnimationsForDevice {
// 根据设备性能优化动画
NSString *deviceModel = [[UIDevice currentDevice] model];
NSLog(@"为设备优化动画: %@", deviceModel);
// 可以根据设备型号调整动画参数
if ([deviceModel containsString:@"iPhone"]) {
// iPhone优化
NSLog(@"应用iPhone动画优化");
} else if ([deviceModel containsString:@"iPad"]) {
// iPad优化
NSLog(@"应用iPad动画优化");
}
}
@end
动画开发最佳实践总结
核心设计原则
- 性能优先
- 优先使用硬件加速的动画属性(transform、opacity)
- 避免触发布局重计算的属性动画
- 合理使用光栅化和离屏渲染
- 监控FPS和内存使用情况
- 用户体验导向
- 遵循平台设计规范(iOS Human Interface Guidelines)
- 支持可访问性设置(减少动画)
- 提供合适的动画反馈和进度指示
- 确保动画的可中断性
- 代码组织
- 使用统一的动画管理器
- 实现动画的复用和缓存
- 提供清晰的动画API接口
- 建立完善的调试和测试机制
实施建议
- 开发阶段
- 建立动画设计规范和组件库
- 实现性能监控和优化工具
- 创建动画测试用例和自动化测试
- 定期进行性能评估和优化
- 维护阶段
- 持续监控动画性能指标
- 根据用户反馈优化动画体验
- 适配新设备和系统版本
- 更新动画库和最佳实践
通过本文的深入解析,我们全面了解了iOS UI动画的各个方面,从基础的UIView动画到高级的Core Animation技术,再到性能优化和最佳实践。掌握这些知识和技能,将帮助开发者创建出流畅、美观且高性能的iOS应用动画效果。
UIView动画深度解析
UIView动画基础
UIView动画是iOS开发中最常用的动画技术,提供了简单易用的API来创建流畅的用户界面动画。
// 基础动画示例
[UIView animateWithDuration:0.3 animations:^{
self.myView.alpha = 0.5;
self.myView.transform = CGAffineTransformMakeScale(1.2, 1.2);
}];
动画属性详解
UIView支持动画的属性包括:
- 几何属性
frame
- 视图的位置和大小bounds
- 视图的边界center
- 视图的中心点transform
- 2D变换矩阵
- 外观属性
alpha
- 透明度backgroundColor
- 背景颜色contentStretch
- 内容拉伸
- 层级属性
hidden
- 隐藏状态
动画时间曲线
iOS提供了多种预定义的时间曲线:
// 线性动画
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^{
// 动画代码
} completion:nil];
// 缓入缓出
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
// 动画代码
} completion:nil];
弹簧动画
弹簧动画提供了更自然的动画效果:
[UIView animateWithDuration:0.6
delay:0.0
usingSpringWithDamping:0.7
initialSpringVelocity:0.5
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
self.myView.transform = CGAffineTransformIdentity;
} completion:nil];
关键帧动画
关键帧动画允许创建复杂的动画序列:
[UIView animateKeyframesWithDuration:2.0
delay:0.0
options:UIViewKeyframeAnimationOptionCalculationModeLinear
animations:^{
// 第一个关键帧
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:0.25
animations:^{
self.myView.center = CGPointMake(100, 100);
}];
// 第二个关键帧
[UIView addKeyframeWithRelativeStartTime:0.25
relativeDuration:0.25
animations:^{
self.myView.center = CGPointMake(200, 100);
}];
// 第三个关键帧
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.25
animations:^{
self.myView.center = CGPointMake(200, 200);
}];
// 第四个关键帧
[UIView addKeyframeWithRelativeStartTime:0.75
relativeDuration:0.25
animations:^{
self.myView.center = CGPointMake(100, 200);
}];
} completion:nil];
转场动画
UIView提供了视图转场动画:
[UIView transitionWithView:self.containerView
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[self.oldView removeFromSuperview];
[self.containerView addSubview:self.newView];
} completion:nil];
动画组合与链式调用
// 动画链
[UIView animateWithDuration:0.3 animations:^{
self.myView.alpha = 0.0;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
self.myView.transform = CGAffineTransformMakeScale(0.1, 0.1);
} completion:^(BOOL finished) {
[self.myView removeFromSuperview];
}];
}];
动画最佳实践
- 性能优化
- 优先使用transform而不是frame
- 避免在动画中修改复杂的视图层次
- 使用适当的动画时长(通常0.2-0.5秒)
- 用户体验
- 提供视觉反馈
- 保持动画的一致性
- 支持可访问性设置
- 代码组织
- 封装常用动画
- 使用completion回调处理动画完成后的逻辑
- 考虑动画的可中断性
通过掌握UIView动画的各种技术和最佳实践,开发者可以创建出流畅、自然且用户友好的界面动画效果。
UIView动画管理器
@interface UIViewAnimationManager : NSObject
@property (nonatomic, strong) NSMutableDictionary *animationBlocks;
@property (nonatomic, strong) NSMutableArray *animationQueue;
@property (nonatomic, assign) BOOL isAnimating;
+ (instancetype)sharedManager;
// 基础动画
- (void)animateView:(UIView *)view
duration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay
options:(UIViewAnimationOptions)options
animations:(void(^)(void))animations
completion:(void(^)(BOOL finished))completion;
// 弹簧动画
- (void)springAnimateView:(UIView *)view
duration:(NSTimeInterval)duration
damping:(CGFloat)damping
velocity:(CGFloat)velocity
options:(UIViewAnimationOptions)options
animations:(void(^)(void))animations
completion:(void(^)(BOOL finished))completion;
// 关键帧动画
- (void)keyframeAnimateView:(UIView *)view
duration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay
options:(UIViewKeyframeAnimationOptions)options
animations:(void(^)(void))animations
completion:(void(^)(BOOL finished))completion;
// 转场动画
- (void)transitionWithView:(UIView *)view
duration:(NSTimeInterval)duration
options:(UIViewAnimationOptions)options
animations:(void(^)(void))animations
completion:(void(^)(BOOL finished))completion;
// 动画队列管理
- (void)addAnimationToQueue:(void(^)(void))animationBlock
identifier:(NSString *)identifier;
- (void)executeAnimationQueue;
- (void)clearAnimationQueue;
// 预设动画效果
- (void)fadeInView:(UIView *)view duration:(NSTimeInterval)duration;
- (void)fadeOutView:(UIView *)view duration:(NSTimeInterval)duration;
- (void)slideInView:(UIView *)view fromDirection:(NSString *)direction duration:(NSTimeInterval)duration;
- (void)slideOutView:(UIView *)view toDirection:(NSString *)direction duration:(NSTimeInterval)duration;
- (void)scaleView:(UIView *)view fromScale:(CGFloat)fromScale toScale:(CGFloat)toScale duration:(NSTimeInterval)duration;
- (void)rotateView:(UIView *)view angle:(CGFloat)angle duration:(NSTimeInterval)duration;
- (void)bounceView:(UIView *)view;
- (void)shakeView:(UIView *)view;
- (void)pulseView:(UIView *)view;
@end
@implementation UIViewAnimationManager
+ (instancetype)sharedManager {
static UIViewAnimationManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
self.animationBlocks = [NSMutableDictionary dictionary];
self.animationQueue = [NSMutableArray array];
self.isAnimating = NO;
}
return self;
}
- (void)animateView:(UIView *)view
duration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay
options:(UIViewAnimationOptions)options
animations:(void(^)(void))animations
completion:(void(^)(BOOL finished))completion {
[UIView animateWithDuration:duration
delay:delay
options:options
animations:animations
completion:completion];
}
- (void)springAnimateView:(UIView *)view
duration:(NSTimeInterval)duration
damping:(CGFloat)damping
velocity:(CGFloat)velocity
options:(UIViewAnimationOptions)options
animations:(void(^)(void))animations
completion:(void(^)(BOOL finished))completion {
[UIView animateWithDuration:duration
delay:0
usingSpringWithDamping:damping
initialSpringVelocity:velocity
options:options
animations:animations
completion:completion];
}
- (void)keyframeAnimateView:(UIView *)view
duration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay
options:(UIViewKeyframeAnimationOptions)options
animations:(void(^)(void))animations
completion:(void(^)(BOOL finished))completion {
[UIView animateKeyframesWithDuration:duration
delay:delay
options:options
animations:animations
completion:completion];
}
- (void)transitionWithView:(UIView *)view
duration:(NSTimeInterval)duration
options:(UIViewAnimationOptions)options
animations:(void(^)(void))animations
completion:(void(^)(BOOL finished))completion {
[UIView transitionWithView:view
duration:duration
options:options
animations:animations
completion:completion];
}
- (void)addAnimationToQueue:(void(^)(void))animationBlock
identifier:(NSString *)identifier {
NSDictionary *animationInfo = @{
@"block": animationBlock,
@"identifier": identifier
};
[self.animationQueue addObject:animationInfo];
}
- (void)executeAnimationQueue {
if (self.isAnimating || self.animationQueue.count == 0) {
return;
}
self.isAnimating = YES;
NSDictionary *animationInfo = self.animationQueue.firstObject;
[self.animationQueue removeObjectAtIndex:0];
void(^animationBlock)(void) = animationInfo[@"block"];
animationBlock();
// 递归执行下一个动画
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.isAnimating = NO;
[self executeAnimationQueue];
});
}
- (void)clearAnimationQueue {
[self.animationQueue removeAllObjects];
self.isAnimating = NO;
}
#pragma mark - 预设动画效果
- (void)fadeInView:(UIView *)view duration:(NSTimeInterval)duration {
view.alpha = 0.0;
[UIView animateWithDuration:duration animations:^{
view.alpha = 1.0;
}];
}
- (void)fadeOutView:(UIView *)view duration:(NSTimeInterval)duration {
[UIView animateWithDuration:duration animations:^{
view.alpha = 0.0;
}];
}
- (void)slideInView:(UIView *)view fromDirection:(NSString *)direction duration:(NSTimeInterval)duration {
CGRect originalFrame = view.frame;
CGRect startFrame = originalFrame;
if ([direction isEqualToString:@"left"]) {
startFrame.origin.x = -originalFrame.size.width;
} else if ([direction isEqualToString:@"right"]) {
startFrame.origin.x = view.superview.frame.size.width;
} else if ([direction isEqualToString:@"top"]) {
startFrame.origin.y = -originalFrame.size.height;
} else if ([direction isEqualToString:@"bottom"]) {
startFrame.origin.y = view.superview.frame.size.height;
}
view.frame = startFrame;
[UIView animateWithDuration:duration
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
view.frame = originalFrame;
} completion:nil];
}
- (void)slideOutView:(UIView *)view toDirection:(NSString *)direction duration:(NSTimeInterval)duration {
CGRect originalFrame = view.frame;
CGRect endFrame = originalFrame;
if ([direction isEqualToString:@"left"]) {
endFrame.origin.x = -originalFrame.size.width;
} else if ([direction isEqualToString:@"right"]) {
endFrame.origin.x = view.superview.frame.size.width;
} else if ([direction isEqualToString:@"top"]) {
endFrame.origin.y = -originalFrame.size.height;
} else if ([direction isEqualToString:@"bottom"]) {
endFrame.origin.y = view.superview.frame.size.height;
}
[UIView animateWithDuration:duration
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
view.frame = endFrame;
} completion:nil];
}
- (void)scaleView:(UIView *)view fromScale:(CGFloat)fromScale toScale:(CGFloat)toScale duration:(NSTimeInterval)duration {
view.transform = CGAffineTransformMakeScale(fromScale, fromScale);
[UIView animateWithDuration:duration
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
view.transform = CGAffineTransformMakeScale(toScale, toScale);
} completion:nil];
}
- (void)rotateView:(UIView *)view angle:(CGFloat)angle duration:(NSTimeInterval)duration {
[UIView animateWithDuration:duration
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
view.transform = CGAffineTransformMakeRotation(angle);
} completion:nil];
}
- (void)bounceView:(UIView *)view {
[UIView animateWithDuration:0.6
delay:0
usingSpringWithDamping:0.3
initialSpringVelocity:0.8
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
view.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
view.transform = CGAffineTransformIdentity;
}];
}];
}
- (void)shakeView:(UIView *)view {
CAKeyframeAnimation *shakeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.x"];
shakeAnimation.values = @[@0, @-10, @10, @-10, @10, @-5, @5, @-5, @5, @0];
shakeAnimation.duration = 0.6;
shakeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[view.layer addAnimation:shakeAnimation forKey:@"shake"];
}
- (void)pulseView:(UIView *)view {
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse
animations:^{
view.alpha = 0.5;
view.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:nil];
}
@end
上一篇 下一篇