339 lines
14 KiB
Objective-C
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
|