首先是效果展示
这里是有一个气泡的样式,在做无规则的边界、大小、位移变化。

23916860-3ed465a753cd7344.gif
这里的实现,我考虑使用CAShapeLayer和UIBezierPath结合实现,这里动画用的是CABasicAnimation的path属性动画。
先贴出所有代码,后续有时间,逐步分析。
这是.h文件
#import <UIKit/UIKit.h>
#import "BubbleModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface BubbleView : UIView
/** */
typedef void(^__nullable ScaleAnimStartCompletion)(BubbleModel *model);
@property (nonatomic,copy) NSArray <BubbleModel*>*scaleArr;
@property (nonatomic,copy) ScaleAnimStartCompletion ScaleAnimStart;
-(void)startScaleAni;
-(void)endScaleAni;
@end
NS_ASSUME_NONNULL_END
这是.m文件
#import "BubbleView.h"
@interface BubbleView ()<CAAnimationDelegate>
@property (nonatomic,assign) CGFloat width;
@property (nonatomic,assign) CGFloat height;
@property (nonatomic,strong) CAShapeLayer *myShapeLayer;
@property (nonatomic,strong) UIBezierPath *myTempToValue;
@property (nonatomic,assign) NSInteger scaleArrIndex;
@end
@implementation BubbleView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setUI];
}
return self;
}
-(void)setUI {
//
self.backgroundColor = [UIColor clearColor];
//
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
effectView.alpha = 0.8;
[self addSubview:effectView];
[effectView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(0);
}];
//
self.width = self.frame.size.width;
self.height = self.frame.size.height;
//
self.myShapeLayer = [[CAShapeLayer alloc]init];
self.myTempToValue = [[UIBezierPath alloc]init];
//
self.myShapeLayer.frame = self.bounds;
self.myShapeLayer.path = [self myToValue].CGPath;
//
self.layer.mask = self.myShapeLayer;
// [self.layer addSublayer:self.myShapeLayer];
//
[self startAnimate];
}
-(void)startAnimate {
[self.myShapeLayer addAnimation:[self pathBasicAnimate] forKey:@"pathBasicAnimate"];
}
-(CABasicAnimation*)pathBasicAnimate {
CABasicAnimation *anima = [CABasicAnimation animationWithKeyPath:@"path"];
anima.delegate = self;
anima.removedOnCompletion = false;
anima.repeatCount = 1;
anima.duration = 2;
anima.fillMode = kCAFillModeForwards;
//
UIBezierPath *path = [self myToValue];
anima.toValue = (__bridge id _Nullable)(path.CGPath);
//
self.myTempToValue = path;
//
return anima;
}
- (UIBezierPath *)myToValue {
//
CGFloat R = 110;
//
CGFloat aR = [self getRandomCGFloatWithFrom:R-10 WithTo:R+10];
CGFloat bR = [self getRandomCGFloatWithFrom:R-10 WithTo:R+10];
CGFloat cR = [self getRandomCGFloatWithFrom:R-10 WithTo:R+10];
CGFloat dR = [self getRandomCGFloatWithFrom:R-10 WithTo:R+10];
CGFloat centerX = self.width/2;
CGFloat centerY = self.height/2;
CGFloat minF = 0.4;
CGFloat maxF = 0.75;
CGFloat offP = 5;
//
UIBezierPath *path = [[UIBezierPath alloc]init];
///AB段
//
CGPoint pointA = CGPointMake(centerX-aR, centerY);
CGPoint pointB = CGPointMake(centerX, centerY-bR);
//
CGPoint controlAB1 = CGPointMake(pointA.x + [self getRandomCGFloatWithFrom:-offP WithTo:offP], pointA.y - bR * [self getRandomCGFloatWithFrom:minF WithTo:maxF]);
CGPoint controlAB2 = CGPointMake(pointB.x - aR * [self getRandomCGFloatWithFrom:minF WithTo:maxF], pointB.y + [self getRandomCGFloatWithFrom:-offP WithTo:offP]);
[path moveToPoint:pointA];
[path addCurveToPoint:pointB controlPoint1:controlAB1 controlPoint2:controlAB2];
///BC段
//
CGPoint pointC = CGPointMake(pointB.x+cR, pointA.y);
//
CGPoint controlBC1 = CGPointMake(pointB.x + cR * [self getRandomCGFloatWithFrom:minF WithTo:maxF], pointB.y + [self getRandomCGFloatWithFrom:-offP WithTo:offP]);
CGPoint controlBC2 = CGPointMake(pointC.x + [self getRandomCGFloatWithFrom:-offP WithTo:offP], pointC.y - bR * [self getRandomCGFloatWithFrom:minF WithTo:maxF]);
[path addCurveToPoint:pointC controlPoint1:controlBC1 controlPoint2:controlBC2];
///CD段
//
CGPoint pointD = CGPointMake(pointB.x, pointC.y + dR);
//
CGPoint controlCD1 = CGPointMake(pointC.x + [self getRandomCGFloatWithFrom:-offP WithTo:offP], pointC.y + dR*[self getRandomCGFloatWithFrom:minF WithTo:maxF]);
CGPoint controlCD2 = CGPointMake(pointD.x + cR * [self getRandomCGFloatWithFrom:minF WithTo:maxF], pointD.y + [self getRandomCGFloatWithFrom:-offP WithTo:offP]);
[path addCurveToPoint:pointD controlPoint1:controlCD1 controlPoint2:controlCD2];
///DA段
//
CGPoint controlDA1 = CGPointMake(pointD.x - aR*[self getRandomCGFloatWithFrom:minF WithTo:maxF], pointD.y + [self getRandomCGFloatWithFrom:-offP WithTo:offP]);
CGPoint controlDA2 = CGPointMake(pointA.x + [self getRandomCGFloatWithFrom:-offP WithTo:offP], pointA.y + dR*[self getRandomCGFloatWithFrom:minF WithTo:maxF]);
[path addCurveToPoint:pointA controlPoint1:controlDA1 controlPoint2:controlDA2];
//
return path;
}
///获取随机小数
- (CGFloat)getRandomCGFloatWithFrom:(CGFloat)from WithTo:(CGFloat)to {
//
NSInteger tempInt1 = (NSInteger)(from*100);
NSInteger tempInt2 = (NSInteger)(to*100);
NSInteger Rnum = [self getRandomNumberWithFrom:tempInt1 WithTo:tempInt2];
CGFloat temp = (CGFloat)Rnum/100;
return temp;
}
- (NSInteger)getRandomNumberWithFrom:(NSInteger)from WithTo:(NSInteger)to {
NSInteger temp = arc4random() % (to - from + 1);
return (from + temp);
}
//
- (void)animationDidStart:(CAAnimation *)anim {
// NSLog(@"%s-%d-%@",__FUNCTION__,__LINE__,[NSThread currentThread]);
}
//
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
//1
if (anim == [self.myShapeLayer animationForKey:@"pathBasicAnimate"]) {
//
self.myShapeLayer.path = self.myTempToValue.CGPath;
//
[self startAnimate];
}
if (anim == [self.myShapeLayer animationForKey:@"scaleBasicAnimate"]) {
//
[self startScale];
}
}
//-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//
// CABasicAnimation *anima = [self.myShapeLayer animationForKey:@"scaleBasicAnimate"];
// //
// if (anima != nil) {
// //
// self.scaleArr = @[
// [[BubbleModel alloc]initWithFromValue:1 WithToValue:1 WithDuration:1]
// ];
// self.scaleArrIndex = 0;
// [self startScale];
// }
// else{
// //
// self.scaleArr = @[
// [[BubbleModel alloc]initWithFromValue:1 WithToValue:0.5 WithDuration:3],
// [[BubbleModel alloc]initWithFromValue:0.5 WithToValue:1.5 WithDuration:7],
// [[BubbleModel alloc]initWithFromValue:1.5 WithToValue:1.5 WithDuration:3],
// [[BubbleModel alloc]initWithFromValue:1.5 WithToValue:1 WithDuration:3]
// ];
// self.scaleArrIndex = 0;
// [self startScale];
// }
//}
-(void)startScaleAni {
//
self.scaleArrIndex = 0;
[self startScale];
}
-(void)endScaleAni {
//
// self.scaleArrIndex = 0;
// [self startScale];
// self.scaleArr = @[];
[self.myShapeLayer removeAnimationForKey:@"scaleBasicAnimate"];
}
-(void)startScale {
//
if (self.scaleArrIndex >= 0 && self.scaleArrIndex < self.scaleArr.count) {
//
BubbleModel *model = self.scaleArr[self.scaleArrIndex];
//
if (model.isShake) {
//震动一下
[self impactFeedbackGenerator];
}
//
[self.myShapeLayer addAnimation:[self scaleBasicAnimateWithFromValue:model.fromValue WithToValue:model.toValue WithDuration:model.duration] forKey:@"scaleBasicAnimate"];
//
self.scaleArrIndex += 1;
//
self.ScaleAnimStart(model);
}
else{
self.scaleArrIndex = 1;
[self startScale];
}
}
-(CABasicAnimation*)scaleBasicAnimateWithFromValue:(CGFloat)fromValue WithToValue:(CGFloat)toValue WithDuration:(CFTimeInterval)duration {
//
CABasicAnimation *animate = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
animate.delegate = self;
animate.removedOnCompletion = false;
animate.fillMode = kCAFillModeForwards;
animate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
//
animate.duration = duration;
//
animate.fromValue = [NSNumber numberWithFloat:fromValue];
animate.toValue = [NSNumber numberWithFloat:toValue];
//
return animate;
}
- (void)impactFeedbackGenerator {
//
UIImpactFeedbackGenerator *obj = [[UIImpactFeedbackGenerator alloc]initWithStyle:UIImpactFeedbackStyleHeavy];
[obj prepare];
[obj impactOccurred];
}
@end