如下,第一次看到陌陌的这个效果,一半惊艳一半懵逼:动画确实有震惊的效果,但是怎么实现的?
分解一下:
- 抖动
- 添加黑白闪烁的半透明蒙层
- 用白色折线画闪电
- 对UILabel截图,图斜劈成两半,后半部分平移后旋转
- 在底部放张碎片的图片
先写出基本的视图,一个UIView对象用于设置圆角,背景颜色等等,一个UILabel对象用于显示内容:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
self.bgView = [[UIView alloc] init];
self.bgView.backgroundColor = [UIColor colorWithRGB:0xADCCFF alpha:1];
self.bgView.left = kMainScreenWidth/2;
self.bgView.top = 100;
self.bgView.layer.cornerRadius = 4;
self.bgView.clipsToBounds = YES;
[self.view addSubview:self.bgView];
self.messageLabel = [[UILabel alloc] init];
self.messageLabel.text = @"卧槽震惊";
[self.messageLabel sizeToFit];
self.messageLabel.textAlignment = NSTextAlignmentCenter;
self.bgView.height = self.messageLabel.height + 20;
self.bgView.width = self.messageLabel.width + 30;
self.messageLabel.center = CGPointMake(self.bgView.width/2, self.bgView.height/2);
[self.bgView addSubview:self.messageLabel];
}
先看一个最难的,步骤4:
UILabel对象的截取
动画比较难的就是对label截取任意形状的图了。
思路:通过把显示内容的label,截取成前后两个梯形获取对应梯形图片或者view,拼接两个图片,这样动画之前看起来和单个label显示没有区别。当需要动画的时候,后边的梯形先向右平移几个dp,然后向下旋转几度。具体平移和旋转的数字可以根据效果自行调整。
首先利用贝塞尔曲线和CAShapeLayer获得自己想要的形状。得到CAShapeLayer对象,先看看长什么样子:
[self.messageLabel.layer addSublayer:shapeLayer];
添加完CAShapeLayer对象后:
黑色遮盖部分就是CAShapeLayer对象,符合,形状符合预期。但是我想要的是遮盖部分显示,未遮盖的“震惊”部分变透明。这就用到了CALayer的mask属性。
该属性也是一个CALayer对象。 简单理解就是:如果设置了CALayer对象mask属性,那么当前CALayer对象只能显示被mask属性遮盖的部分,其余部分变透明——正好符合我们的预期:
self.messageLabel.layer.mask = shapeLayer;
通过对现在的UILabel对象截屏得一个只显示前半部分梯形内图像后半部分透明的视图snapshotView1,然后添加到self.bgView上:
UIView *snapshotView1 = [self.messageLabel snapshotViewAfterScreenUpdates:YES];
snapshotView1.center = CGPointMake(self.bgView.width/2, self.bgView.height/2);
[self.bgView addSubview:snapshotView1];
同理可得只显示后半部分内容前半部分透明的视图snapshotView2,再添加到self.bgView上。
实际上snapshotView1,snapshotView2和self.messageLabel的size是一样的,所以将snapshotView1和snapshotView2的位置设置为一样的就能得到和图一一样的视觉效果。
动画时断裂效果代码:
- (void)rupture {
CGAffineTransform transform = CGAffineTransformIdentity;
// 向右平移3dp
transform = CGAffineTransformTranslate(transform, 3, 0);
// 旋转10度
transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 10.0);
self.snapshotView2.layer.affineTransform = transform;
}
抖动
抖动效果的实现就是使用了UIView提供的弹簧效果动画:
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^__nullable)(BOOL finished))completion
就是这animations block里设置向上移动5dp,因为弹簧效果,会不停的抖动
- (void)shake {
[UIView animateWithDuration:1.0 delay:0 usingSpringWithDamping:0.08 initialSpringVelocity:30 options:0 animations:^{
self.bgView.top = self.bgView.top-5;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.1 animations:^{
self.bgView.top = 100;
}];
// 抖动结束断裂
[self rupture];
// 显示断裂碎片
[self showSplinterView];
}];
}
黑白闪烁的背景
思路:往self.bgView上添加一个黑色半透明的视图对象,同时循环动画改变视图对象的透明度。这样就有了闪烁效果
# pragma mark - 添加黑白闪烁的背景蒙层
- (void)flashMask {
[self stopFlashMask];
self.flashMaskView = [[UIView alloc] initWithFrame:self.bgView.bounds];
self.flashMaskView.backgroundColor = [UIColor blackColor];
self.flashMaskView.alpha = 0.1;
[self.flashMaskView.layer addAnimation:[self opacityForever_Animation:0.1] forKey:nil];
[self.bgView addSubview:self.flashMaskView];
}
- (void)stopFlashMask {
if (self.flashMaskView && [self.flashMaskView superview]) {
[self.flashMaskView removeFromSuperview];
}
}
- (CABasicAnimation *)opacityForever_Animation:(float)time
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];//必须写opacity才行。
animation.fromValue = [NSNumber numberWithFloat:0.7f];
animation.toValue = [NSNumber numberWithFloat:0.2f];//这是透明度。
animation.autoreverses = YES;
animation.duration = time;
animation.repeatCount = 3;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];///没有的话是均匀的动画。
return animation;
}
闪电
思路:闪电就是白色的折线,尽量画的写实一点就行了。
生成一个UIBezierPath对象,然后不停的调用addLineToPoint:方法添加一段一段的线,然后根据折线生成CAShapeLayer对象,给CAShapeLayer对象添加动画方法,使得折线动画出来而不是一下子全部出现
- (void)flash {
[self removeFlash];
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(self.bgView.width*5/9, 0)];
[path addLineToPoint:CGPointMake(self.bgView.width*5/9 - 3, self.bgView.height/10)];
[path addLineToPoint:CGPointMake(self.bgView.width*5/9+self.bgView.width/9, self.bgView.height/10 + self.bgView.height/7)];
[path addLineToPoint:CGPointMake(self.bgView.width*5/9+self.bgView.width/9 - 3, self.bgView.height/10 + self.bgView.height/7 + self.bgView.height/5)];
[path addLineToPoint:CGPointMake(self.bgView.width*5/9+self.bgView.width/9 - 3 - 4, self.bgView.height/10 + self.bgView.height/7 + self.bgView.height/5 + 2)];
[path addLineToPoint:CGPointMake(self.bgView.width*5/9+self.bgView.width/9 - 3 - 4 - 1, self.bgView.height/10 + self.bgView.height/7 + self.bgView.height/5 + 2 + 2)];
[path addLineToPoint:CGPointMake(self.bgView.width*5/9+self.bgView.width/9 - 3 - 4 - 1 + self.bgView.width/20, self.bgView.height/10 + self.bgView.height/7 + self.bgView.height/5 + 2 + 2 + self.bgView.height/7)];
[path addLineToPoint:CGPointMake(self.bgView.width*5/9+self.bgView.width/9 - 3 - 4 - 1 + self.bgView.width/20 - 3, self.bgView.height/10 + self.bgView.height/7 + self.bgView.height/5 + 2 + 2 + self.bgView.height/7 + 2)];
[path addLineToPoint:CGPointMake(self.bgView.width*5/9+self.bgView.width/9 - 3 - 4 - 1 + self.bgView.width/20 , self.bgView.height/10 + self.bgView.height/7 + self.bgView.height/5 + 2 + 2 + self.bgView.height/7 + 3)];
self.flashLayer = [CAShapeLayer layer];
self.flashLayer.strokeColor = [UIColor whiteColor].CGColor;
self.flashLayer.fillColor = [UIColor clearColor].CGColor;
self.flashLayer.path = path.CGPath;
[self.bgView.layer addSublayer:self.flashLayer];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
pathAnimation.duration = 0.3;
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
pathAnimation.repeatCount = 1;
pathAnimation.delegate = self;
[self.flashLayer addAnimation:pathAnimation forKey:nil];
}
把以上几种效果,适当组合一下就山寨个差不多了。