两个动画效果来了解一下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. [Everyday Mathematics]20150128

    求极限 $$\bex \lim_{x\to 0}\sex{\frac{e^x+e^{2x}+\cdots+e^{nx}}{n}}^\frac{1}{x}. \eex$$

  2. 浅淡C/C++中的typedef和#define

    在C/C++中,我们平时写程序可能经常会用到typedef关键字和#define宏 定义命令,在某些情况下使用它们会达到相同的效果,但是它们是有实质性的区别,一个是C/C++的关键字,一个是C/C++ ...

  3. IOS 疑问记录

    1. NSThread  中的  NSRunLoop 2. NSThread  中的 performSelector:onThread:withObject:waitUntilDone:

  4. Android自带CalendarView类实现日历视图

    文章由来:学习一下日历控件的实现,Android3.0以下的版本请查阅其他文章. 本文主要是介绍Android日历视图控件CalendarView相关的内容,然后在后面会给出一个简单的Demo. Ca ...

  5. 常见设计模式解析和实现(C++)Prototype模式(原型模式)

    作用:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. UML结构图: 抽象基类: 1)  Prototype:虚拟基类,所有原型的基类,提供Clone接口函数 接口函数: 1)  P ...

  6. WCF扩展

    WCF 可扩展性 WCF 提供了许多扩展点供开发人员自定义运行时行为. WCF 在 Channel Layer 之上还提供了一个高级运行时,主要是针对应用程序开发人员.在 WCF 文档中,它常被称为服 ...

  7. LeetCode 232 Implement Queue using Stacks

    Implement the following operations of a queue using stacks. push(x) -- Push element x to the back of ...

  8. homework-05 GoldNumberServer

    作业要求 这次作业要求实现一个黄金数游戏服务器,游戏流程如下,每个client向服务器提交一个有理数,服务器接收到所有客户端的提交后计算这些数字的平均数,再将其乘以黄金分割得到一个GoldNumber ...

  9. Tomcat中配置JNDI数据源

    准备工作: Tomcat版本:tomcat6.0以上 下例中均使用MySQL数据库 将对应数据源的jar包和MySQL的驱动包拷贝至tomcat的lib文件夹下 一.全局数据源 1步骤一:配置 在to ...

  10. 【Java】C/C++与Java的简单比较

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/5827273.html     C/C++:            编译(不同的系统编译出不同的机器码,所以同一 ...