前奏
最近接到一个新的需求,需要一个自定义相机的圆形按钮并且实现中间镂空的动画,效果如下
![](https://img.haomeiwen.com/i3346256/77e07c0355a1a5b6.gif)
大概是一开始想得太简单了,实现过程中尝试了几种方式都发现不能完美实现效果,最后在折腾了半天后采用CADisplayLink+UIBezierPath总算实现了
实现
首先是申明的属性
///遮罩layer
@property (nonatomic, strong) CAShapeLayer * maskLayer;
///按钮视图
@property (nonatomic, strong) UIView * hollowV;
/// 按钮状态打开/关闭
@property (nonatomic, assign) BOOL isOpen;
/// 动画计时器
@property (nonatomic, strong) CADisplayLink * timer;
/// 记录动画开始时间
@property (nonatomic, assign) double tempLinkStamp;
/// 动画进度 0 - 1
@property (nonatomic, assign) CGFloat progress;
在viewDidLoad中创建视图
UIView * view = [[UIView alloc] initWithFrame:CGRectMake((Screen_Width - 100) / 2, Screen_Height - 300, 100, 100)];
view.backgroundColor = UIColor.whiteColor;
_hollowV = view;
接下来就是在_hollow中挖出我们的需要的镂空样式
这里实现原理是使用UIBezierPath绘制出我们需要的描边,然后通过为视图设置layer遮罩实现镂空的效果,具体如下
///根据半径绘制UIBezierPath
///radius为要挖去部分的半径
- (UIBezierPath *)pathWithHollowRadius:(CGFloat)radius {
//取视图边框路径
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(_hollowV.frame.size.width / 2, _hollowV.frame.size.height / 2) radius:_hollowV.frame.size.width / 2 startAngle:0 endAngle:2 * M_PI clockwise:NO];
//要挖去的部分
UIBezierPath * hollowPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(_hollowV.frame.size.width / 2, _hollowV.frame.size.height / 2) radius:radius startAngle:0 endAngle:2 * M_PI clockwise:YES];
[path appendPath:hollowPath];
return path;
}
使用绘制的的UIBezierPath设置遮罩layer
//镂空遮罩layer
_maskLayer = [CAShapeLayer layer];
_maskLayer.path = [self pathWithHollowRadius: _hollowV.frame.size.width / 2 - 4].CGPath;
_hollowV.layer.mask = _maskLayer;
于是乎我们就得到了这样一个镂空
![](https://img.haomeiwen.com/i3346256/ef88f2003218f023.png)
动画实现
首先之前有未完成的动画就直接停掉并直接设置到完成的状态
progress:镂空打开的进度,打开动画时progress 是 0 -> 1,关闭是 1 -> 0
/// 打开动画
- (void)openAni {
if (_timer) {
[self stopTimer];
self.progress = 0;
}
_isOpen = YES;
//清空之前的记录,重新开始动画
_tempLinkStamp = 0;
_timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(onlink:)];
[_timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
///关闭动画
- (void)closeAni {
if (_timer) {
[self stopTimer];
self.progress = 1;
}
_isOpen = NO;
//清空
_tempLinkStamp = 0;
_timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(onlink:)];
[_timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
CADisplayLink的回调处理。因为CADisplayLink的频率是和屏幕每一帧刷新同步的,所以动画实现后的效果灰常丝滑
- (void)onlink:(CADisplayLink *)link {
if (_tempLinkStamp == 0) {
_tempLinkStamp = link.timestamp;
}
//动画时间
CGFloat animationTime = 0.5;
//计算动画执行时间
CGFloat time = link.timestamp - _tempLinkStamp;
//以0.5秒为动画时间,计算当前动画进度
CGFloat progress = _isOpen ? time / animationTime : 1 - time / animationTime;
//更新视图
self.progress = progress;
if (time >= 0.5) {
[self stopTimer];
}
}
- (void)stopTimer {
[_timer invalidate];
_timer = nil;
}
最后根据计时器回调中计算的进度更新动画效果
///更新,根据镂空的进度,更新视图
-(void)setProgress:(CGFloat)progress {
_progress = progress;
NSLog(@"%f",progress);
//镂空layer
CAShapeLayer * layer = _hollowV.layer.mask;
//
CGFloat radius = (_hollowV.frame.size.width / 2 - 4) * progress;
layer.path = [self pathWithHollowRadius:radius].CGPath;
}
添加一个点击测试
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (_isOpen) {
[self closeAni];
} else {
[self openAni];
}
}
到这里就完成了!
总结
通过CADisplayLink实现的动画,在一定程度上可以有更自由的发挥空间,而且性能甚至更好
网友评论