所有的动画、绘图、这里全搞定
看着我这标题就觉得有点狂啊,其实是本人最近工作有点闲,看了很多人写的sample,从中萃取精华,总结下,以防止自己忘记了,岂不白白浪费了这几天的努力。废话就这么多,现在开始正题:
先说说咱这篇文章会讲到什么吧,首先我会讲讲绘图,然后讲讲动画
咱们首先从绘图说起,
- 使用CoreGraphics进行绘图
说到绘图,那绘图是在哪里绘呢,当然是在View里面了,我们可以重写UIView的- (void)drawRect:(CGRect)rect方法,然后在里面进行绘图,绘图的话就离不来CoreGraphics了,然而CoreGraphics的核心就是QuzCore里面的几个函数了。废话少说,直接上代码
- (void)drawRect:(CGRect)rect {
// 获取绘图上下文环境
CGContextRef context = UIGraphicsGetCurrentContext();
// 设置线条的宽度
CGContextSetLineWidth(context, 4.0);
// 设置线条的颜色
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
// 将绘画笔移到一个点作为绘图的起点
CGContextMoveToPoint(context, 20.0, 20.0);
// 由起点画一条线到终点
CGContextAddLineToPoint(context, 300, 400);
// 画出来
CGContextStrokePath(context);
}
这段代码之后呈现出的效果如下:
如你所见,这段代码的功能是画了一条红色的斜线。当然如果你想画一个矩形的话也是同样的一个道理,在上面的代码后面加上如下代码就好:
// 画一个矩形
CGRect arect = CGRectMake(200, 30, 49, 59);
// 设置填充色
CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
// 填充矩形
CGContextFillRect(context, arect);
// 将举行显示出来
CGContextAddRect(context, arect);
效果的话自己去试吧,我就不上图了,另外谁能告诉我怎么把图片缩小,这太占地了。绘图就说这么多了,感觉用处不大啊,以后感觉用处大再补充吧。
-
使用CAShapeLayer 、UIBezierPath、CABasicAnimation画
经常看到别人发出一下比较屌的动画例子,其中用到的两个必不可少的技术点有三个,分别是:
CAShapeLayer:这是动画的主要载体,动画都是由他执行
UIBezierPath:用来描绘CAShapeLayer的边界
CABasicAnimation:动画对象,由他对动画的执行过程进行描述
使用实例如下:
#import "NibView.h"
@interface NibView()// 执行动画的载体 @property (nonatomic, strong) CAShapeLayer *shapLayer; @end @implementation NibView /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */ // 画面的初始化在此方法内进行 - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; self.frame = [UIApplication sharedApplication].keyWindow.bounds; self.backgroundColor = [UIColor redColor]; UIBezierPath *bpath = [UIBezierPath bezierPath]; [bpath moveToPoint:CGPointMake(100, 150)]; [bpath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(200, 80) controlPoint2:CGPointMake(200, 200)]; [bpath addCurveToPoint:CGPointMake(300, 400) controlPoint1:CGPointMake(400, 230) controlPoint2:CGPointMake(250, 350)]; [bpath addLineToPoint:CGPointMake(100, 400)]; [bpath closePath]; self.shapLayer.path = bpath.CGPath; [self.layer addSublayer:_shapLayer]; return self; } // 获得一个path - (UIBezierPath *)rectPath { UIBezierPath *rectpath = [UIBezierPath bezierPath]; [rectpath moveToPoint:CGPointMake(100.0, 150.0)]; [rectpath addLineToPoint:CGPointMake(300, 150)]; [rectpath addLineToPoint:CGPointMake(300, 400)]; [rectpath addLineToPoint:CGPointMake(100, 400)]; [rectpath closePath]; return rectpath; } // 执行动画 - (void)animate { CABasicAnimation *expandAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; expandAnimation.fromValue = (__bridge id)(_shapLayer.path); expandAnimation.toValue = (__bridge id)[self rectPath].CGPath; expandAnimation.beginTime = 0; expandAnimation.duration = 0.5; expandAnimation.fillMode = kCAFillModeForwards; expandAnimation.removedOnCompletion = NO; [self.shapLayer addAnimation:expandAnimation forKey:nil]; } // 加个按钮让动画可以重复进行 - (IBAction)goAnimate:(UIButton *)sender { [self animate]; } #pragma mark - initViews - (CAShapeLayer *)shapLayer { if (!_shapLayer) { _shapLayer = [[CAShapeLayer alloc] init]; _shapLayer.frame = self.bounds; _shapLayer.fillColor = [UIColor greenColor].CGColor; } return _shapLayer; } @end
下面对上面的代码进行说明:
- 我这里是将代码动画放在一个xib文件里面的,需要注意的是当xib文件被加载时它调用的是- (id)initWithCoder:(NSCoder *)aDecoder这个方法,而不是- (id)initWithFrame:(CGRect)frame这个方法,使用初始化的事情都应该放在这里面来进行。
- 在animate这个方法里面有这么两行
expandAnimation.fillMode = kCAFillModeForwards;
expandAnimation.removedOnCompletion = NO;
需要注意的是这两行要同时写上动画才不会回去。
动画执行的效果如下:
另外你可以利用CAAnimationGroup来组织来管理动画,使得几个动画连续执行,这样可以形成一系列的连贯动画效果,例如:
#import "NibView.h"
@interface NibView()
// 执行动画的对戏那个
@property (nonatomic, strong) CAShapeLayer *shapLayer;
@end
@implementation NibView
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
// 画面的初始化在此方法内进行
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
self.frame = [UIApplication sharedApplication].keyWindow.bounds;
self.backgroundColor = [UIColor redColor];
self.shapLayer.path = [self curvePath].CGPath;
[self.layer addSublayer:_shapLayer];
return self;
}
// 获得一个饱含曲线的path
- (UIBezierPath *)curvePath {
UIBezierPath *bpath = [UIBezierPath bezierPath];
[bpath moveToPoint:CGPointMake(70, 150)];
[bpath addCurveToPoint:CGPointMake(270, 150)
controlPoint1:CGPointMake(170, 80)
controlPoint2:CGPointMake(170, 200)];
[bpath addCurveToPoint:CGPointMake(270, 400)
controlPoint1:CGPointMake(370, 230)
controlPoint2:CGPointMake(220, 350)];
[bpath addLineToPoint:CGPointMake(70, 400)];
[bpath closePath];
return bpath;
}
// 获得一个path
- (UIBezierPath *)rectPath {
UIBezierPath *rectpath = [UIBezierPath bezierPath];
[rectpath moveToPoint:CGPointMake(100.0, 150.0)];
[rectpath addLineToPoint:CGPointMake(300, 150)];
[rectpath addLineToPoint:CGPointMake(300, 400)];
[rectpath addLineToPoint:CGPointMake(100, 400)];
[rectpath closePath];
return rectpath;
}
// 获得一个正方形边迹
- (UIBezierPath *)suqarePath {
UIBezierPath *squarPath = [UIBezierPath bezierPath];
[squarPath moveToPoint:CGPointMake(10, 150)];
[squarPath addLineToPoint:CGPointMake(310, 150)];
[squarPath addLineToPoint:CGPointMake(310, 450)];
[squarPath addLineToPoint:CGPointMake(10, 450)];
[squarPath closePath];
return squarPath;
}
// 执行动画
- (void)animate {
// 原始形态变成长方形
CABasicAnimation *expandAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
expandAnimation.fromValue = (__bridge id)(_shapLayer.path);
expandAnimation.toValue = (__bridge id)[self rectPath].CGPath;
expandAnimation.beginTime = 0;
expandAnimation.duration = 0.5;
[self.shapLayer addAnimation:expandAnimation forKey:nil];
// 长方形变成正方形
CABasicAnimation *expandAnimation2 = [CABasicAnimation animationWithKeyPath:@"path"];
expandAnimation2.fromValue = (__bridge id)[self rectPath].CGPath;
expandAnimation2.toValue = (__bridge id)[self suqarePath].CGPath;
expandAnimation2.beginTime = expandAnimation.beginTime + expandAnimation.duration;
expandAnimation2.duration = 0.5;
// 正方形又回到原始形态
CABasicAnimation *expandAnimation3 = [CABasicAnimation animationWithKeyPath:@"path"];
expandAnimation3.fromValue = (__bridge id)[self suqarePath].CGPath;
expandAnimation3.toValue = (__bridge id)(_shapLayer.path);
expandAnimation3.beginTime = expandAnimation2.beginTime + expandAnimation2.duration;
expandAnimation3.duration = 0.5;
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = @[expandAnimation, expandAnimation2, expandAnimation3];
group.beginTime = expandAnimation.beginTime;
group.duration = expandAnimation3.beginTime + expandAnimation3.duration;
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
group.repeatCount = 2;
[self.shapLayer addAnimation:group forKey:nil];
}
// 加个按钮让动画可以重复进行
- (IBAction)goAnimate:(UIButton *)sender {
[self animate];
}
#pragma mark - initViews
- (CAShapeLayer *)shapLayer {
if (!_shapLayer) {
_shapLayer = [[CAShapeLayer alloc] init];
_shapLayer.frame = self.bounds;
_shapLayer.fillColor = [UIColor greenColor].CGColor;
}
return _shapLayer;
}
@end
由于修改较大,使用干脆又重现全部贴了出来了,下面做以下说明:
- 首先为了代码的整齐我把initWithCoder里面的画边界部分的代码抽了出来,单独形成了一个方法
- 我增加了一个返回正方形的轨迹方法suqarePath
- 我对animate方法进行了修改,引入了CAAnimationGroup,这里仍需要注意的是fillMode和removedOnCompletion这两个熟悉,现在把他应用到动画组上。
通过动画组,我们可以形成各种细腻的动画效果,一切貌似变的明朗起来.
-
绕着Z轴旋转的方法
有一种动画叫做旋转,对于旋转来说如果像上面一样一个个慢慢组的话会写死你有木有。那旋转动画怎么玩呢,很简单,看到我们上面声明动画是这么玩的:
[CABasicAnimation animationWithKeyPath:@"path"],
然而,现在我们将这么玩:
[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]
他们两的区别就是KeyPath变了,看到这里,我们明白了,其实动画的种类就是由这个东西决定的。是@"path"说明这个动画是通过改变边界来形成动画,那很自然@"transform.rotation.z"就是绕着Z轴旋转形成的动画了。废话不多说,再次上代码:
首先在类中加入如下方法
// 旋转动画
- (void)rotation {
CABasicAnimation *rotaionAnimate = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotaionAnimate.toValue = @(M_PI * 2.0);
rotaionAnimate.duration = 0.5;
rotaionAnimate.removedOnCompletion = YES;
[self.shapLayer addAnimation:rotaionAnimate forKey:nil];}
然后在修改goAnimate方法
- (CAShapeLayer *)shapLayer {
if (!_shapLayer) {
_shapLayer = [[CAShapeLayer alloc] init];
_shapLayer.frame = self.bounds;
_shapLayer.fillColor = [UIColor greenColor].CGColor;
}
return _shapLayer;
}
这里我们让图层旋转360度,运行效果如下:
另外需要说明的是可以制定图像绕着那一点旋转,我们只要指定它的描点,在rotation方法里面加入下面这句代码
self.shapLayer.anchorPoint = CGPointMake(0.1, 0.1);
效果如下:
由于我们改变了描点,所以图像的fram变了。
- 描边动画
有关描边动画也是改变一下KeyPath,它的KeyPath是:@"strokeEnd"
在代码中加入如下方法
- (void)strokeBoard {
self.shapLayer.strokeColor = [UIColor whiteColor].CGColor;
self.shapLayer.lineWidth = 20.0;
CABasicAnimation *strokeAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeAnimation.fromValue = @0.0;
strokeAnimation.toValue = @1.0;
strokeAnimation.duration = 1.0;
[self.shapLayer addAnimation:strokeAnimation forKey:nil];
}
然后在goAnimate方法里面加入如下代码
[NSTimer scheduledTimerWithTimeInterval:4.5
target:self
selector:@selector(strokeBoard)
userInfo:nil
repeats:NO];
这里strokeBoard方法里面我们设置了shapLayer的lineWidth和strokeColor,lineWidth默认是0,strokeColor默认是透明的。
当lineWidth非0时,显示方法是内外各一半。为了便于观察,这里运行前把上面的描点的那句代码注释掉,运行效果如下:
- CAKeyframeAnimation:关键帧动画。(和CABasicAnimation平行)就是一帧一帧的动画了,一般用来处理GIF图片的
首先可以在View里面加入一张图片,然后让图片抖动起来,抖动但动画代码如下:
// 抖动动画
- (void)animate {
// 定义帧动画
CAKeyframeAnimation animate = [CAKeyframeAnimation animation];
// 改变弧度
animate.values = @[@(M_PI/1805),@(-M_PI/1805),@(M_PI/1805)];
// 关键帧是什么必须有,其实就是和什么有关的动画了
animate.keyPath = @"transform.rotation";
// 重复次数
animate.repeatCount = MAXFLOAT;
animate.duration = 0.2;
[_imageView.layer addAnimation:animate forKey:nil];
}
最后实现的效果如下:
-
动画但暂停
如果动画时加在layer上的话,动画是可以被暂停的,只需要将执行动画的那个layer的speed属性设置为0就好,要回复的话设置成1.0就好
如暂停和恢复上面的动画:
- (void)stopAnimate {// 获取暂停时间 // CFTimeInterval pausedTime = [self.imageView.layer convertTime:CACurrentMediaTime() fromLayer:nil]; // // self.imageView.layer.timeOffset = pausedTime; if (self.imageView.layer.speed == 0) { self.imageView.layer.speed = 1.0; } else { self.imageView.layer.speed = 0; } }