
两个动画效果来了解一下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运行成功的话应该是这样的。

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

有个这个方法后,我们可以控制 CAShapeLayer
的 strokeEnd
的属性大小来实现动画效果,首页我们先设置它的值为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
属性,但是我们使用 CAShapeLayer
的 strokeStart
和 strokeEnd
结合起来实现动画,其二停止动画后我们可以使用截图当前的状态同时移除动画,这样就可以保留每个状态的颜色。为了实现这些,我们使用 CABasicAnimation
和 CAlayer
的属性 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
设置 strokeStart
和 strokeEnd
,然后移除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;
}
}
|
最后你可以在 github 上面下载这个项目。
CAGradientLayer

首页我们创建一个 UIView
的子类,然后我们使用 CAGradientLayer
作为默认的 CALayer
。
|
+ (Class)layerClass {
return [CAGradientLayer class];
}
|
CAGradientLayer
是 CALayer
的一个子类,添加了一些额外的属性,我们将是使用 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看项目的更多细节。
- iOS 之使用CAShapeLayer中的CAGradientLayer实现圆环的颜色渐变
本文转载自:http://blog.csdn.net/zhoutao198712/article/details/20864143 在 Github上看到一些进度条的功能,都是通过Core Graph ...
- CAGradientLayer
参考: CAShapeLayer和CAGradientLayer 一 简介 1,CAGradientLayer,处理颜色渐变: 2,CAGradientLayer的渐变色可以做隐式动画: 3,大部分情 ...
- CALayer-CAShapeLayer/CAGradientLayer
参考博客 CAShapeLayer http://blog.csdn.net/yongyinmg/article/details/38755955 CAShapeLayer和CAGradientLay ...
- 【iOS实现一个颜色渐变的弧形进度条】
在Github上看到一些进度条的功能,都是通过Core Graph来实现.无所谓正确与否,但是开发效率明显就差很多了,而且运行效率还是值得考究的.其实使用苹果提供的Core Animation能够非常 ...
- CoreAnimation笔记
核心动画继承结构 CoreAnimation Core Animation是直接作用在CALayer上的(并非UIView上)非常强大的跨Mac OS X和iOS平台的动画处理API,Core Ani ...
- iOS 多个精致动画
iOS 多个精致动画 http://www.cocoachina.com/bbs/read.php?tid=301262 iOS 零碎小知识 http://www.cocoachina.co ...
- 最新 iOS 框架整体梳理(三)
这一篇得把介绍框架这个系列终结了,不能超过三篇了,不然太长了..... 还是老规矩,前面两篇的机票在下方: 最新 iOS 框架整体梳理(一) 最新 iOS 框架整体梳理(二) Part - 3 ...
- IOS CAShapeLayer CAGradientLayer UIBezierPath 使用实例
CGRect rect = CGRectMake(100, 100, 100, 100); UIView * bgView = [[UIView alloc]initWithFrame:rect]; ...
- CAShapeLayer(UIBezierPath)、CAGradientLayer绘制动态小车
看到一个大神写的代码,引用过来让大家看看! // 1.CAShapeLayer是一种特殊的层,可以在上面渲染图形. // 2.CAShapeLayer继承自CALayer,可使用CALayer的所 ...
随机推荐
- IOS 时间 日历 处理集合
1.获得当前时间 从1970开始的秒数 NSTimeInterval time = [[NSDate date[ timeIntervalSince1970]]; NSString * str = [ ...
- CGAffineTransformScale
[UIViewbeginAnimations:nilcontext:nil]; [UIViewsetAnimationDuration:0.5]; _imgView.transform = CGAff ...
- DataGrid的打印预览和打印
using System;using System.Drawing;using System.Collections;using System.ComponentModel;using System. ...
- 转载--详解tomcat配置
http://www.importnew.com/17124.html 原文链接 几乎所有容器类型的应用都会包含一个名为 server.xml 的文件结构.基本上,其中的每个元数据或者配置都是容器完 ...
- 判断线段和直线相交 POJ 3304
// 判断线段和直线相交 POJ 3304 // 思路: // 如果存在一条直线和所有线段相交,那么平移该直线一定可以经过线段上任意两个点,并且和所有线段相交. #include <cstdio ...
- (转载)OC学习篇之---第一个程序HelloWorld
之前的一片文章简单的介绍了OC的相关概述,从这篇开始我们就开始学习OC的相关知识了,在学习之前,个人感觉需要了解的其他的两门语言:一个是C/C++,一个是面向对象的语言(当然C++就是面向对象,不过这 ...
- [Hive - LanguageManual] Statistics in Hive
Statistics in Hive Statistics in Hive Motivation Scope Table and Partition Statistics Column Statist ...
- 关于python的import
在软件包里,必须添加__init__.py文件. 想要对外公开的module必须在__init__.py内import一次,这样这些module才能被外部代码import并调用.
- Python 发送邮件包含附件报表示例
之前需要用Python发送报表邮件,在网上找了下资料,基本上符合要求了. 相关的示例如下,懂python的人应该都知道. from email.mime.text import MIMEText fr ...
- Red5源代码分析 - 关键类及其初始化过程
原文地址:http://semi-sleep.javaeye.com/blog/348768 Red5如何响应rmpt的请求,中间涉及哪些关键类? 响应请求的流程如下: 1.Red5在启动时会调用RT ...