TZ : 飞机虽然飞的很快,但是我们不要忘了思考
一 : 科普一分钟
我们看到很多产品都有炫酷的动画,我们会有跃跃欲试的感觉,复杂的结构拆解开各个小部分,都是有基本的动画效果组合而成,下面主要介绍一下CoreAnimation这套苹果为我们提供的功能强大的api,为我们提供了各种动画效果.配合着我们的想象力来实现各种效果.
二 : CALayer和其重要属性
CALayer
我们平时的基础控件包含UIview, 点击按钮,label,等等.这些控件之所以能显示在我们的眼前,因为这些控件上都有一个图层 CALayer
我们在创建这些基础控件时,其内部会自动创建一个图层 CALayer
对象.
- 给UIView
CALayer
属性约束
@property (strong, nonatomic) IBOutlet UIView *tzView;
//设置阴影的颜色
self.tzView.layer.shadowColor = [UIColor orangeColor].CGColor;
//设置阴影的不透明度
self.tzView.layer.shadowOpacity = 1;
self.tzView.layer.shadowOffset = CGSizeMake(-5, -5);
//设置阴影模糊半径
self.tzView.layer.shadowRadius = 5;
//边框宽度,往里边延伸
self.tzView.layer.borderWidth = 2;
self.tzView.layer.borderColor = [UIColor greenColor].CGColor;
//设置圆角
self.tzView.layer.cornerRadius = 50;
- 给UIImageView
CALayer
属性约束
@property (strong, nonatomic) IBOutlet UIImageView *tzImageView;
//设置阴影的颜色
self.tzImageView.layer.shadowColor = [UIColor orangeColor].CGColor;
//设置阴影的不透明度
self.tzImageView.layer.shadowOpacity = 1;
self.tzImageView.layer.shadowOffset = CGSizeMake(-5, -5);
//设置阴影模糊半径
self.tzImageView.layer.shadowRadius = 5;
//边框宽度,往里边延伸
self.tzImageView.layer.borderWidth = 2;
self.tzImageView.layer.borderColor = [UIColor greenColor].CGColor;
//设置圆角
self.tzImageView.layer.cornerRadius = 50;
//把超过根层以外的东西都剪裁掉.
self.tzImageView.layer.masksToBounds = YES;
对于 imageView
我们设置圆角的时候 要 加一行代码 self.tzImageView.layer.masksToBounds = YES;
这个是因为 imageView
的 layer 层 上面还有一层 contens
来存放 图片 如果不加上述代码,只是对其根层设置圆角,并没有对 contens
设置
- 我们自己写一个
CALayer
CALayer *layer = [CALayer layer];
layer.backgroundColor = [UIColor redColor].CGColor;
layer.frame = CGRectMake(50, 50, 100, 100);
[self.view.layer addSublayer:layer];
layer.contents = (id)[UIImage imageNamed:@"tz.jpg"].CGImage;
注意 : CAlayer
是定义在 QuartzCore
框架的
CGImagerRef
CGColorRef
是定义在CoreGraphics
UIImage
和 UIColor
是定义在UIKit框架中的
QuartzCore
和 CoreGraphics
框架是可以跨平台的在MacOSX 和 iOS 上都可以使用
为了保证移植性
QuartzCore
不能使用 UIImage
和
UIColor
只能使用 CGImagerRef
和 CGColorRef
所以我们通常会转一下
-
CATransform3D
我们想改变图层的形变平移等等动画是怎么做的呢,可以用这个
@property (strong, nonatomic) IBOutlet UIImageView *imageView;
参数 1 : 运动角度
参数 2 : 在 X 轴 上是否有运动,
参数 3 : 在 Y轴上是否有运动
参数 4 : 在 Z轴上是否有运动
self.imageView.layer.transform = CATransform3DMakeRotation(M_PI, 1, 1, 0);
快速写法
[self.imageView.layer setValue:@(100) forKeyPath:@"transform.translation.x"];
-
position
和anchorPoint
属性
CALayer
特别重要的两个属性
position
: 用来设置 CAlayer在父层中的位置 以父层左上角为原点(0,0)
anchorPoint
: 又称为 锚点
决定了CALayer
身上 的哪个点会在 position
所指定的位置, 以自己的左上角为原点(0,0) X, 和 Y的 取值范围 0~1
默认值为 (0.5,0.5);
我们把 锚点
也就是 anchorPoint
点 想象成 CALayer
上的一个钉子,把父层想象成一面墙 上面有个洞 就是 position
现在那个洞在哪个地方 我们就用钉子 扎到哪个地方,这个是不是好理解了一点. 接下来用代码 来描述
@property(nonatomic,weak)CALayer *layer;
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(200, 200, 100, 100);
layer.backgroundColor = [UIColor greenColor].CGColor;
self.layer = layer;
[self.view.layer addSublayer:layer];
接下来我们让它 进行改变 看看效果
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.layer.anchorPoint = CGPointMake(0, 0);
self.layer.position = CGPointMake(0,0);
}
效果.gif
假如我们没有设置 anchorPoint
会有什么效果呢 别忘了默认是 (0.5,0.5)哦
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.layer.position = CGPointMake(0,0);
}
效果2.gif
- 隐式动画
注意看上面的gif 可以感觉出来有动画的效果,就是移动过去的,为什么呢,这是因为 手动创建的图层都会自带一个动画效果
也就是非根层
那么什么是根层
呢 每一个View 内部都默认关联一个 CALayer
我们称这个layer 为RootLayer
->根层
非根层 - > 手动创建的 CALayer
对象都存在隐式动画
可以通过 动画事务对隐式动画进行操作
//关闭 /打开 隐式动画
[CATransaction setDisableActions:NO];
控制隐式动画
[CATransaction begin];
[CATransaction setDisableActions:NO];
//动画时长
[CATransaction setAnimationDuration:2];
self.layer.position = CGPointMake(100, 400);
self.layer.bounds = CGRectMake(0, 0, 90, 90);
self.layer.backgroundColor = [UIColor redColor].CGColor;
//结束
[CATransaction commit];
三 : CoreAnimation 和其子类
子类解析.png我们分析一下这个子类结构图
CAMediaTiming
是它们共同遵守的一个协议
CATransition
是我们熟悉的转场动画
CABasicAnimation
是一个值到另一个值
CAKeyframeAnimation
一个值到多个值
- 了解
要想使用 CoreAnimation
我们先要有CALayer
因为CoreAnimation
只作用在 CALayer
上有效果
通过创建 CAAnimation
对象 并且设置动画属性
最后在layer 层上添加 动画 就OK 了
2 位移动画
平移.gif //1.创建动画对象(设置layer 的属性值)
CABasicAnimation *anmi = [CABasicAnimation animation];
//2.设置属性
anmi.keyPath = @"position.x";
anmi.toValue = @300;
//动画完成时,会自动删除动画
anmi.removedOnCompletion = NO;
anmi.fillMode = kCAFillModeForwards;
// anmi.fillMode = @"forwards";
//3.添加动画
[self.TZview.layer addAnimation:anmi forKey:nil];
- 放大缩小动画
//-------心跳部分
//创建动画
CABasicAnimation *heartanmi = [CABasicAnimation animation];
//设置属性值
heartanmi.keyPath = @"transform.scale";
heartanmi.toValue = @0;
//设置动画执行次数
heartanmi.repeatCount = MAXFLOAT;
//动画执行时长
heartanmi.duration = 0.7;
//自动反转效果(怎样去,怎样回)
heartanmi.autoreverses = YES;
//添加动画
[self.heartImage.layer addAnimation:heartanmi forKey:nil];
- 指定路径位移
CAKeyframeAnimation *Anmi = [CAKeyframeAnimation animation];
Anmi.duration = 2;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 50)];
[path addLineToPoint:CGPointMake(300, 50)];
[path addLineToPoint:CGPointMake(300, 400)];
Anmi.keyPath = @"position";
Anmi.path = path.CGPath;
[self.boheImgeView.layer addAnimation:Anmi forKey:nil];
- 删除图标动画
CAKeyframeAnimation *boheAnmi = [CAKeyframeAnimation animation];
//设置属性
boheAnmi.keyPath = @"transform.rotation";
// boheAnmi.values = @[@(angleToRad(-5)),@(angleToRad(5))];
boheAnmi.values = @[@(angleToRad(-5)),@(angleToRad(5)),@(angleToRad(-5))];
//动画执行次数
boheAnmi.repeatCount = MAXFLOAT;
//反转
[self.boheImgeView.layer addAnimation:boheAnmi forKey:nil];
- 转场动画
-(void)animationMove{
//添加转场动画
CATransition *anmi = [CATransition animation];
anmi.duration = 1;
//设置转场类型 立体
// anmi.type = @"cube";
//水滴
// anmi.type = @"rippleEffect";
//翻页效果
// anmi.type = @"pageCurl";
//平推效果
// anmi.type = @"push";
//收缩效果
anmi.type = @"suckEffect";
//动画起始位置
anmi.startProgress = 0.3;
//动画结束位置
anmi.endProgress = 0.5;
[self.TZimageView.layer addAnimation:anmi forKey:nil];
}
点击
-(void)animationSelecImage{
_i++;
if (_i == 4) {
_i = 1;
}
NSString *imageName = [NSString stringWithFormat:@"%d",_i];
self.TZimageView.image = [UIImage imageNamed:imageName];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self animationSelecImage];
[self animationMove];
}
- 复制层
@property (strong, nonatomic) IBOutlet UIView *showView;
//复制层
CAReplicatorLayer *rel = [CAReplicatorLayer layer];
rel.frame = self.showView.bounds;
[self.showView.layer addSublayer:rel];
//赋值份数
rel.instanceCount = 5;
//创建一个震动条
CALayer *layer = [CALayer layer];
layer.contents = (id)[UIImage imageNamed:@"dishu.jpg"].CGImage;
layer.backgroundColor = [UIColor redColor].CGColor;
// layer.frame = CGRectMake(0, self.showView.bounds.size.height - 100, 30, 100);
layer.bounds = CGRectMake(0, 0, 30, 100);
layer.anchorPoint = CGPointMake(0, 1);
layer.position = CGPointMake(0, self.showView.bounds.size.height);
[rel addSublayer:layer];
//添加动画
CABasicAnimation *anmi = [CABasicAnimation animation];
anmi.keyPath = @"transform.scale.y";
anmi.toValue = @0;
anmi.repeatCount = MAXFLOAT;
anmi.autoreverses = YES;
anmi.duration = 0.3;
[layer addAnimation:anmi forKey:nil];
rel.instanceTransform = CATransform3DMakeTranslation(45, 0, 0);
//子层动画延迟执行
rel.instanceDelay = 1;
解析 : 有的人会对 layer.anchorPoint = CGPointMake(0, 1);
不理解 这样解释,所有的动画效果都是 围绕着锚点为中心进行动画的,默认的锚点为(0.5 , 0.5) 我们为了实现效果 要移动锚点的位置.
rel.instanceTransform = CATransform3DMakeTranslation(45, 0, 0);
这个设置的含义是 ,当我们没有设置的时候 所有的复制层全都叠加在一起,当设置后 ,对复制出来的子层做形变操作,每一个是相对于上一个子层做的形变
- 倒影效果
我们先自己定义一下关联 一下 self.view
目的是直接给我们返回一个复制层的layer
@interface TZview : UIView
@end
@implementation TZview
//返回当前UIview内容layer 类型
+(Class)layerClass{
return [CAReplicatorLayer class];
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
CAReplicatorLayer *repl = (CAReplicatorLayer *)self.view.layer;
repl.instanceCount = 2;
//绕着复制曾的锚点进行旋转
repl.instanceTransform = CATransform3DMakeRotation(M_PI, 1, 0, 0);
//阴影 把所有颜色减淡
repl.instanceRedOffset -= 0.2;
repl.instanceGreenOffset -= 0.2;
repl.instanceBlueOffset -= 0.2;
repl.instanceAlphaOffset -= 0.2;
}
解析 : 这个的注意点 就是 instanceTransform
这个属性设置时候 是 绕着复制曾的锚点进行旋转
的
- 手写动画效果
想把图片换成血滴 怕有些同学不适应,仿造聂风成魔时候的feel.
首先我们要自定义一个view
替换系统 self.view
先完成手写板部分功能.
#import <UIKit/UIKit.h>
@interface TZdrawView : UIView
-(void)start;
-(void)redraw;
@end
#import "TZdrawView.h"
@interface TZdrawView()
@property(nonatomic,strong)UIBezierPath *path;
@property(nonatomic,weak)CALayer *dotlayer;
@end
@implementation TZdrawView
(1) 添加手势 完成 手写板功能
-(void)awakeFromNib{
[super awakeFromNib];
//添加手势 创建路径
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:pan];
UIBezierPath *path = [UIBezierPath bezierPath];
self.path = path;
}
-(void)pan:(UIPanGestureRecognizer*)pan{
//获取当前点
CGPoint curp = [pan locationInView:self];
if (pan.state == UIGestureRecognizerStateBegan) {
//设置启点
[self.path moveToPoint:curp];
}else if (pan.state == UIGestureRecognizerStateChanged){
//添加一根线到当前点
[self.path addLineToPoint:curp];
//重绘图
[self setNeedsDisplay];
}
}
-(void)drawRect:(CGRect)rect{
//绘制路径
[self.path stroke];
}
解析 : 当我们调用 [self setNeedsDisplay];
就会触发 drawRect
方法
(2) 绘制 聂风 成魔时候的花瓣效果
首先把花瓣layer 添加上
-(void)awakeFromNib{
[super awakeFromNib];
//添加手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:pan];
//添加粒子
CALayer *dotlayer = [CALayer layer];
dotlayer.frame = CGRectMake(0, -20, 20, 20);
// dotlayer.backgroundColor = [UIColor redColor].CGColor;
dotlayer.contents = (id)[UIImage imageNamed:@"flower"].CGImage;
self.dotlayer = dotlayer;
[self.layer addSublayer:dotlayer];
//复制层
CAReplicatorLayer *repl = (CAReplicatorLayer*)self.layer;
repl.instanceCount = 30;
//设置动画演延时执行时长
repl.instanceDelay = 0.1;
// //创建路径,设置启点
UIBezierPath *path = [UIBezierPath bezierPath];
self.path = path;
}
让自己成为复制层
//成为复制层
+(Class)layerClass{
return [CAReplicatorLayer class];
}
开始绘制动画
-(void)start{
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
anim.keyPath = @"position";
anim.path = self.path.CGPath;
anim.repeatCount = MAXFLOAT;
anim.duration = 13;
[self.dotlayer addAnimation:anim forKey:nil];
}
(3)重新绘制功能
-(void)redraw{
//删除动画
[self.dotlayer removeAllAnimations];
//删除路径
[self.path removeAllPoints];
//重绘制
[self setNeedsDisplay];
}
(4)最后就是把封装好的画板给我们的控制器用,这步就省略了.
四 : 实例解析未读消息移除动画
QQ移除消息.gif这个功能我们主要用到的一个图层叫 CAShapeLayer
这个图层的作用就是控制形变.我们先来分析一下这个功能的结构图
根据简单的 同位角角相等 和内错角相等 还有一些正余弦知识我们可以了解到 各个点的位置
解析 : 有的人会问 为什么 A点坐标 那个 为什么 y1 + r1 * sinθ
为什么不是 y1 - r1 * sinθ
因为当我们手指移动时候 这个 cosθ 是变化的 所以正负不一样,你也可以写成 y1 - r1 * sinθ 但是此时按照某一时刻的分析 我们暂且这么做.
(1)首先我们自定义一个btn 按钮
@interface TZmessageBtn : UIButton
@end
#import "TZmessageBtn.h"
@interface TZmessageBtn()
@property(nonatomic,weak)UIView *smallcircle;
@property(nonatomic,strong)CAShapeLayer *shapL;
@end
(2)添加移动手势 和 初始化视图
-(void)awakeFromNib{
[super awakeFromNib];
[self setUp];
//添加手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:pan];
}
-(void)setUp{
//圆角
self.layer.cornerRadius = self.bounds.size.width * 0.5;
//设置背景颜色
[self setBackgroundColor:[UIColor blueColor]];
[self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
self.titleLabel.font = [UIFont systemFontOfSize:12];
//添加小圆
UIView *smallcircle = [[UIView alloc]initWithFrame:self.frame];
smallcircle.layer.cornerRadius = self.layer.cornerRadius;
smallcircle.backgroundColor = self.backgroundColor;
self.smallcircle = smallcircle;
[self.superview addSubview:smallcircle];
//把一个UIView添加到指定的位置
[self.superview insertSubview:smallcircle belowSubview:self];
}
(3).两个圆 根据拖动计算距离
//求两个圆之间的距离
-(CGFloat)distanceWithSmallCircle:(UIView*)smallCircle BigCircle:(UIView*)bigcircle{
//x轴方向偏移量
CGFloat offsetX = bigcircle.center.x - smallCircle.center.x;
//y轴方向偏移量
CGFloat offsetY = bigcircle.center.y - smallCircle.center.y;
return sqrt(offsetX * offsetX + offsetY * offsetY);
}
(4)计算不规则的弧形
-(UIBezierPath*)pathWithSmallCircle:(UIView*)smallCircle BigCircle:(UIView*)bigcircle{
CGFloat x1 = smallCircle.center.x;
CGFloat y1 = smallCircle.center.y;
CGFloat x2 = bigcircle.center.x;
CGFloat y2 = bigcircle.center.y;
CGFloat d = [self distanceWithSmallCircle:smallCircle BigCircle:bigcircle];
if (d <= 0) {
return nil;
}
//------------
CGFloat cosθ = (y2 - y1) / d;
CGFloat sinθ = (x2 - x1) / d;
CGFloat r1 = smallCircle.bounds.size.width * 0.5;
CGFloat r2 = bigcircle.bounds.size.width * 0.5;
//描述点
//A点
CGPoint pointA = CGPointMake(x1 - r1 *cosθ, y1 + r1 * sinθ);
//B点
CGPoint pointB = CGPointMake(x1 + r1 * cosθ, y1 - r1 * sinθ);
//C点
CGPoint pointC = CGPointMake(x2 + r2 * cosθ, y2 - r2 * sinθ);
//D点
CGPoint pointD = CGPointMake(x2 - r2 * cosθ, y2 + r2 * sinθ);
// O点
CGPoint pointO = CGPointMake(pointA.x + d * 0.5 * sinθ, pointA.y + d * 0.5 * cosθ);
//P点
CGPoint pointP = CGPointMake(pointB.x + d * 0.5 * sinθ, pointB.y + d * 0.5 * cosθ);
UIBezierPath *path = [UIBezierPath bezierPath];
//AB
[path moveToPoint:pointA];
[path addLineToPoint:pointB];
//BC(曲线)
[path addQuadCurveToPoint:pointC controlPoint:pointP];
//CD
[path addLineToPoint:pointD];
//DA(曲线);
[path addQuadCurveToPoint:pointA controlPoint:pointO];
return path;
}
(5)设置按钮高亮情况
// 取消高亮
-(void)setHighlighted:(BOOL)highlighted{
}
(6)懒加载 图形图层
-(CAShapeLayer *)shapL{
if (_shapL == nil) {
_shapL = [CAShapeLayer layer];
_shapL.fillColor = [UIColor blueColor].CGColor;
//形状图层
[self.superview.layer insertSublayer:_shapL atIndex:0];
}
return _shapL;
}
(7)拖动事件
-(void)pan:(UIPanGestureRecognizer*)pan{
//拖动
CGPoint transp = [pan translationInView:self];
//transform 并没有修改center 修改的是frame
// self.transform = CGAffineTransformTranslate(self.transform, transp.x, transp.y);
CGPoint center = self.center;
center.x += transp.x;
center.y += transp.y;
self.center = center;
//复位
[pan setTranslation:CGPointZero inView:self];
CGFloat distance = [self distanceWithSmallCircle:self.smallcircle BigCircle:self];
//小圆半径根据距离增大半径减小
CGFloat smallR = self.bounds.size.width * 0.5;
smallR -= distance/10.0;
self.smallcircle.bounds = CGRectMake(0, 0, smallR * 2, smallR * 2);
self.smallcircle.layer.cornerRadius = smallR ;
//形状图层
UIBezierPath *path = [self pathWithSmallCircle:self.smallcircle BigCircle:self];
if (self.smallcircle.hidden == NO) {
self.shapL.path = path.CGPath;
}
if (distance > 60) {
//小圆隐藏,路径隐藏
self.smallcircle.hidden = YES;
[self.shapL removeFromSuperlayer];
self.shapL = nil;
}
// 手指松开
if (pan.state == UIGestureRecognizerStateEnded) {
//判断结束时是否大于60
if (distance >= 60) {
//让按钮消失
//播放动画消失
UIImageView *imagev = [[UIImageView alloc]initWithFrame:self.bounds];
[self addSubview:imagev];
NSMutableArray *Array = [NSMutableArray array];
for (int i = 0; i < 8; i++) {
UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"%d",i+1]];
[Array addObject:image];
}
imagev.animationImages = Array;
[imagev startAnimating];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self removeFromSuperview];
});
}else{
//复位操作
[self.shapL removeFromSuperlayer];
self.shapL = nil;
// 还原位置
[UIView animateWithDuration:0.5 delay:0 usingSpringWithDamping:0.2 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{
//设置大圆中心点位置
self.center = self.smallcircle.center;
} completion:^(BOOL finished) {
// 显示小圆
self.smallcircle.hidden = NO;
}];
}
}
}
解析 我们对于变化的计算方式通常是求出极值,然后再计算响应比例.
(8) 引用自定义按钮 并且 VC取消autolayout
self.view.translatesAutoresizingMaskIntoConstraints = NO;
五 : 总结
对于动画效果,我们不必过多的追求极端和完美,我们只要做好最基础的,好的效果就迎刃而解了.
我们还应该了解什么时候使用核心动画 什么时候 使用我们的UI动画
(1) 当我们动画只作用在Layer 层的时候
(2)我们核心的动画看到的都是假象,因真正的view 并没有发生变化.
(3)当我们不需要与用户进行交互的时候 使用核心动画
(4)我们使用根据路径做动画,还要转场动画时候 用核心动画.
我们先掌握这些简单的动画效果和基础知识,才能做出更漂亮的动画.
//下期 - 算法篇-再续.
网友评论