Files
featherVoice/QXLive/Tools/QXFloatAnimationHelper.m
2025-11-28 22:43:06 +08:00

251 lines
8.6 KiB
Objective-C

//
// QXFloatAnimationHelper.m
// QXLive
//
// Created by 启星 on 2025/11/22.
//
#import "QXFloatAnimationHelper.h"
#import <objc/runtime.h>
// 关联对象键
static void * const kFloatAnimationKey = "kFloatAnimationKey";
@interface QXFloatAnimationHelper ()
@property (nonatomic, strong) NSMutableSet<UIView *> *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<UIView *> *)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