CAShapeLayer和CAGradientLayer

两个动画效果来了解一下CALayer的两个重要的subClass:CAGradientLayer和CAShapeLayer。先看CAShapeLayer,我们做一个和Spark相机一样的圆形进度,每一段有一种颜色,标识不同时间段录的视频。

“”

阅读器

CAShapeLayerCAGradientLayer

转自KooFrank‘s Blog

两个动画效果来了解一下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创建一个属性,这样不用每次都重复创建。

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

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

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

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

  1. [self.layer addSublayer:backgroundLayer];

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

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

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

  1. CAShapeLayer *progressLayer = [CAShapeLayerlayer];
  2. progressLayer.path = self.circlePath.CGPath;
  3. progressLayer.strokeColor = [[selfrandomColor] CGColor];
  4. progressLayer.fillColor = [[UIColorclearColor] CGColor];
  5. progressLayer.lineWidth = self.strokeWidth;
  6. progressLayer.strokeEnd = 0.f;

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

  1. [self.progressLayers addObject:progressLayer];

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

  1. self.currentProgressLayer = progressLayer;

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

  1. - (void)addNewLayer
  2. {
  3. CAShapeLayer *progressLayer = [CAShapeLayer layer];
  4. progressLayer.path = self.circlePath.CGPath;
  5. progressLayer.strokeColor = [[self randomColor] CGColor];
  6. progressLayer.fillColor = [[UIColor clearColor] CGColor];
  7. progressLayer.lineWidth = self.strokeWidth;
  8. progressLayer.strokeEnd = 0.f;
  9. [self.layer addSublayer:progressLayer];
  10. [self.progressLayers addObject:progressLayer];
  11. self.currentProgressLayer = progressLayer;
  12. }

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

  1. - (void)updateAnimations
  2. {
  3. CGFloat duration = self.duration * (1.f - [[self.progressLayers firstObject] strokeEnd]);
  4. CGFloat strokeEndFinal = 1.f;
  5. for (CAShapeLayer *progressLayer in self.progressLayers)
  6. {
  7. CABasicAnimation *strokeEndAnimation = nil;
  8. strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
  9. strokeEndAnimation.duration = duration;
  10. strokeEndAnimation.fromValue = @(progressLayer.strokeEnd);
  11. strokeEndAnimation.toValue = @(strokeEndFinal);
  12. strokeEndAnimation.autoreverses = NO;
  13. strokeEndAnimation.repeatCount = 0.f;
  14. strokeEndAnimation.fillMode = kCAFillModeForwards;
  15. strokeEndAnimation.removedOnCompletion = NO;
  16. strokeEndAnimation.delegate = self;
  17. [progressLayer addAnimation:strokeEndAnimation forKey:@"strokeEndAnimation"];
  18. strokeEndFinal -= (progressLayer.strokeEnd - progressLayer.strokeStart);
  19. if (progressLayer != self.currentProgressLayer)
  20. {
  21. CABasicAnimation *strokeStartAnimation = nil;
  22. strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
  23. strokeStartAnimation.duration = duration;
  24. strokeStartAnimation.fromValue = @(progressLayer.strokeStart);
  25. strokeStartAnimation.toValue = @(strokeEndFinal);
  26. strokeStartAnimation.autoreverses = NO;
  27. strokeStartAnimation.repeatCount = 0.f;
  28. strokeStartAnimation.fillMode = kCAFillModeForwards;
  29. strokeStartAnimation.removedOnCompletion = NO;
  30. [progressLayer addAnimation:strokeStartAnimation forKey:@"strokeStartAnimation"];
  31. }
  32. }
  33. CABasicAnimation *backgroundLayerAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
  34. backgroundLayerAnimation.duration = duration;
  35. backgroundLayerAnimation.fromValue = @(self.backgroundLayer.strokeStart);
  36. backgroundLayerAnimation.toValue = @(1.f);
  37. backgroundLayerAnimation.autoreverses = NO;
  38. backgroundLayerAnimation.repeatCount = 0.f;
  39. backgroundLayerAnimation.fillMode = kCAFillModeForwards;
  40. backgroundLayerAnimation.removedOnCompletion = NO;
  41. backgroundLayerAnimation.delegate = self;
  42. [self.backgroundLayer addAnimation:backgroundLayerAnimation forKey:@"strokeStartAnimation"];
  43. }

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

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

presentationLayer在文档中是这么说的:

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

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

  1. - (void)removeAnimations
  2. {
  3. for (CAShapeLayer *progressLayer in self.progressLayers)
  4. {
  5. progressLayer.strokeStart = [progressLayer.presentationLayer strokeStart];
  6. progressLayer.strokeEnd = [progressLayer.presentationLayer strokeEnd];
  7. [progressLayer removeAllAnimations];
  8. }
  9. self.backgroundLayer.strokeStart = [self.backgroundLayer.presentationLayer strokeStart];
  10. [self.backgroundLayer removeAllAnimations];
  11. }

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

  1. - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
  2. {
  3. if (self.hasFinishedAnimating == NO && flag)
  4. {
  5. [self removeAnimations];
  6. self.finishedAnimating = flag;
  7. }
  8. }

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

CAGradientLayer

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

  1. + (Class)layerClass {
  2. return [CAGradientLayer class];
  3. }

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

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

  1. // Use a horizontal gradient
  2. CAGradientLayer *layer = (id)[self layer];
  3. [layer setStartPoint:CGPointMake(0.0, 0.5)];
  4. [layer setEndPoint:CGPointMake(1.0, 0.5)];
  5. // Create colors using hues in +5 increments
  6. NSMutableArray *colors = [NSMutableArray array];
  7. for (NSInteger hue = 0; hue <= 360; hue += 5) {
  8. UIColor *color;
  9. color = [UIColor colorWithHue:1.0 * hue / 360.0
  10. saturation:1.0
  11. brightness:1.0
  12. alpha:1.0];
  13. [colors addObject:(id)[color CGColor]];
  14. }
  15. [layer setColors:[NSArray arrayWithArray:colors]];

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

  1. - (void)performAnimation {
  2. // Move the last color in the array to the front
  3. // shifting all the other colors.
  4. CAGradientLayer *layer = (id)[self layer];
  5. NSMutableArray *mutable = [[layer colors] mutableCopy];
  6. id lastColor = [[mutable lastObject] retain];
  7. [mutable removeLastObject];
  8. [mutable insertObject:lastColor atIndex:0];
  9. [lastColor release];
  10. NSArray *shiftedColors = [NSArray arrayWithArray:mutable];
  11. [mutable release];
  12. // Update the colors on the model layer
  13. [layer setColors:shiftedColors];
  14. // Create an animation to slowly move the gradient left to right.
  15. CABasicAnimation *animation;
  16. animation = [CABasicAnimation animationWithKeyPath:@"colors"];
  17. [animation setToValue:shiftedColors];
  18. [animation setDuration:0.08];
  19. [animation setRemovedOnCompletion:YES];
  20. [animation setFillMode:kCAFillModeForwards];
  21. [animation setDelegate:self];
  22. [layer addAnimation:animation forKey:@"animateGradient"];
  23. }
  24. - (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag {
  25. [self performAnimation];
  26. }

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

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

然后在initWithFrame:里面添加:

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

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

  1. - (void)setProgress:(CGFloat)value {
  2. if (progress != value) {
  3. // Progress values go from 0.0 to 1.0
  4. progress = MIN(1.0, fabs(value));
  5. [self setNeedsLayout];
  6. }
  7. }
  8. - (void)layoutSubviews {
  9. // Resize our mask layer based on the current progress
  10. CGRect maskRect = [maskLayer frame];
  11. maskRect.size.width = CGRectGetWidth([self bounds]) * progress;
  12. [maskLayer setFrame:maskRect];
  13. }

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

参考:

Spark Camera’s recording meter

Animated progress view with CAGradientLayer

时间: 2024-08-26 16:40:25

CAShapeLayer和CAGradientLayer的相关文章

iOS实现一个颜色渐变的弧形进度条

在Github上看到一些进度条的功能,都是通过Core Graph来实现.无所谓正确与否,但是开发效率明显就差很多了,而且运行效率还是值得考究的.其实使用苹果提供的Core Animation能够非常简单和方便的实现环形进度条效果,而且还可以高效的保证动画效果,无论是前进还是后退.文字水平比较有限,就多用代码说话. 1.先来一个结果 80%的状态: 99%的状态: 2.需要用到的宏: #define degreesToRadians(x) (M_PI*(x)/180.0) //把角度转换成PI的

【iOS实现一个颜色渐变的弧形进度条】

在Github上看到一些进度条的功能,都是通过Core Graph来实现.无所谓正确与否,但是开发效率明显就差很多了,而且运行效率还是值得考究的.其实使用苹果提供的Core Animation能够非常简单和方便的实现环形进度条效果,而且还可以高效的保证动画效果,无论是前进还是后退.文字水平比较有限,就多用代码说话. 1.先来一个结果 80%的状态: 99%的状态: 2.需要用到的宏: #define degreesToRadians(x) (M_PI*(x)/180.0) //把角度转换成PI的

IOS CAShapeLayer CAGradientLayer UIBezierPath 使用实例

CGRect rect = CGRectMake(100, 100, 100, 100); UIView * bgView = [[UIView alloc]initWithFrame:rect]; bgView.backgroundColor = [UIColor grayColor]; [self.view addSubview:bgView]; CAShapeLayer * trackLayer = [CAShapeLayer layer]; trackLayer.frame = bgVi

[控件] 动态实时设置CAShapeLayer贝塞尔曲线的坐标点

动态实时设置CAShapeLayer贝塞尔曲线的坐标点 效果图: 源码: PathDirectionView.h 与 PathDirectionView.m // // PathDirectionView.h // Path // // Created by XianMingYou on 15/2/27. // Copyright (c) 2015年 XianMingYou. All rights reserved. // #import <UIKit/UIKit.h> #import &qu

CAGradientLayer

一 简介 1,CAGradientLayer,处理颜色渐变: 2,CAGradientLayer的渐变色可以做隐式动画: 3,大部分情况下,CAGradientLayer都是与CAShapeLayer配合使用的 4,CAGradientLayer可以用作png遮照效果. 二 坐标系统 三 色差动画实现 四 用CAGradientLayer封装色差动画的view

CAGradientLayer功能

一.CAGradientLayer介绍 1.CAGradientLayer是用于处理渐变色的层结构 2.CAGradientLayer的渐变色能够做隐式动画 3.大部分情况下.CAGradientLayer都是与CAShapeLayer配合使用的 4.CAGradientLayer能够用作png遮罩效果 二.CAGradientLayer坐标系统 1.CAGradientLayer的坐标系统是从坐标(0,0)到(1,1)绘制的矩形 2.CAGradientLayer的frame值的size不为正

CAGradientLayer渐变颜色动画

CAGradientLayer渐变颜色动画 或许你用过CAGradientLayer,你知道他是用于渐变颜色的,但你是否直到,CAGradientLayer的渐变颜色是可以动画的哦. 源码: // // RootViewController.m // CAGradientLayer // // Copyright (c) 2014年 Y.X. All rights reserved. // #import "RootViewController.h" #import "CAS

IOS进度渐变图层CAGradientLayer

看支付宝蚂蚁积分,天气预报等好多APP都有圆形渐变效果,今天就试着玩了. 一.CAGradientLayer类中属性介绍 CAGradientLayer继承CALayer,主要有以下几个属性: [email protected](nullable, copy) NSArray *colors; 渐变的颜色 这个数组中只设置一个颜色是不显示的 [email protected](nullable, copy) NSArray<NSNumber *> *locations;每种颜色的最亮的位置 [

CAGradientLayer颜色渐变器

使用CAGradientLayer可以实现颜色的渐变, 我们先看下头文件 @interface CAGradientLayer : CALayer @property(nullable, copy) NSArray *colors;//颜色渐变的数组 @property(nullable, copy) NSArray<NSNumber *> *locations;//渐变颜色的区间分布,locations的数组长度和color一致,默认是nil,会平均分布 @property CGPoint