核心动画

  • 核心动画基本概念
  • 基础动画(CABasicAnimation)
  • 关键帧动画(CAKeyframeAnimation)
  • 动画组
  • 转场动画-CATransition
  • UIView的转场动画-双视图

一、核心动画基本概念

-导入QuartzCore.framework框架

1⃣️开发步骤

1.初始化一个动画对象(CAAnimation)并且设置一些动画相关属性

2.CALayer中很多属性都可以通过CAAnimation实现动画效果,包括:opacity、position、transform、bounds、contents等(可以在API文档中搜索:CALayer Animatable Properties)

3.添加动画对象到层(CALayer)中,开始执行动画

4.通过调用CALayer的addAnimation:forKey增加动画到层(CALayer)中,这样就能触发动画。通过调用removeAnimationForKey可以停止层中的动画

5.Core Animation的动画执行过程都是后台操作的,不会阻塞主线程

2⃣️属性

1.duration:动画的持续时间

2.repeatCount:重复次数(HUGE_VALF、MAX FLOAT无限重复)

3.repeatDuration:重复时间(用的很少)

4.removedOnCompletion:默认为Yes。动画执行完后默认会从图层删除掉

   5.fillMode
   6.biginTime
   7.timingFunction:速度控制函数,控制动画节奏
   8.delegate 

二、基础动画(CABasicAnimation) 

如果只是实现简单属性变化的动画效果,可以使用UIView的块动画替代基本动画

1⃣️属性说明

-fromValue:keyPath相应属性值的初始值

-toValue:keyPath相应属性的结束值

2⃣️动画过程说明:

-随着动画的就行,在duration的持续时间内,keyPath相应的属性值从fromValue渐渐变为toValue

-keyPath内容是CALayer的可动画Animation属性

-如果fillMode=kCAFillModeForwards同时removedOnCompletion=NO,那么在动画执行完毕后,图层会保持显示动画执行后的状态,但在实质上,图层的属性值还是动画执行前的初始值,并没有真正改变

3⃣️代码实现

位移需要考虑目标点设定的问题

1.将动画的所有方法封装到一个类里面

MyCAHelper.h
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>

#define kCAHelperAlphaAnimation @"opacity";                 // 淡入淡出动画
#define kCAHelperScaleAnimation @"transform.scale";         // 比例缩放动画
#define kCAHelperRotationAnimation @"transform.rotation";   // 旋转动画
#define kCAHelperPositionAnimation @"position";             // 平移位置动画

@interface MyCAHelper : NSObject

#pragma mark - 基本动画统一调用方法
+ (CABasicAnimation *)myBasicAnimationWithType:(NSString *)animationType
                                      duration:(CFTimeInterval)duration
                                          from:(NSValue *)from
                                            to:(NSValue *)to
                                 autoRevereses:(BOOL)autoRevereses;

#pragma mark - 关键帧动画方法
#pragma mark 摇晃动画
+ (CAKeyframeAnimation *)myKeyShakeAnimationWithDuration:(CFTimeInterval)duration
  angle:(CGFloat)angle
                                             repeatCount:(CGFloat)repeatCount;

#pragma mark 贝塞尔路径动画
+ (CAKeyframeAnimation *)myKeyPathAnimationWithDuration:(CFTimeInterval)duration
  path:(UIBezierPath *)path;

#pragma mark 弹力仿真动画
+ (CAKeyframeAnimation *)myKeyBounceAnimationFrom:(CGPoint)from
                                               to:(CGPoint)to
                                         duration:(CFTimeInterval)duration;

@end
MyCAHelper.m
#import "MyCAHelper.h"

@implementation MyCAHelper

#pragma mark - 基本动画统一调用方法
+ (CABasicAnimation *)myBasicAnimationWithType:(NSString *)animationType
                    duration:(CFTimeInterval)duration
                      from:(NSValue *)from
                      to:(NSValue *)to
                 autoRevereses:(BOOL)autoRevereses
{
  // 1. 实例化一个CA动画对象
  CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:animationType];
  
  // 2. 设置动画属性
  [anim setDuration:duration];
  
  [anim setFromValue:from];
  [anim setToValue:to];
  
  [anim setAutoreverses:autoRevereses];
  
  return anim;
}

#pragma mark - 关键帧动画方法
#pragma mark 摇晃动画
+ (CAKeyframeAnimation *)myKeyShakeAnimationWithDuration:(CFTimeInterval)duration
                           angle:(CGFloat)angle
                       repeatCount:(CGFloat)repeatCount
{
  // 1. 初始化动画对象实例
  CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];
  
  // 2. 设置动画属性
  [anim setDuration:duration];
  
  [anim setValues:@[@(angle), @(-angle), @(angle)]];
  
  [anim setRepeatCount:repeatCount];
  
  return anim;
}

#pragma mark 贝塞尔路径动画
+ (CAKeyframeAnimation *)myKeyPathAnimationWithDuration:(CFTimeInterval)duration
                           path:(UIBezierPath *)path
{
  // 1. 初始化动画对象实例
  CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
  
  // 2. 设置动画属性
  [anim setDuration:duration];
  
  [anim setPath:path.CGPath];
  
  return anim;
}

#pragma mark 弹力仿真动画
+ (CAKeyframeAnimation *)myKeyBounceAnimationFrom:(CGPoint)from
                         to:(CGPoint)to
                     duration:(CFTimeInterval)duration
{
  // 是一个基于路径的动画
  // 首先定义一个路径,记录弹力仿真的整个路径
  CGMutablePathRef path = CGPathCreateMutable();
  
  // 弹力仿真路径创建代码
  // 计算起始点与目标点之间的位置偏移量,这个偏移量的目的是为了能够计算出小球第一次延伸的长度
  CGFloat offsetX = from.x - to.x;
  CGFloat offsetY = from.y - to.y;
  
  // 1. 移动到起始点
  CGPathMoveToPoint(path, NULL, from.x, from.y);
  // 2. 将目标点的坐标添加到路径之中
  CGPathAddLineToPoint(path, NULL, to.x, to.y);
  // 3. 设置小球的弹力因子
  CGFloat offsetDivider = 4.0f;
  
  while (YES) {
    // 加延伸方向的路径
    CGPathAddLineToPoint(path, NULL, to.x + offsetX / offsetDivider,
               to.y + offsetY / offsetDivider);
    
    // 再次将目标点添加到路径
    CGPathAddLineToPoint(path, NULL, to.x, to.y);
    
    // 弹力因子递增,保证越来越接近目标点
    offsetDivider += 6.0f;
    
    // 当小球的当前位置距离目标点足够小,我们退出循环
    if ((abs(offsetX / offsetDivider) < 10.0f)
      && (abs(offsetY / offsetDivider) < 10.0f)) {
      
      break;
    }
  }
  
  CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
  [anim setPath:path];
  
  // 释放路径
  CGPathRelease(path);
  
  [anim setDuration:duration];
  
  return anim;
}

@end
#import "ViewController.h"
#import "MyCAHelper.h"

@interface ViewController ()
{
  UIView *_demoView;
  CGPoint location;
}
@end

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
  [self.view setBackgroundColor:[UIColor lightGrayColor]];
  
  _demoView = [[UIView alloc]initWithFrame:CGRectMake(50, 50, 100, 100)];
  [_demoView setBackgroundColor:[UIColor whiteColor]];
  [self.view addSubview:_demoView];
}

- (void)didReceiveMemoryWarning
{
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  UITouch *touch = [touches anyObject];
  location = [touch locationInView:self.view];
  
//	[_demoView setCenter:location];
  /** 
   1.测试基本动画
   */
//	CABasicAnimation *anim = [self testBasic1];
//	[anim setRepeatCount:3];
//	
//	[_demoView.layer addAnimation:anim forKey:nil];
  /**
   2.测试弹力仿真动画效果
   */
//	[_demoView.layer addAnimation:[self test1:_demoView.center to:location] forKey:nil];
  /**
   3.测试路径关键帧动画
   */
//	[_demoView.layer addAnimation:[self test2] forKey:nil];
//	[_demoView.layer addAnimation:[self test4:_demoView.center to:location] forKey:nil];
  /**
   4.测试摇晃关键帧动画
   */
  // 点击屏幕,开始摇晃,再次点击,停止摇晃
//	CAAnimation *anim = [_demoView.layer animationForKey:@"shakeAnimation"];
//	if (anim) {
//		[_demoView.layer removeAnimationForKey:@"shakeAnimation"];
//	} else {
//		[_demoView.layer addAnimation:[self test5] forKey:@"shakeAnimation"];
//	}
  CAKeyframeAnimation *anim = [self test1:_demoView.center to:location];
  [_demoView.layer addAnimation:anim forKey:nil];
}
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
  // 需要在这里对不同对象的动画方法进行完成处理!
  [_demoView setCenter:location];
  NSLog(@"%@", NSStringFromCGPoint(_demoView.center));
}

#pragma mark - 重构方法测试
#pragma mark 测试贝塞尔路径关键帧动画
- (CAKeyframeAnimation *)test5
{
  return [MyCAHelper myKeyShakeAnimationWithDuration:0.2 angle:M_PI_4 / 18 repeatCount:MAXFLOAT];
}

#pragma mark 测试贝塞尔路径关键帧动画
- (CAKeyframeAnimation *)test4:(CGPoint)from to:(CGPoint)to
{
  UIBezierPath *path = [UIBezierPath bezierPath];
  
  // 有两个控制点去挤出的曲线,能挤出S型的曲线
  [path moveToPoint:from];
  [path addCurveToPoint:to controlPoint1:CGPointMake(320, 0) controlPoint2:CGPointMake(0, 460)];
  
  return [MyCAHelper myKeyPathAnimationWithDuration:2.0 path:path];
}

#pragma mark 测试贝塞尔路径关键帧动画
- (CAKeyframeAnimation *)test3:(CGPoint)from to:(CGPoint)to
{
  UIBezierPath *path = [UIBezierPath bezierPath];
  
  // 只有一个控制点去挤出的曲线
  [path moveToPoint:from];
  [path addQuadCurveToPoint:to controlPoint:CGPointMake(320, 230)];
  
  return [MyCAHelper myKeyPathAnimationWithDuration:2.0 path:path];
}

#pragma mark 测试路径关键帧动画
- (CAKeyframeAnimation *)test2
{
  UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 100, 100)];
  
  return [MyCAHelper myKeyPathAnimationWithDuration:2.0 path:path];
}

#pragma mark 测试弹力仿真动画效果
- (CAKeyframeAnimation *)test1:(CGPoint)from to:(CGPoint)to
{
  CAKeyframeAnimation *anim = [MyCAHelper myKeyBounceAnimationFrom:from to:to duration:1.5];
  [anim setFillMode:kCAFillModeForwards];
  [anim setRemovedOnCompletion:NO];
  
  [anim setDelegate:self];
  
  return anim;
}

- (CABasicAnimation *)testBasic1
{
  return [MyCAHelper myBasicAnimationWithType:@"opacity" duration:1.0 from:@(1.0) to:@(0.3) autoRevereses:YES];
}

@end

三、关键帧动画(CAKeyframeAnimation) 

基础动画只能从一个值到另一个值,关键帧动画可以用一个数组保存一系列值

1⃣️属性说明

-values:所有的值(用的较少)

-path:路线(如果设置了path,那么values将被忽略)

-keyTimes:可以为对应的关键帧制定对应的时间点,取值范围是0到1.0

2⃣️过程步骤

-初始化自定义视图

-点击屏幕,执行动画

     1.指定点平移动画(values)

     2.路径平移动画(path C语言框架CGMutablePathRef,需要手动释放内存)

     3.贝塞尔路径动画(OC框架UIBezierPath)

     4.摇晃动画(修改旋转角度)

3⃣️代码重构

如上代码,重构在了一起


四、动画组 

动画是可以并发执行的

-定义一个group组

-定义动画

-将动画加入group组

-可以给组设置属性

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  UITouch *touch = [touches anyObject];
  CGPoint location = [touch locationInView:self.view];
  
  [_demoView setBackgroundColor:[UIColor redColor]];
  
  // 1. 定义动画组
  CAAnimationGroup *group = [CAAnimationGroup animation];
  // 定义一组动画
  // 淡入淡出动画
  CABasicAnimation *alpha = [MyCAHelper myBasicAnimationWithType:kCAHelperAlphaAnimation duration:1.0 from:@(1.0) to:@(0.3) autoRevereses:YES];
  // 旋转动画
  CABasicAnimation *rotation = [MyCAHelper myBasicAnimationWithType:kCAHelperRotationAnimation duration:2.0 from:@(-M_PI_2) to:@(M_PI_2) autoRevereses:NO];
  // 缩放动画
  CABasicAnimation *scale = [MyCAHelper myBasicAnimationWithType:kCAHelperScaleAnimation duration:0.5 from:@(1.0) to:@(0.1) autoRevereses:YES];
  
  // 关键帧路径动画,弹力仿真动画效果
  CAKeyframeAnimation *path = [self test1:_demoView.center to:location];
    
  // 2. 设置动画组属性
  [group setAnimations:@[alpha, path, rotation, scale]];
  
  // 设置动画的时长
  [group setDuration:4.0];
  
  // 3. 将动画组添加到图层
  [_demoView.layer addAnimation:group forKey:nil];
}

五、转场动画-CATransition 
1⃣️属性说明

-type:动画过渡类型

-subtype:动画过渡方向

-startProgress:动画起点(在整体动画的百分比)

-endProgress:动画终点(在整体动画的百分比)

-增加一个转场演示视图

-增加轻扫手势

-在轻扫手势方法中

     1.更改演示视图内容

     2.创建转场动画效果

     3.将转场动画添加到视图的图层

// 轻扫手势操作
- (void)swipeAction:(UISwipeGestureRecognizer *)sender
{
  // 通过轻扫手势,让切换出来的视图是蓝色的
  if (_demoView.tag == 0) {
    [_demoView setBackgroundColor:[UIColor blueColor]];
    [_demoView setTag:1];
  } else {
    [_demoView setBackgroundColor:[UIColor redColor]];
    [_demoView setTag:0];
  }
  
  // 根据视图内容我们来实现专场动画
  CATransition *anim = [CATransition animation];
  // 设置专场动画的过渡类型
  [anim setType:@"cameraIrisHollowClose"];
  // 需要根据手势的方向,来决定专场动画的动画方向
  // 注意:在转场动画中,动画方向的左右是和手势的方向相反的
  if (sender.direction == UISwipeGestureRecognizerDirectionLeft) {
    [anim setSubtype:kCATransitionFromRight];
  } else {
    [anim setSubtype:kCATransitionFromLeft];
  }
  
  [_demoView.layer addAnimation:anim forKey:nil];
}

- (void)viewDidLoad
{
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
  // 1. 实例化自定义视图
  _demoView = [[UIView alloc]initWithFrame:self.view.bounds];
  [_demoView setBackgroundColor:[UIColor redColor]];
  
  [self.view addSubview:_demoView];
  
  // 2. 增加轻扫手势
  UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc]
                       initWithTarget:self
                       action:@selector(swipeAction:)];
  [swipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
  [_demoView addGestureRecognizer:swipeLeft];
  
  UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc]
                       initWithTarget:self
                       action:@selector(swipeAction:)];
  [swipeRight setDirection:UISwipeGestureRecognizerDirectionRight];
  [_demoView addGestureRecognizer:swipeRight];
}

六、UIView的转场动画-双视图 

+(void)transitionWithView:(UIView*)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void(^)(void))animations completion:(void(^)(BOOL finished))completion;

1⃣️参数说明

-duration:动画的持续时间

-view:需要进行转场动画的视图

-options:转场动画的类型

-animations:将改变视图属性的代码放在这个Block里

-completion:动画结束后,自动调用的Block 

@interface ViewController ()
{
  UIImageView *_demoImageView;
  
  UIImageView *_demoImageView2;
}
@end

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
  // 实例化第一个UIImageView的对象
  _demoImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"1.jpg"]];
  [self.view addSubview:_demoImageView];
  
  // 实例化第二个UIImageView对象
  // 注意:在双视图转场动画中,不要将第二个视图添加到主视图
  _demoImageView2 = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"2.jpg"]];
}

- (void)didReceiveMemoryWarning
{
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  // 单击屏幕的时候,实现转场动画效果
  [self animation1];
}

#pragma mark - 单视图的转场动画
- (void)animation2
{
  UIImageView *from;
  UIImageView *to;
  
  if (_demoImageView.superview) {
    from = _demoImageView;
    to = _demoImageView2;
  } else {
    from = _demoImageView2;
    to = _demoImageView;
  }
  
  [UIView transitionFromView:from toView:to duration:1.0f options:UIViewAnimationOptionTransitionCrossDissolve completion:^(BOOL finished) {
    
    NSLog(@"image view1 的主视图: %@", _demoImageView.superview);
    NSLog(@"image view2 的主视图: %@", _demoImageView2.superview);
  }];
}

#pragma mark - 单视图的转场动画
- (void)animation1
{
  [UIView transitionWithView:_demoImageView duration:1.0f options:UIViewAnimationOptionTransitionCurlUp animations:^{
    
    // 在动画块代码中设置视图内容变化
    if (_demoImageView.tag == 0) {
      [_demoImageView setImage:[UIImage imageNamed:@"2.jpg"]];
      [_demoImageView setTag:1];
    } else {
      [_demoImageView setImage:[UIImage imageNamed:@"1.jpg"]];
      [_demoImageView setTag:0];
    }
    
  } completion:nil];
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值