
两个动画效果来了解一下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 五星评分控件
程序中需要打分的功能,在网上找了几个,都不是很满意.下面是实现出的效果.可以点击,可以拖动. 使用方法:初始化控件. TQStarRatingView *starRatingView = [[TQSt ...
- MultiSet
Guava引进了JDK里没有的,但是非常有用的一些新的集合类型.所有这些新集合类型都能和JDK里的集合平滑集成.Guava集合非常精准地实现了JDK定义的接口.Guava中定义的新集合有: Multi ...
- C# 邮件发送系统
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...
- XTUOJ 1252 Defense Tower 贪心
题目链接:http://202.197.224.59/OnlineJudge2/index.php/Problem/read/id/1252 思路:考虑每条边对玩家的伤害 假设连接的节点是u,v,破坏 ...
- IOS UIView(UIButton)通过显示动画移动的时候 响应点击的解决方案
今天在做一个UIButton显示动画的时候,遇到一个问题,就是在移动的时候 ,需要相应它的点击时间(click) 通过CAKeyframeAnimation 来移动UIButton的layer ,效果 ...
- C语言char[]和char*比较
先看看一个例子: #include <iostream> using namespace std; main() { char *c1 = "abc"; char c2 ...
- 【转】Maven实战(二)---多模块开发---缺少Jar包
原博文出于:http://blog.csdn.net/liutengteng130/article/details/41611755 感谢! Maven里面的Jar包经常出现Missing的情况 ...
- html学习笔记之position
今天主要一直看试验position的各种属性,现在记录下来以此备忘. position有四种常有属性,分别是static,fixed.absolute,relative fixed就是相对于窗口的位置 ...
- 最大连续子数组问题-homework-01
1)先写我的 github 的介绍: github 的域名:http://www.github.com/zhuifeng1022 登入 github 大概是下面的视图: 按照助教的方法:我已经建好了代 ...
- 使用MySQL正则表达式查询
MySQL用WHERE子句对正则表达式提供了初步的支持,允许你指定用正则表达式过滤SELECT检索出的数据. REGEXP后所跟的东西作为正则表达式处理. 代码 SELECT prod_name FR ...