博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
CAShapeLayer和CAGradientLayer
阅读量:5037 次
发布时间:2019-06-12

本文共 8959 字,大约阅读时间需要 29 分钟。

两个动画效果来了解一下CALayer的两个重要的subClass,CAGradientLayer和CAShapeLayer。 微视录制视频的时候那个进度效果和Spark相机类似,但是个人还是比较喜欢Spark相机的录制的效果。

CAShapeLayer

我们做一个和Spark相机一样的圆形进度,每一段有一种颜色,标识不同时间段录的视频。

首先,我们创建一个 UIView 的子类叫 RecordingCircleOverlayView 这样看起来比较有意义,然后我们看到圆形进度条有一个底色是灰色的圆形轨迹,所以我们创建一个 CAShapeLayer ,然后提供一个 CGPathRef 给它的 path 属性,我们使用 UIBezierPath 这个类的 bezierPathWithArcCenter:radius:startAngle:endAngle:clockwise: 这个方法给 CAShapeLayer 提供 path

图中我们可以看到有彩色一些片段,我们使用另外一个 CAShapeLayer 和同样的 CGPathRef 作为背景层,由于是同样的Path,所以我们给 UIBezierPath 创建一个属性,这样不用每次都重复创建。

 
CGPoint arcCenter = CGPointMake(CGRectGetMidY(self.bounds), CGRectGetMidX(self.bounds)); CGFloat radius = CGRectGetMidX(self.bounds) - insets.top - insets.bottom; self.circlePath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:radius startAngle:M_PI endAngle:-M_PI clockwise:NO];

开始角度 M_PI 和结束角度 -M_PI 和Spark相机是一样的逆时针方向,然后我们再创建一个背景层

 
CAShapeLayer *backgroundLayer = [CAShapeLayerlayer]; backgroundLayer.path = self.circlePath.CGPath; backgroundLayer.strokeColor = [[UIColor lightGrayColor] CGColor]; backgroundLayer.fillColor = [[UIColorclearColor] CGColor]; backgroundLayer.lineWidth = self.strokeWidth;

然后我们把 backgroundLayer 添加为 RecordingCircleOverlayView 的subLayer

 
[self.layer addSublayer:backgroundLayer];

如果我们build运行成功的话应该是这样的。

现在我们需要一个方法来实现开始和停止进度,如果我们回头去看 ,我们需要按下手指才会开始松开结束,首先 UITapGestureRecognizerUIControlEventTouchUpInside 没有方法检测按下和松开,但是我们可以用 UIControlEventTouchDown ,但是我们在Reveal里面并没有看到它是这么做的,所以最后决定使用复写 UIRespondertouchesBegan:WithEvent: and touchesEnded:WithEvent: 方法来实现。

有个这个方法后,我们可以控制 CAShapeLayerstrokeEnd 的属性大小来实现动画效果,首页我们先设置它的值为0然后把这个layer添加作为子类。

 
CAShapeLayer *progressLayer = [CAShapeLayerlayer]; progressLayer.path = self.circlePath.CGPath; progressLayer.strokeColor = [[selfrandomColor] CGColor]; progressLayer.fillColor = [[UIColorclearColor] CGColor]; progressLayer.lineWidth = self.strokeWidth; progressLayer.strokeEnd = 0.f;

然后我们发现有多个 CAShapeLayer 分别代表不同的段,而且每个 CAShapeLayer 都有自己的 strokeEnd ,所以我们创建一个数组,把每一个 CAShapeLayer 添加到数组里。

 
[self.progressLayers addObject:progressLayer];

继而我们又需要一个属性代表当前的正在增加可以动画的片段,所以我们添加一个属性来记录当前的进度的layer。

 
self.currentProgressLayer = progressLayer;

所以最后方法看起来是这样的。

 
- (void)addNewLayer { CAShapeLayer *progressLayer = [CAShapeLayer layer]; progressLayer.path = self.circlePath.CGPath; progressLayer.strokeColor = [[self randomColor] CGColor]; progressLayer.fillColor = [[UIColor clearColor] CGColor]; progressLayer.lineWidth = self.strokeWidth; progressLayer.strokeEnd = 0.f; [self.layer addSublayer:progressLayer]; [self.progressLayers addObject:progressLayer]; self.currentProgressLayer = progressLayer; }

为了让它可以有动画,我们有两个重点,其一我们可以使用 rotation transform 属性,但是我们使用 CAShapeLayerstrokeStartstrokeEnd 结合起来实现动画,其二停止动画后我们可以使用截图当前的状态同时移除动画,这样就可以保留每个状态的颜色。为了实现这些,我们使用 CABasicAnimationCAlayer 的属性 presentationLayer ,直接上代码。

 
- (void)updateAnimations { CGFloat duration = self.duration * (1.f - [[self.progressLayers firstObject] strokeEnd]); CGFloat strokeEndFinal = 1.f; for (CAShapeLayer *progressLayer in self.progressLayers) { CABasicAnimation *strokeEndAnimation = nil; strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; strokeEndAnimation.duration = duration; strokeEndAnimation.fromValue = @(progressLayer.strokeEnd); strokeEndAnimation.toValue = @(strokeEndFinal); strokeEndAnimation.autoreverses = NO; strokeEndAnimation.repeatCount = 0.f; strokeEndAnimation.fillMode = kCAFillModeForwards; strokeEndAnimation.removedOnCompletion = NO; strokeEndAnimation.delegate = self; [progressLayer addAnimation:strokeEndAnimation forKey:@"strokeEndAnimation"]; strokeEndFinal -= (progressLayer.strokeEnd - progressLayer.strokeStart); if (progressLayer != self.currentProgressLayer) { CABasicAnimation *strokeStartAnimation = nil; strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"]; strokeStartAnimation.duration = duration; strokeStartAnimation.fromValue = @(progressLayer.strokeStart); strokeStartAnimation.toValue = @(strokeEndFinal); strokeStartAnimation.autoreverses = NO; strokeStartAnimation.repeatCount = 0.f; strokeStartAnimation.fillMode = kCAFillModeForwards; strokeStartAnimation.removedOnCompletion = NO; [progressLayer addAnimation:strokeStartAnimation forKey:@"strokeStartAnimation"]; } } CABasicAnimation *backgroundLayerAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"]; backgroundLayerAnimation.duration = duration; backgroundLayerAnimation.fromValue = @(self.backgroundLayer.strokeStart); backgroundLayerAnimation.toValue = @(1.f); backgroundLayerAnimation.autoreverses = NO; backgroundLayerAnimation.repeatCount = 0.f; backgroundLayerAnimation.fillMode = kCAFillModeForwards; backgroundLayerAnimation.removedOnCompletion = NO; backgroundLayerAnimation.delegate = self; [self.backgroundLayer addAnimation:backgroundLayerAnimation forKey:@"strokeStartAnimation"]; }

上面代码中我们看到我们遍历了所有的 CAShapeLayer ,给每个 strokeEnd 添加了 CABasicAnimation 动画,然后给不是当前的layer的 strokeStart 属性添加了一个动画。再来看看duration,假设一圈代表45秒钟,这个意味着每次停止之后又开始的话duration肯定是减少的,所以用duration代表一圈剩余的可以录制的时间,再看 strekeEndFinal ,假设有很多段,肯定不是每个段的strkeEnd都是1所以这个是用来标识每段可以达到的最终距离一圈为(0-1)。最后我们需要更新background layer除去有彩色段剩余的地方。

你可能注意到上面的代码里面并没有移除动画,所以对于显示每一个 CAShapeLayer 我们设置都是通过layers的 presentationLayer 设置 strokeStartstrokeEnd ,然后移除CAShapeLayer上的所有动画。

While an animation is in progress, you can retrieve this object and use it to get the current values for those animations.

所以把上面所说的结合起来,代码应该是这样的。

 
- (void)removeAnimations { for (CAShapeLayer *progressLayer in self.progressLayers) { progressLayer.strokeStart = [progressLayer.presentationLayer strokeStart]; progressLayer.strokeEnd = [progressLayer.presentationLayer strokeEnd]; [progressLayer removeAllAnimations]; } self.backgroundLayer.strokeStart = [self.backgroundLayer.presentationLayer strokeStart]; [self.backgroundLayer removeAllAnimations]; }

最后,还有一个问题是我们需要确保我们完成了动画以后手指按下不要保持添加layer和更新动画这些操作,所以我们可以设置一个代理方法像这样,就大功告成了。

 
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { if (self.hasFinishedAnimating == NO && flag) { [self removeAnimations]; self.finishedAnimating = flag; } }

最后你可以在 上面下载这个项目。

CAGradientLayer

首页我们创建一个 UIView 的子类,然后我们使用 CAGradientLayer 作为默认的 CALayer

 
+ (Class)layerClass { return [CAGradientLayer class]; }

CAGradientLayerCALayer 的一个子类,添加了一些额外的属性,我们将是使用 colors , startPoint , endPoint 这些来创建一个有梯度的动画.

现在有几个方法来实现这种彩色的效果,一种是我现在将要使用的创建一个包含 UIColor 的数组,有不同的色调的值,在你的 initWithFrame 方法里添加一下代码:

 
// Use a horizontal gradient CAGradientLayer *layer = (id)[self layer]; [layer setStartPoint:CGPointMake(0.0, 0.5)]; [layer setEndPoint:CGPointMake(1.0, 0.5)]; // Create colors using hues in +5 increments NSMutableArray *colors = [NSMutableArray array]; for (NSInteger hue = 0; hue <= 360; hue += 5) { UIColor *color; color = [UIColor colorWithHue:1.0 * hue / 360.0 saturation:1.0 brightness:1.0 alpha:1.0]; [colors addObject:(id)[color CGColor]]; } [layer setColors:[NSArray arrayWithArray:colors]];

现在运行你可以看见一个水平光谱图,下一步创建移动的效果,我们可以遍历这个颜色的数组使用layer animation,一个动画结束的时候会前面的颜色方法最后重复这个进度,方法是这样:

 
- (void)performAnimation { // Move the last color in the array to the front // shifting all the other colors. CAGradientLayer *layer = (id)[self layer]; NSMutableArray *mutable = [[layer colors] mutableCopy]; id lastColor = [[mutable lastObject] retain]; [mutable removeLastObject]; [mutable insertObject:lastColor atIndex:0]; [lastColor release]; NSArray *shiftedColors = [NSArray arrayWithArray:mutable]; [mutable release]; // Update the colors on the model layer [layer setColors:shiftedColors]; // Create an animation to slowly move the gradient left to right. CABasicAnimation *animation; animation = [CABasicAnimation animationWithKeyPath:@"colors"]; [animation setToValue:shiftedColors]; [animation setDuration:0.08]; [animation setRemovedOnCompletion:YES]; [animation setFillMode:kCAFillModeForwards]; [animation setDelegate:self]; [layer addAnimation:animation forKey:@"animateGradient"]; } - (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag { [self performAnimation]; }

为了增加一个标识进度的进行,我们可以使用mask属性来屏蔽一部分,在头文件中添加两个属性:

 
@property (nonatomic, readonly) CALayer *maskLayer; @property (nonatomic, assign) CGFloat progress;

然后在 initWithFrame: 里面添加:

 
maskLayer = [CALayer layer]; [maskLayer setFrame:CGRectMake(0, 0, 0, frame.size.height)]; [maskLayer setBackgroundColor:[[UIColor blackColor] CGColor]]; [layer setMask:maskLayer];

创建一个宽度为0的mask覆盖整个View,mask的颜色不重要,当我们 progress 属性更新的时候我们会增加它的宽度,所以复写 setProgress: 方法像下面这样:

 
- (void)setProgress:(CGFloat)value { if (progress != value) { // Progress values go from 0.0 to 1.0 progress = MIN(1.0, fabs(value)); [self setNeedsLayout]; } } - (void)layoutSubviews { // Resize our mask layer based on the current progress CGRect maskRect = [maskLayer frame]; maskRect.size.width = CGRectGetWidth([self bounds]) * progress; [maskLayer setFrame:maskRect]; }

现在当我们设置 progress 值的时候我们要确保它在0到1之间,然后下一步在 layoutSubviews 里面我们重新定义mask的值。

当然也可以从github看项目的更多细节。

转载于:https://www.cnblogs.com/Free-Thinker/p/5948614.html

你可能感兴趣的文章
C#正则表达式引发的CPU跑高问题以及解决方法
查看>>
云计算之路-阿里云上:“黑色30秒”走了,“黑色1秒”来了,真相也许大白了...
查看>>
APScheduler调度器
查看>>
设计模式——原型模式
查看>>
【jQuery UI 1.8 The User Interface Library for jQuery】.学习笔记.1.CSS框架和其他功能
查看>>
如何一个pdf文件拆分为若干个pdf文件
查看>>
web.xml中listener、 filter、servlet 加载顺序及其详解
查看>>
前端chrome浏览器调试总结
查看>>
获取手机验证码修改
查看>>
数据库连接
查看>>
python中数据的变量和字符串的常用使用方法
查看>>
等价类划分进阶篇
查看>>
delphi.指针.PChar
查看>>
Objective - C基础: 第四天 - 10.SEL类型的基本认识
查看>>
java 字符串转json,json转对象等等...
查看>>
极客前端部分题目收集【索引】
查看>>
第四天 selenium的安装及使用
查看>>
关于js的设计模式(简单工厂模式,构造函数模式,原型模式,混合模式,动态模式)...
查看>>
KMPnext数组循环节理解 HDU1358
查看>>
android调试debug快捷键
查看>>