Files
peko-ios/YuMi/Modules/YMRoom/View/AnimationView/GiftAnimationHelper.m
2025-04-02 11:04:07 +08:00

339 lines
14 KiB
Objective-C

//
// GiftAnimationManager.m
// YuMi
//
// Created by P on 2024/12/9.
//
#import "GiftAnimationHelper.h"
// Constants
static const CGFloat kGiftImageSize = 55.0f;
static const CGFloat kStandardAnimationDuration = 3.2f;
static const CGFloat kComboAnimationDuration = 1.0f;
static const CGFloat kCleanupDelay = 0.25f;
static const CGFloat kComboInitialScale = 0.4f;
static const CGFloat kComboFinalScale = 1.5; //2.0f;
static const CGFloat kComboScaleDuration = 0.1f;
static const CGFloat kComboMoveDuration = 0.5f;
static const CGFloat kComboTotalDuration = 0.6f;
static const CGFloat kStandardScaleDuration = 0.8f;
static const CGFloat kStandardTotalDuration = 3.2f;
static const CGFloat kStandardSecondPhaseDelay = 0.8f;
static const CGFloat kStandardFinalPhaseDelay = 2.6f;
@interface GiftAnimationHelper ()
@property (nonatomic, strong) NSMutableSet<NetImageView *> *giftReuseArray;
@property (nonatomic, strong) NSMutableSet<NetImageView *> *giftVisibleArray;
@property (nonatomic, strong) UIView *lowLevelView;
@end
@implementation GiftAnimationHelper
- (instancetype)init {
if (self = [super init]) {
self.giftReuseArray = [NSMutableSet set];
self.giftVisibleArray = [NSMutableSet set];
}
return self;
}
- (void)beginGiftAnimation:(NSString *)giftUrl
startPoint:(CGPoint)startPoint
endPoint:(CGPoint)endPoint
isGiftCombing:(BOOL)isGiftCombing
toTargetView:(UIView *)targetView {
// Input validation
if ([NSString isEmpty:giftUrl] ){//|| CGPointEqualToPoint(startPoint, endPoint)) {
// NSLog(@"Invalid input parameters for gift animation");
return;
}
if (targetView == nil) {
return;
}
self.lowLevelView = targetView;
// Get or create gift image view
NetImageView *giftImageView = [self dequeueGiftImageView];
[self configureGiftImageView:giftImageView
withGiftUrl:giftUrl
startPoint:startPoint];
// Add to view hierarchy
[self.lowLevelView addSubview:giftImageView];
// Create and apply animation
CGFloat animationDuration = isGiftCombing ? kComboAnimationDuration : kStandardAnimationDuration;
CAAnimationGroup *animationGroup = isGiftCombing ?
[self createGiftComboAnimationStartPoint:startPoint endPoint:endPoint] :
[self createGiftAnimationStartPoint:startPoint endPoint:endPoint];
// Ensure correct starting position
giftImageView.layer.position = startPoint;
[giftImageView.layer addAnimation:animationGroup forKey:@"giftDisplayViewAnimation"];
// Schedule cleanup
[self performSelector:@selector(animationDidFinish:)
withObject:giftImageView
afterDelay:(animationDuration + kCleanupDelay)];
}
- (NetImageView *)dequeueGiftImageView {
NetImageView *giftImageView = [self.giftReuseArray anyObject];
if (giftImageView) {
[self.giftReuseArray removeObject:giftImageView];
} else {
giftImageView = [[NetImageView alloc] initWithFrame:CGRectMake(0, 0, kGiftImageSize, kGiftImageSize)];
[self.giftVisibleArray addObject:giftImageView];
}
return giftImageView;
}
- (void)configureGiftImageView:(NetImageView *)giftImageView
withGiftUrl:(NSString *)giftUrl
startPoint:(CGPoint)startPoint {
giftImageView.center = startPoint;
giftImageView.alpha = 1.0;
giftImageView.layer.anchorPoint = CGPointMake(0.5, 0.5);
giftImageView.imageUrl = giftUrl;
giftImageView.hidden = NO;
}
- (void)animationDidFinish:(NetImageView *)giftImageView {
[giftImageView removeFromSuperview];
giftImageView.hidden = YES;
[self.giftReuseArray addObject:giftImageView];
}
- (CAAnimationGroup *)createGiftComboAnimationStartPoint:(CGPoint)startPoint
endPoint:(CGPoint)endPoint {
// Initial shrink animation
CAKeyframeAnimation *initialScale = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
initialScale.duration = kComboScaleDuration;
initialScale.values = @[@1.0, @(kComboInitialScale)];
initialScale.repeatCount = 1;
initialScale.calculationMode = kCAAnimationCubic;
initialScale.removedOnCompletion = NO;
initialScale.fillMode = kCAFillModeForwards;
// Movement animation
CAKeyframeAnimation *movement = [CAKeyframeAnimation animationWithKeyPath:@"position"];
movement.duration = kComboMoveDuration;
movement.beginTime = kComboScaleDuration;
movement.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
movement.values = @[
[NSValue valueWithCGPoint:startPoint],
[NSValue valueWithCGPoint:endPoint]
];
movement.repeatCount = 1;
movement.removedOnCompletion = NO;
movement.fillMode = kCAFillModeForwards;
// Growth during movement animation
CAKeyframeAnimation *growthDuringMove = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
growthDuringMove.duration = kComboMoveDuration;
growthDuringMove.beginTime = kComboScaleDuration;
growthDuringMove.values = @[@(kComboInitialScale), @(kComboFinalScale)];
growthDuringMove.repeatCount = 1;
growthDuringMove.calculationMode = kCAAnimationCubic;
growthDuringMove.removedOnCompletion = NO;
growthDuringMove.fillMode = kCAFillModeForwards;
// Combine animations
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = kComboTotalDuration;
group.animations = @[initialScale, movement, growthDuringMove];
group.repeatCount = 1;
group.removedOnCompletion = NO;
group.fillMode = kCAFillModeForwards;
return group;
}
/**
* Creates a complex gift animation with multiple phases
* @param startPoint Initial position of the gift
* @param endPoint Final position of the gift
* @return CAAnimationGroup containing multiple scale and position animations
*/
- (CAAnimationGroup *)createGiftAnimationStartPoint:(CGPoint)startPoint
endPoint:(CGPoint)endPoint {
CGPoint centerPoint = CGPointMake(KScreenWidth / 2, KScreenHeight / 2);
// Initial scale up animation
CAKeyframeAnimation *initialScale = [CAKeyframeAnimation animation];
initialScale.duration = kStandardScaleDuration;
initialScale.keyPath = @"transform.scale";
// initialScale.values = @[@1.0, @1.5, @2.0, @1.5];
initialScale.values = @[@1.0, @1.2, @1.5, @1.2];
initialScale.repeatCount = 1;
initialScale.calculationMode = kCAAnimationCubic;
initialScale.removedOnCompletion = NO;
initialScale.fillMode = kCAFillModeForwards;
// Second phase scale animation
CAKeyframeAnimation *secondScale = [CAKeyframeAnimation animation];
secondScale.duration = kStandardScaleDuration;
secondScale.beginTime = kStandardSecondPhaseDelay;
secondScale.keyPath = @"transform.scale";
// secondScale.values = @[@1.5, @2.0, @2.5, @3.0];
secondScale.values = @[@1.2, @1.5, @2.0, @2.5];
secondScale.repeatCount = 1;
secondScale.calculationMode = kCAAnimationCubic;
secondScale.removedOnCompletion = NO;
secondScale.fillMode = kCAFillModeForwards;
// Move to center animation
CAKeyframeAnimation *moveToCenter = [CAKeyframeAnimation animation];
moveToCenter.duration = kStandardScaleDuration;
moveToCenter.beginTime = kStandardSecondPhaseDelay;
moveToCenter.keyPath = @"position";
moveToCenter.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
moveToCenter.values = @[
[NSValue valueWithCGPoint:startPoint],
[NSValue valueWithCGPoint:centerPoint]
];
moveToCenter.repeatCount = 1;
moveToCenter.removedOnCompletion = NO;
moveToCenter.fillMode = kCAFillModeForwards;
// Final scale down animation
CAKeyframeAnimation *finalScale = [CAKeyframeAnimation animation];
finalScale.duration = kStandardScaleDuration;
finalScale.beginTime = kStandardFinalPhaseDelay;
finalScale.keyPath = @"transform.scale";
// finalScale.values = @[@3.0, @2.5, @2.0, @1.5, @1.0];
finalScale.values = @[@2.5, @2.0, @1.5, @1.2, @1.0];
finalScale.repeatCount = 1;
// Move to final position animation
CAKeyframeAnimation *moveToEnd = [CAKeyframeAnimation animation];
moveToEnd.duration = kStandardScaleDuration;
moveToEnd.beginTime = kStandardFinalPhaseDelay;
moveToEnd.keyPath = @"position";
moveToEnd.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
moveToEnd.values = @[
[NSValue valueWithCGPoint:centerPoint],
[NSValue valueWithCGPoint:endPoint]
];
moveToEnd.repeatCount = 1;
// Combine all animations
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = kStandardTotalDuration;
group.animations = @[initialScale, secondScale, moveToCenter, finalScale, moveToEnd];
group.repeatCount = 1;
group.removedOnCompletion = NO;
group.fillMode = kCAFillModeForwards;
return group;
}
//- (CAAnimationGroup *)createGiftComboAnimationStartPoint:(CGPoint)startPoint
// endPoint:(CGPoint)endPoint {
// // 缩放动画1: 动画开始时缩放至 0.4
// CAKeyframeAnimation *scaleAnimation1 = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
// scaleAnimation1.duration = 0.1;
// scaleAnimation1.values = @[@1.0, @0.4];
// scaleAnimation1.repeatCount = 1;
// scaleAnimation1.calculationMode = kCAAnimationCubic;
// scaleAnimation1.removedOnCompletion = NO;
// scaleAnimation1.fillMode = kCAFillModeForwards;
//
// // 位移动画: 0.5秒内从起点移动到目标点
// CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
// positionAnimation.duration = 0.5;
// positionAnimation.beginTime = 0.1; // 缩放结束后开始位移
// positionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// positionAnimation.values = @[[NSValue valueWithCGPoint:startPoint], [NSValue valueWithCGPoint:endPoint]];
// positionAnimation.repeatCount = 1;
// positionAnimation.removedOnCompletion = NO;
// positionAnimation.fillMode = kCAFillModeForwards;
//
// // 缩放动画2: 在位移的过程中逐渐变大到 1.2
// CAKeyframeAnimation *scaleAnimation2 = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
// scaleAnimation2.duration = 0.5;
// scaleAnimation2.beginTime = 0.1; // 同时与位移动画进行
// scaleAnimation2.values = @[@0.4, @2];
// scaleAnimation2.repeatCount = 1;
// scaleAnimation2.calculationMode = kCAAnimationCubic;
// scaleAnimation2.removedOnCompletion = NO;
// scaleAnimation2.fillMode = kCAFillModeForwards;
//
// // 创建动画组
// CAAnimationGroup *group = [CAAnimationGroup animation];
// group.duration = 0.6; // 总时间为 0.1 (缩放) + 0.5 (位移与缩放)
// group.animations = @[scaleAnimation1, positionAnimation, scaleAnimation2];
// group.repeatCount = 1;
// group.removedOnCompletion = NO;
// group.fillMode = kCAFillModeForwards;
//
// return group;
//}
//
///// 图片运动的动画组
///// @param startPoint 开始的点
///// @param endPoint 结束的点
//- (CAAnimationGroup *)createGiftAnimationStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
// CGPoint centerPoint = CGPointMake(KScreenWidth / 2, KScreenHeight / 2);
// CAKeyframeAnimation *animation0 = [CAKeyframeAnimation animation];
// animation0.duration = 0.8;
// animation0.keyPath = @"transform.scale";
// animation0.values = @[@1.0,@1.5,@2.0,@1.5];
// animation0.repeatCount = 1;
// animation0.calculationMode = kCAAnimationCubic;
// animation0.removedOnCompletion = NO;
// animation0.fillMode = kCAFillModeForwards;
//
// CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animation];
// animation1.duration = 0.8;
// animation1.beginTime = 0.8;
// animation1.keyPath = @"transform.scale";
// animation1.values = @[@1.5,@2.0,@2.5,@3.0];
// animation1.repeatCount = 1;
// animation1.calculationMode = kCAAnimationCubic;
// animation1.removedOnCompletion = NO;
// animation1.fillMode = kCAFillModeForwards;
//
// CAKeyframeAnimation *animation2 = [CAKeyframeAnimation animation];
// animation2.duration = 0.8;
// animation2.beginTime = 0.8;
// animation2.keyPath = @"position";
// animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];;
// animation2.values = @[[NSValue valueWithCGPoint:startPoint],[NSValue valueWithCGPoint:CGPointMake(centerPoint.x ,centerPoint.y)]];
// animation2.repeatCount = 1;
// animation2.removedOnCompletion = NO;
// animation2.fillMode = kCAFillModeForwards;
//
// CAKeyframeAnimation *animation3 = [CAKeyframeAnimation animation];
// animation3.duration = 0.8;
// animation3.beginTime = 2.6;//0.8+0.8+1
// animation3.keyPath = @"transform.scale";
// animation3.values = @[@3,@2.5,@2,@1.5,@1];
// animation3.repeatCount = 1;
//
// CAKeyframeAnimation *animation4 = [CAKeyframeAnimation animation];
// animation4.duration = 0.8;
// animation4.beginTime = 2.6;
// animation4.keyPath = @"position";
// animation4.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
// animation4.values = @[[NSValue valueWithCGPoint:CGPointMake(centerPoint.x ,centerPoint.y)],[NSValue valueWithCGPoint:endPoint]];
// animation4.repeatCount = 1;
//
// CAAnimationGroup *group = [CAAnimationGroup animation];
// group.duration = 3.2;
// group.animations = @[animation0,animation1,animation2, animation3,animation4];
// group.repeatCount = 1;
// group.removedOnCompletion = NO;
// group.fillMode = kCAFillModeForwards;
//
// return group;
//}
@end