// // QXFloatAnimationHelper.m // QXLive // // Created by 启星 on 2025/11/22. // #import "QXFloatAnimationHelper.h" #import // 关联对象键 static void * const kFloatAnimationKey = "kFloatAnimationKey"; @interface QXFloatAnimationHelper () @property (nonatomic, strong) NSMutableSet *animatedViews; @end @implementation QXFloatAnimationHelper + (instancetype)sharedController { static QXFloatAnimationHelper *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[QXFloatAnimationHelper alloc] init]; }); return instance; } - (instancetype)init { self = [super init]; if (self) { _animatedViews = [NSMutableSet set]; _style = FloatAnimationStyleSmooth; _amplitude = 1.0; _duration = 3.0; _autoStart = YES; } return self; } - (void)addFloatAnimationToView:(UIView *)view { [self addFloatAnimationToView:view withStyle:self.style]; } - (void)addFloatAnimationToView:(UIView *)view withStyle:(FloatAnimationStyle)style { if (!view || [self.animatedViews containsObject:view]) { return; } [self.animatedViews addObject:view]; // 保存原始位置 CGPoint originalCenter = view.center; objc_setAssociatedObject(view, kFloatAnimationKey, [NSValue valueWithCGPoint:originalCenter], OBJC_ASSOCIATION_RETAIN_NONATOMIC); // 根据样式创建不同的动画 switch (style) { case FloatAnimationStyleSmooth: [self addSmoothFloatAnimationToView:view]; break; case FloatAnimationStyleBounce: [self addBounceFloatAnimationToView:view]; break; case FloatAnimationStyleWave: [self addWaveFloatAnimationToView:view]; break; case FloatAnimationStyleBreath: [self addBreathFloatAnimationToView:view]; break; } } - (void)addSmoothFloatAnimationToView:(UIView *)view { CGPoint originalCenter = [objc_getAssociatedObject(view, kFloatAnimationKey) CGPointValue]; // 创建关键帧动画 CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; NSMutableArray *pathValues = [NSMutableArray array]; NSInteger frames = 60; for (int i = 0; i <= frames; i++) { CGFloat progress = (CGFloat)i / frames; CGFloat radians = progress * M_PI * 2; CGFloat offsetY = sin(radians) * self.amplitude; CGPoint point = CGPointMake(originalCenter.x, originalCenter.y + offsetY); [pathValues addObject:[NSValue valueWithCGPoint:point]]; } animation.values = pathValues; animation.duration = self.duration; animation.repeatCount = HUGE_VALF; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; // 随机延迟,避免所有视图同步 animation.beginTime = CACurrentMediaTime() + (arc4random_uniform(1000) / 1000.0) * 1.0; [view.layer addAnimation:animation forKey:@"smoothFloatAnimation"]; } - (void)addBounceFloatAnimationToView:(UIView *)view { CGPoint originalCenter = [objc_getAssociatedObject(view, kFloatAnimationKey) CGPointValue]; CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position.y"]; // 弹跳效果的值 NSArray *values = @[ @(originalCenter.y), @(originalCenter.y - self.amplitude * 0.3), @(originalCenter.y - self.amplitude), @(originalCenter.y - self.amplitude * 0.7), @(originalCenter.y - self.amplitude * 0.3), @(originalCenter.y), @(originalCenter.y + self.amplitude * 0.2), @(originalCenter.y + self.amplitude * 0.5), @(originalCenter.y + self.amplitude * 0.2), @(originalCenter.y) ]; animation.values = values; animation.duration = self.duration; animation.repeatCount = HUGE_VALF; // 弹跳的时间函数 animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.5 :1.8 :0.8 :0.8]; animation.beginTime = CACurrentMediaTime() + (arc4random_uniform(1000) / 1000.0) * 1.0; [view.layer addAnimation:animation forKey:@"bounceFloatAnimation"]; } - (void)addWaveFloatAnimationToView:(UIView *)view { CGPoint originalCenter = [objc_getAssociatedObject(view, kFloatAnimationKey) CGPointValue]; CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position.y"]; NSMutableArray *values = [NSMutableArray array]; NSInteger frames = 60; // 波浪效果 - 多个正弦波叠加 for (int i = 0; i <= frames; i++) { CGFloat progress = (CGFloat)i / frames; // 主波 CGFloat wave1 = sin(progress * M_PI * 2) * self.amplitude; // 次波 CGFloat wave2 = sin(progress * M_PI * 4) * self.amplitude * 0.3; CGFloat offsetY = wave1 + wave2; [values addObject:@(originalCenter.y + offsetY)]; } animation.values = values; animation.duration = self.duration; animation.repeatCount = HUGE_VALF; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; animation.beginTime = CACurrentMediaTime() + (arc4random_uniform(1000) / 1000.0) * 1.0; [view.layer addAnimation:animation forKey:@"waveFloatAnimation"]; } - (void)addBreathFloatAnimationToView:(UIView *)view { CGPoint originalCenter = [objc_getAssociatedObject(view, kFloatAnimationKey) CGPointValue]; // 组合动画:位置 + 透明度 CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position.y"]; CAKeyframeAnimation *alphaAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; NSMutableArray *positionValues = [NSMutableArray array]; NSMutableArray *alphaValues = [NSMutableArray array]; NSInteger frames = 60; for (int i = 0; i <= frames; i++) { CGFloat progress = (CGFloat)i / frames; CGFloat radians = progress * M_PI * 2; // 位置变化 CGFloat offsetY = sin(radians) * self.amplitude; [positionValues addObject:@(originalCenter.y + offsetY)]; // 透明度变化 (0.8 - 1.0) CGFloat alpha = 0.9 + 0.1 * sin(radians); [alphaValues addObject:@(alpha)]; } positionAnimation.values = positionValues; positionAnimation.duration = self.duration; positionAnimation.repeatCount = HUGE_VALF; alphaAnimation.values = alphaValues; alphaAnimation.duration = self.duration; alphaAnimation.repeatCount = HUGE_VALF; CAAnimationGroup *group = [CAAnimationGroup animation]; group.animations = @[positionAnimation, alphaAnimation]; group.duration = self.duration; group.repeatCount = HUGE_VALF; group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; group.beginTime = CACurrentMediaTime() + (arc4random_uniform(1000) / 1000.0) * 1.0; [view.layer addAnimation:group forKey:@"breathFloatAnimation"]; } - (void)addFloatAnimationToViews:(NSArray *)views { for (UIView *view in views) { [self addFloatAnimationToView:view]; } } - (void)removeFloatAnimationFromView:(UIView *)view { if (!view) return; [self.animatedViews removeObject:view]; // 移除所有漂浮动画 [view.layer removeAnimationForKey:@"smoothFloatAnimation"]; [view.layer removeAnimationForKey:@"bounceFloatAnimation"]; [view.layer removeAnimationForKey:@"waveFloatAnimation"]; [view.layer removeAnimationForKey:@"breathFloatAnimation"]; // 恢复原始位置 NSValue *originalCenterValue = objc_getAssociatedObject(view, kFloatAnimationKey); if (originalCenterValue) { view.center = [originalCenterValue CGPointValue]; } } - (void)removeAllFloatAnimations { for (UIView *view in self.animatedViews) { [self removeFloatAnimationFromView:view]; } [self.animatedViews removeAllObjects]; } - (void)pauseFloatAnimationForView:(UIView *)view { CFTimeInterval pausedTime = [view.layer convertTime:CACurrentMediaTime() fromLayer:nil]; view.layer.speed = 0.0; view.layer.timeOffset = pausedTime; } - (void)resumeFloatAnimationForView:(UIView *)view { CFTimeInterval pausedTime = view.layer.timeOffset; view.layer.speed = 1.0; view.layer.timeOffset = 0.0; view.layer.beginTime = 0.0; CFTimeInterval timeSincePause = [view.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; view.layer.beginTime = timeSincePause; } @end