CADisplayLink+UIBezierPath实现镂空动画

2020-06-06  本文已影响0人  Lucky_C

前奏

最近接到一个新的需求,需要一个自定义相机的圆形按钮并且实现中间镂空的动画,效果如下

image

大概是一开始想得太简单了,实现过程中尝试了几种方式都发现不能完美实现效果,最后在折腾了半天后采用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;

于是乎我们就得到了这样一个镂空


Simulator Screen Shot - iPhone 11 Pro Max - 2020-06-05 at 22.18.13.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实现的动画,在一定程度上可以有更自由的发挥空间,而且性能甚至更好

上一篇下一篇

猜你喜欢

热点阅读