两个动画效果来了解一下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 ,我们需要按下手指才会开始松开结束,首先 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;
}
}

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

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看项目的更多细节。

CAShapeLayer和CAGradientLayer的更多相关文章

  1. iOS 之使用CAShapeLayer中的CAGradientLayer实现圆环的颜色渐变

    本文转载自:http://blog.csdn.net/zhoutao198712/article/details/20864143 在 Github上看到一些进度条的功能,都是通过Core Graph ...

  2. CAGradientLayer

    参考: CAShapeLayer和CAGradientLayer 一 简介 1,CAGradientLayer,处理颜色渐变: 2,CAGradientLayer的渐变色可以做隐式动画: 3,大部分情 ...

  3. CALayer-CAShapeLayer/CAGradientLayer

    参考博客 CAShapeLayer http://blog.csdn.net/yongyinmg/article/details/38755955 CAShapeLayer和CAGradientLay ...

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

    在Github上看到一些进度条的功能,都是通过Core Graph来实现.无所谓正确与否,但是开发效率明显就差很多了,而且运行效率还是值得考究的.其实使用苹果提供的Core Animation能够非常 ...

  5. CoreAnimation笔记

    核心动画继承结构 CoreAnimation Core Animation是直接作用在CALayer上的(并非UIView上)非常强大的跨Mac OS X和iOS平台的动画处理API,Core Ani ...

  6. iOS 多个精致动画

    iOS 多个精致动画  http://www.cocoachina.com/bbs/read.php?tid=301262 iOS 零碎小知识     http://www.cocoachina.co ...

  7. 最新 iOS 框架整体梳理(三)

    这一篇得把介绍框架这个系列终结了,不能超过三篇了,不然太长了..... 还是老规矩,前面两篇的机票在下方: 最新 iOS 框架整体梳理(一) 最新 iOS 框架整体梳理(二) Part - 3     ...

  8. IOS CAShapeLayer CAGradientLayer UIBezierPath 使用实例

    CGRect rect = CGRectMake(100, 100, 100, 100); UIView * bgView = [[UIView alloc]initWithFrame:rect]; ...

  9. CAShapeLayer(UIBezierPath)、CAGradientLayer绘制动态小车

    看到一个大神写的代码,引用过来让大家看看! //  1.CAShapeLayer是一种特殊的层,可以在上面渲染图形. //  2.CAShapeLayer继承自CALayer,可使用CALayer的所 ...

随机推荐

  1. ASP.NET MVC+Bootstrap个人博客之打造清新分页Helper(三)

    有点另类,分页直接是在后台拼接好html,然后发送到前台的: 1. 分页容器: <div class="pagination"> <ul> //****** ...

  2. POJ 1519 Digital Roots

    题意:求数根. 解法:一个数的数根就是mod9的值,0换成9,只是没想到给的是一个大数……只好先把每位都加起来再mod9…… 代码: #include<stdio.h> #include& ...

  3. C# 中LinkLabel的简单使用

    界面中加入一个LinkLabel控件

  4. Delphi 让自己的软件实现双击打开文件 转

    unit shjAssociateFileType; interface uses Windows, Registry; {将文件类型strFileExtension与程序strExeFileName ...

  5. HDU 5765 Bonds 巧妙状压暴力

    题意:给一个20个点无向连通图,求每条边被多少个极小割集包括 分析:极小割集是边的集合,很显然可以知道,极小割集恰好吧原图分成两部分(这个如果不明白可以用反证法) 然后就是奉上官方题解:http:// ...

  6. XTUOJ1247 Pair-Pair 预处理+暴力

    分析:开个1000*1000的数组,预处理矩阵和,然后分类讨论就好 时间复杂度:O(n) #include <cstdio> #include <iostream> #incl ...

  7. Multiclass Classification

    之前我们都是在Binary classification的基础上学习算法和知识. 如何使用Binary classification算法进行Multiclass classification呢? (一 ...

  8. 线性模型(2):Linear Regression

    此笔记源于台湾大学林轩田老师<机器学习基石><机器学习技法> 我们已经学习过PLA算法,所谓的线性模型就是:计算核心为.PLA是一种分类方法,这里介绍线性回归方法(与概率与统计 ...

  9. 多校6 1001 HDU5793 A Boring Question (推公式 等比数列求和)

    题解:http://bestcoder.hdu.edu.cn/blog/ 多校6 HDU5793 A Boring Question // #pragma comment(linker, " ...

  10. CORBA

    公共对象请求代理体系结构(Common Object Request Broker Architecture)