- 在iOS中,你能看的见摸得着的东西基本上都是
UIView
,比如一个按钮、一个文本标签、一个文字输入框,一个图标等等,这些都是UIView - 其实UIView之所以能显示在屏幕, 完全是应为它内部的一个
图层
- 在创建UIView对象时,UIView内部会自动创建一个图层(即
CALayer
对象),通过UIView
的layer属性可以访问这个层
@property(nonatomic,readonly,retain)CALayer*layer;
- 当
UIView
需要显示到屏幕上时,会调用drawRect:方法进行绘图,并且会将所有的内容绘制在自己的图层上,绘图完毕后,徐彤会讲图层拷贝到屏幕上,于是就完成了UIView的显示 - 换句话说,
UIView
本身不具备显示的功能,是它内部的层才有显示功能
<h5>CALayer的基本使用</h5>
- 通过操作CALayer对象,可以很方便地调整UIView的一些外观属性,比如:
- 阴影
- 圆角大小
- 边框宽度和颜色
- … …
- 还可以给图层添加动画,来实现一些比较炫酷的效果
<b>UIView的Layer</b>
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 设置阴影的颜色
self.colorV.layer.shadowColor = [UIColor blackColor].CGColor;
// 设置阴影的不透明度
self.colorV.layer.shadowOpacity = 1;
// 设置阴影的偏移量
self.colorV.layer.shadowOffset = CGSizeMake(30, 30);
// 设置阴影的模糊半径
self.colorV.layer.shadowRadius = 10;
// 边框宽度,往里面走的
self.colorV.layer.borderWidth = 10;
// 边框颜色,
self.colorV.layer.borderColor = [UIColor brownColor].CGColor;
// 设置圆角
self.colorV.layer.cornerRadius = 30;
}
最终呈现效果
<b>UIView的Layer</b>
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 设置阴影的颜色
self.imageV.layer.shadowColor = [UIColor blackColor].CGColor;
// 设置阴影的不透明度
self.imageV.layer.shadowOpacity = 1;
// 设置阴影的偏移量
self.imageV.layer.shadowOffset = CGSizeMake(30, 30);
// 设置阴影的模糊半径
self.imageV.layer.shadowRadius = 10;
// 边框宽度,往里面走的
self.imageV.layer.borderWidth = 10;
// 边框颜色,
self.imageV.layer.borderColor = [UIColor brownColor].CGColor;
// 注意:UIImageView的layer使用有些不一样,在设置圆角的时候需要注意:
// 设置圆角
self.imageV.layer.cornerRadius = 100;
}
呈现效果:
<b>原因:CALayer层中,有一个专门存放图片的层:contents</b>
self.imageV.layer.masksToBounds = YES;
呈现效果:
<h5>CATransform3D</h5>
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 3D效果
[UIView animateWithDuration:0.5 animations:^{
// 第一种设置方法:
self.imageV.layer.transform = CATransform3DMakeRotation(M_PI, 1, 1, 0);
// 把结构体换成对象
NSValue *value = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 1, 1, 0)];
// 第二种设置方式:
// 我们一般通过KVC做快速旋转。平移,缩放
[self.imageV.layer setValue:@(M_PI) forKey:@"transform.rotation.x"];
}];
}
<b>在第二种方法中,forKey的值不是随便填写的,它在CATransform3D Key Paths中有规定</b>
Field Key Path | Description |
---|---|
rotation.x | Set to an NSNumber object whose value is the rotation, in radians, in the x axis. |
rotation.y | Set to an NSNumber object whose value is the rotation, in radians, in the y axis. |
rotation.z | Set to an NSNumber object whose value is the rotation, in radians, in the z axis. |
rotation | Set to an NSNumber object whose value is the rotation, in radians, in the z axis. This field is identical to setting the rotation.z field. |
scale.x | Set to an NSNumber object whose value is the scale factor for the x axis. |
scale.y | Set to an NSNumber object whose value is the scale factor for the y axis. |
scale.z | Set to an NSNumber object whose value is the scale factor for the z axis. |
scale | Set to an NSNumber object whose value is the average of all three scale factors. |
translation.x | Set to an NSNumber object whose value is the translation factor along the x axis. |
translation.y | Set to an NSNumber object whose value is the translation factor along the y axis. |
translation.z | Set to an NSNumber object whose value is the translation factor along the z axis. |
translation | Set to an NSValue object containing an NSSize or CGSize data type. That data type indicates the amount to translate in the x and y axis. |
<h5>UIView和CALayer的选择</h5>
使用Layer展示一张图片
CALayer *layer = [[CALayer alloc] init];
layer.backgroundColor = [UIColor brownColor].CGColor;
layer.frame = CGRectMake(100, 100, 100, 100);
[self.view.layer addSublayer:layer];
layer.contents = (id)[UIImage imageNamed:@"123.png"].CGImage;
<b>关于CALayer的疑惑</b>
- 首先
- CALayer是定义在QuartzCore框架中的
- CGImageRef、CGColorRef两种数据类型是定义在CoreGraphics框架中的
- UIColor、UIImage是定义在UIKit框架中的
- 其次
- QuartzCore框架和CoreGraphics框架是可以跨平台使用的,在iOS和Mac OS X上都能使用
- 但是UIKit只能在iOS中使用
- 为了保证可移植性,QuartzCore不能使用UIImage、UIColor,只能使用CGImageRef、CGColorRef
<b>UIView和CALayer的选择</b>
- 通过CALayer,就能做出跟UIImageView一样的界面效果
- 既然CALayer和UIView都能实现相同的显示效果,那究竟该选择谁好呢?
- 其实,对比CALayer,UIView多了一个事件处理的功能。也就是说,CALayer不能处理用户的触摸事件,而UIView可以
- 所以,如果显示出来的东西需要跟用户进行交互的话,用UIView;如果不需要跟用户进行交互,用UIView或者CALayer都可以
- 当然,CALayer的性能会高一些,因为它少了事件处理的功能,更加轻量级
<h5>position和anchorPoint</h5>
<b>CALayer有2个非常重要的属性:position
和anchorPoint
</b>
- @property CGPoint position;
- 用来设置CALayer在父层中的位置
- 以父层的左上角为原点(0, 0)
- @property CGPoint anchorPoint;
- 称为“定位点”、“锚点”
- 决定着CALayer身上的哪个点会在position属性所指的位置
- 以自己的左上角为原点(0, 0)
- 它的x、y取值范围都是0~1,默认值为(0.5, 0.5)
<b> UIView的center和layer的position是一个点</br>
position和anchorPoint 点重合</b>
<h5>隐式动画</h5>
- 每一个UIView内部都默认关联着一个CALayer,我们可用称这个Layer为Root Layer(根层)
- 所有的非RootLayer,也就是手动创建的CALayer对象,都存在着隐式动画
- 什么是隐式动画?
- 当对非RootLayer的部分属性进行修改时,默认会自动产生一些动画效果
- 而这些属性称为AnimatableProperties(可动画属性)
- 列举几个常见的AnimatableProperties:
- bounds:用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画
- backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画
- position:用于设置CALayer的位置。修改这个属性会产生平移动画
- l可以通过动画事务(CATransaction)关闭默认的隐式动画效果
// 只有非根层才有隐式动画(自己手动创建)
[CATransaction begin];
[CATransaction setAnimationDuration: 2.0];
[CATransaction setDisableActions:YES];
[CATransaction commit];
<b>挂钟案例</b>
// 在storyboard中拖一个UIImageView并确定宽高相同,然后拖线,然后添加图片
@property (weak, nonatomic) IBOutlet UIImageView *clockView;
/** 当前的秒针 */
@property (nonatomic, strong) CALayer *secondLayer;
/** 当前的分针 */
@property (nonatomic, strong) CALayer *minuteLayer;
/** 当前的时针 */
@property (nonatomic, strong) CALayer *hourLayer;
// 代码中用到的宏
//每一秒旋转的度数
#define perSecondAngle 6
//每一分旋转的度数
#define perMinuteAngle 6
//每一分旋转的度数
#define perHourAngle 30
// 每一分钟,时针旋转的角度
#define perMinuteWithHourRotateAngle 0.5
// 角度转弧度的宏
#define angle2radian(angle) ((angle) / 180.0 * M_PI)
- (void)viewDidLoad {
[super viewDidLoad];
[self setHourLayer];
[self setMinuteLayer];
[self setSecondLayer];
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeChange) userInfo:nil repeats:YES];
// 为了防止刚开始,秒针跳一下,在加载的时候就调用一次,
[self timeChange];
}
- (void)timeChange {
NSCalendar *calendar = [NSCalendar currentCalendar];
// components: 是日历的组件, 年,月,日,时,分,秒
//fromDate: 从什么时间开始获取
NSDateComponents *dateComponents = [calendar components:NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour fromDate:[NSDate date]];
// 获取当前是多少秒
NSInteger currentSecond = dateComponents.second + 1;
// 秒针开始旋转
// 计算秒针当前旋转的角度
// angle = 当前多少秒 * 每一秒旋转的角度
CGFloat secondAngle = currentSecond * perSecondAngle;
self.secondLayer.transform = CATransform3DMakeRotation(angle2radian(secondAngle), 0, 0, 1);
// 获取当前是多少分
NSInteger currentMinute = dateComponents.minute;
// 分针开始旋转
// 计算秒针当前旋转的角度
// angle = 当前多少分 * 每一分旋转的角度
CGFloat minuteAngle = currentMinute * perMinuteAngle;
self.minuteLayer.transform = CATransform3DMakeRotation(angle2radian(minuteAngle), 0, 0, 1);
// 获取当前是多少小时
NSInteger currentHour = dateComponents.hour;
// 分针开始旋转
// 计算秒针当前旋转的角度
// angle = 当前多少小时 * 每一小时旋转的角度
CGFloat hourAngle = currentHour * perHourAngle + currentMinute * perMinuteWithHourRotateAngle;
self.hourLayer.transform = CATransform3DMakeRotation(angle2radian(hourAngle), 0, 0, 1);
}
// // 添加秒针
- (void)setSecondLayer {
CALayer *secondLayer = [CALayer layer];
secondLayer.bounds = CGRectMake(0, 0, 1, 80);
secondLayer.backgroundColor = [UIColor redColor].CGColor;
secondLayer.anchorPoint = CGPointMake(0.5, 1);
secondLayer.position = CGPointMake(self.clockView.bounds.size.width * 0.5, self.clockView.bounds.size.height * 0.5);
[self.clockView.layer addSublayer:secondLayer];
self.secondLayer = secondLayer;
}
// // 添加分针
- (void)setMinuteLayer {
CALayer *minuteLayer = [CALayer layer];
minuteLayer.bounds = CGRectMake(0, 0, 2, 70);
minuteLayer.backgroundColor = [UIColor blackColor].CGColor;
minuteLayer.cornerRadius = 1.5;
minuteLayer.anchorPoint = CGPointMake(0.5, 1);
minuteLayer.position = CGPointMake(self.clockView.bounds.size.width * 0.5, self.clockView.bounds.size.height * 0.5);
[self.clockView.layer addSublayer:minuteLayer];
self.minuteLayer = minuteLayer;
}
// // 添加时针
- (void)setHourLayer {
CALayer *hourLayer = [CALayer layer];
hourLayer.bounds = CGRectMake(0, 0, 3, 50);
hourLayer.backgroundColor = [UIColor blackColor].CGColor;
hourLayer.cornerRadius = 1.5;
hourLayer.anchorPoint = CGPointMake(0.5, 1);
hourLayer.position = CGPointMake(self.clockView.bounds.size.width * 0.5, self.clockView.bounds.size.height * 0.5);
[self.clockView.layer addSublayer:hourLayer];
self.hourLayer = hourLayer;
}
<h5>Core Animation</h5>
- Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍。也就是说,使用少量的代码就可以实现非常强大的功能。
- Core Animation可以用在Mac OS X和iOS平台。
- Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。
- 要注意的是,Core Animation是直接作用在CALayer上的,并非UIView。
- 乔帮主在2007年的WWDC大会上亲自为你演示Core Animation的强大:点击查看视频
<h5>核心动画继承结构</h5>
<b>注意:图中的黑色虚线代表“继承”某个类,红色虚线代表“遵守”某个协议</b>
<h5>Core Animation的使用步骤</h5>
- 如果不是xcode5之后的版本,使用它需要先添加QuartzCore.framework和引入对应的框架<QuartzCore/QuartzCore.h>
- 开发步骤:
- 1.首先得有CALayer
- 2.初始化一个CAAnimation对象,并设置一些动画相关属性
- 3.通过调用CALayer的addAnimation:forKey:方法,增加CAAnimation对象到CALayer中,这样就能开始执行动画了
- 4.通过调用CALayer的removeAnimationForKey:方法可以停止CALayer中的动画
<h5>CAAnimation——简介</h5>
- CAAnimation是所有动画对象的父类,负责控制动画的持续时间和速度,是个抽象类,不能直接使用,应该使用它具体的子类
- 属性说明:(红色代表来自CAMediaTiming协议的属性)
-
duration
:动画的持续时间 -
repeatCount
:重复次数,无限循环可以设置<b>HUGE_VALF</b>或者<b>MAXFLOAT</b> -
repeatDuration
:重复时间 - removedOnCompletion:默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。<b>如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards</b>
-
fillMode
:决定当前对象在非active时间段的行为。比如动画开始之前或者动画结束之后 -
beginTime
:可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,<b>CACurrentMediaTime()</b>为图层的当前时间 - timingFunction:速度控制函数,控制动画运行的节奏
- delegate:动画代理
-
<b>CAAnimation——动画填充模式</b>
- fillMode属性值(要想fillMode有效,最好设置removedOnCompletion = NO)
-
kCAFillModeRemoved
这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态 -
kCAFillModeForwards
当动画结束后,layer会一直保持着动画最后的状态 -
kCAFillModeBackwards
在动画开始前,只需要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始。 -
kCAFillModeBoth
这个其实就是上面两个的合成.动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态
-
<b>CAAnimation——速度控制函数</b>
- 速度控制函数(CAMediaTimingFunction)
- 1.kCAMediaTimingFunctionLinear(线性):匀速,给你一个相对静态的感觉
- 2.kCAMediaTimingFunctionEaseIn(渐进):动画缓慢进入,然后加速离开
- 3.kCAMediaTimingFunctionEaseOut(渐出):动画全速进入,然后减速的到达目的地
- 4.kCAMediaTimingFunctionEaseInEaseOut(渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。这个是默认的动画行为。
<b>CAAnimation——动画代理方法</b>
- CAAnimation在分类中定义了代理方法
@interfaceNSObject(CAAnimationDelegate)
/*Called when the animation begins its active duration. */
-(void)animationDidStart:(CAAnimation*)anim;
/*Called when the animation either completes its active duration or
* is removed from the object it is attached to(i.e. the layer). 'flag'
* is true if the animation reached the end ofits active duration
* without being removed. */
-(void)animationDidStop:(CAAnimation*)animfinished:(BOOL)flag;
@end
<h5>CABasicAnimation(基础动画)基本使用</h5>
简单心跳制作
// storyboard中拖一个200*200的ImageView,并拖线
@property (weak, nonatomic) IBOutlet UIImageView *imageV;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 1.创建动画对象
CABasicAnimation *basicAnimation = [CABasicAnimation animation];
// 2.设置动画属性值
basicAnimation.keyPath = @"transform.scale";
basicAnimation.toValue = @0;
// 设置动画执行的次数
basicAnimation.repeatCount = MAXFLOAT;
// 设置动画执行长度
basicAnimation.duration = 1;
// 自动翻转(怎么样去,怎么样回来)
basicAnimation.autoreverses = YES;
/**
// 3.添加动画
@param CAAnimation 动画对象
@param NSString (forKey)添加动画组需要用到的标识
*/
[self.imageV.layer addAnimation:basicAnimation forKey:nil];
}
呈现效果:
<h5>CAKeyframeAnimation(帧动画)基本使用</h5>
简单制作卸载程序的抖动效果
// 1.第一种简单使用,values——
/** 角度转弧度 */
#define angle2radian(angle) ((angle) / 180.0 * M_PI)
@property (weak, nonatomic) IBOutlet UIImageView *iconV;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 1.创建动画
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animation];
// 2.设置动画属性值
keyframeAnimation.keyPath = @"transform.rotation";
keyframeAnimation.values = @[@(angle2radian(-5)), @(angle2radian(5)), @(angle2radian(-5))];
keyframeAnimation.repeatCount = MAXFLOAT;
keyframeAnimation.duration = 0.2;
// keyframeAnimation.autoreverses = YES;
[self.iconV.layer addAnimation:keyframeAnimation forKey:nil];
}
呈现效果:
// 2.第二种简单使用,path——
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 1.创建动画
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animation];
keyframeAnimation.duration = 2;
keyframeAnimation.autoreverses = YES;
keyframeAnimation.repeatCount = MAXFLOAT;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 70)];
[path addLineToPoint:CGPointMake(300, 100)];
[path addLineToPoint:CGPointMake(100, 90)];
keyframeAnimation.keyPath = @"position";
keyframeAnimation.path = path.CGPath;
[self.iconV.layer addAnimation:keyframeAnimation forKey:nil];
}
呈现效果:
<h5>CATransition(转场动画)基本使用</h5>
// storyboard创建ImageView设置宽高,并拖线
@property (weak, nonatomic) IBOutlet UIImageView *photoV;
static int _i = 0;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 转场代码和转场动画必须得在同一个方法中
// // 转场代码
_i++;
if (_i == 5) {
_i = 0;
}
NSString *imageName = [NSString stringWithFormat:@"%d", _i];
self.photoV.image = [UIImage imageNamed:imageName];
// // 转场动画
// 添加动画
CATransition *transitionAnimation = [CATransition animation];
// 设置转场动画
transitionAnimation.type = @"rippleEffect";
// 设置动画的起始位置 ------+++------
transitionAnimation.startProgress = 0.3;
// 设置动画的结束位置 ------+++------
transitionAnimation.endProgress = 0.5;
transitionAnimation.duration = 1;
[self.photoV.layer addAnimation:transitionAnimation forKey:nil];
}
呈现效果:
<b>转场动画过渡效果</b>
类型字符串 | 效果说明 | 关键字 | 方向 |
---|---|---|---|
fade | 交叉淡化过渡 | YES | |
push | 新视图把旧视图推出去 | YES | |
moveIn | 新视图移到旧视图上面 | YES | |
reveal | 将旧视图移开,显示下面的新视图 | YES | |
cube | 立方体翻滚效果 | ||
oglFlip | 上下左右翻转效果 | ||
suckEffect | 收缩效果,如一块布被抽走 | NO | |
rippleEffect | 水滴效果 | NO | |
pageCurl | 向上翻页效果 | ||
pageUnCurl | 向下翻页效果 | ||
cameraIrisHollowOpen | 相机镜头打开效果 | NO | |
cameraIrisHollowClose | 相机镜头关闭效果 | NO |
<h5>CAAnimationGroup(动画组)基本使用</h5>
@property (weak, nonatomic) IBOutlet UIView *colorView;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CABasicAnimation *basicAnimation01 = [CABasicAnimation animation];
basicAnimation01.keyPath = @"position.y";
basicAnimation01.toValue = @300;
// basicAnimation01.removedOnCompletion = NO;
// basicAnimation01.fillMode = kCAFillModeForwards;
//
// [self.colorView.layer addAnimation:basicAnimation01 forKey:nil];
CABasicAnimation *basicAnimation02 = [CABasicAnimation animation];
basicAnimation02.keyPath = @"transform.scale";
basicAnimation02.toValue = @0.5;
// basicAnimation02.removedOnCompletion = NO;
// basicAnimation02.fillMode = kCAFillModeForwards;
//
// [self.colorView.layer addAnimation:basicAnimation02 forKey:nil];
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
// 会自动执行animations数组当中的所有动画对象
animationGroup.animations = @[basicAnimation01, basicAnimation02];
animationGroup.removedOnCompletion = NO;
animationGroup.fillMode = kCAFillModeForwards;
[self.colorView.layer addAnimation:animationGroup forKey:nil];
}
呈现效果:
<h5>UIView与核心动画区别</h5>
- 核心动画只作用在layer上
- 核心动画看到的都是假象,它并没有修改UIView的真实位置
<b>什么时候使用核心动画</b>
- 当不需要与用户进行交互的时候,使用核心动画
- 当需要根据路径做动画的时候,使用核心动画
- 当作转场动画时,使用核心动画(转场类型比较多)