IOS基础:动画

2020-10-20  本文已影响0人  时光啊混蛋_97boy

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录

比较

iOS系统本身提供了两套绘图的框架,即UIBezierPathCore Graphics。前者所属UIKit,其实是对Core Graphics框架关于path的进一步封装,所以使用起来比较简单。但是毕竟Core Graphics更接近底层,所以它更加强大。我们使用UIView的 -(void)drawRect:方法绘图就是使用的CoreGraphics框架。

CoreGraphics(核心图形)

它是iOS的核心图形库,包含Quartz2D绘图API接口,常用的CGFloatCGSizeCGPoint等这些图形,都定义在这个框架中,类名以CG开头的都属于CoreGraphics框架,它提供的都是C语言函数接口,是可以在iOSmac OS通用的。

CoreAnimation(核心动画)

用来做动画的,非常的简单,但是效果非常绚丽。

CoreGraphics和CoreAnimation的关系

它们都是跨iOSMac OS使用的,这点区别于UIKit,并且- CoreAnimation中大量使用到CoreGraphics中的类,因为实现动画要用到图形库中的东西。


一、UIView 动画

1、简介

比较
常用枚举类型
视图动画曲线 UIViewAnimationCurve
视图内容填充模式 UIViewContentMode
视图动画过渡效果 UIViewAnimationTransition
视图自动调整大小方式 UIViewAutoresizing
视图的动画选项 UIViewAnimationOptions
视图关键帧动画选项 UIViewKeyframeAnimationOptions
视图的系统动画 UISystemAnimation
布局约束的轴 UILayoutConstraintAxis(水平还是竖直)
常见视图动画扩展
UIViewGeometry:视图几何相关的扩展 
UIViewHierarchy:视图层次结构相关的扩展
UIViewRendering:视图外观渲染相关的扩展,例如是否隐藏、透明度、背景颜色等
UIViewAnimation:视图动画相关的扩展
UIViewAnimationWithBlocks:视图用block快速定义动画的扩展
UIViewKeyframeAnimations:视图关键帧动画相关的扩展
UIViewGestureRecognizers:视图上手势相关的扩展 
UIViewMotionEffects:视图上运动效果相关的扩展
UIConstraintBasedLayoutCoreMethods:视图上约束相关的扩展
UISnapshotting:视图快照相关的扩展

2、动画演示

动画演示 动画演示 动画演示 动画演示
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    // 红方块起始frame
    if (CGRectEqualToRect(self.redOriginFrame, CGRectZero))
    {
        self.redOriginFrame = self.redView.frame;
    }
    
    // 蓝方块起始frame
    if (CGRectEqualToRect(self.blueOriginFrame, CGRectZero))
    {
        self.blueOriginFrame = self.blueView.frame;
    }
    
    
    switch (indexPath.item)
    {
        case 0:// 重置
        {
            self.redView.frame = self.redOriginFrame;// 红方块起始frame
            self.redView.alpha = 1.0;// 透明
            self.redView.transform = CGAffineTransformIdentity;// 移动
            self.redView.backgroundColor = [UIColor redColor];
            
            self.blueView.frame = self.blueOriginFrame;// 蓝方块起始frame
            
            break;
        }
        case 1:// 移动红色方块
        {
            CGRect redFrame = self.redView.frame;
            
            // 下移100点
            redFrame.origin.y += 100;
            // 产生动画效果
            [UIView animateWithDuration:UIAnimationDuration animations:^{
                // 红方块新的frame
                self.redView.frame = redFrame;
            }];
            break;
        }
        case 2:// 缩小红色方块,蛮奇怪的,它放大以后再缩小,我明明是减小宽高
        {
            CGRect redFrame = self.redView.frame;
            
            // 缩小宽高
            redFrame.size.width -= 50;
            redFrame.size.height -= 50;
            // 产生动画效果
            [UIView animateWithDuration:UIAnimationDuration animations:^{
                // 红方块新的frame
                self.redView.frame = redFrame;
            }];
            break;
        }
        case 3:// 旋转红色方块
        {
            // 获取初始transform属性
            CGAffineTransform transform = self.redView.transform;
            // 旋转90度
            transform = CGAffineTransformRotate(transform, M_PI/4);
            // 产生动画效果
            [UIView animateWithDuration:UIAnimationDuration animations:^{
                // 红方块新的transform属性
                self.redView.transform = transform;
            }];
            break;
        }
        case 4:// 改变红色为紫色
        {
            [UIView animateWithDuration:UIAnimationDuration animations:^{
                self.redView.backgroundColor = [UIColor purpleColor];
            }];
            break;
        }
        case 5:// 改变透明度为半透明
        {
            [UIView animateWithDuration:UIAnimationDuration animations:^{
                self.redView.alpha = 0.5;
            }];
            break;
        }
        case 6:// 移动红色方块并同时旋转90度
        {
            // 下移
            CGRect redFrame = self.redView.frame;
            redFrame.origin.y += 100;
            
            // 旋转
            CGAffineTransform transform = self.redView.transform;
            transform = CGAffineTransformRotate(transform, M_PI/2);
            
            // 产生动画效果
            [UIView animateWithDuration:UIAnimationDuration animations:^{
                // 新的transform和frame属性
                self.redView.frame = redFrame;
                self.redView.transform = transform;
            }];
            break;
        }
        case 7:// 先移动后旋转
        {
            // 下移
            CGRect redFrame = self.redView.frame;
            redFrame.origin.y += 100;
            
            // 旋转
            CGAffineTransform transform = self.redView.transform;
            transform = CGAffineTransformRotate(transform, M_PI/2);
            
            [UIView animateWithDuration:UIAnimationDuration animations:^{// 下移动画
                self.redView.frame = redFrame;
            } completion:^(BOOL finished) {// 完成后进入旋转动画
                [UIView animateWithDuration:UIAnimationDuration animations:^{
                    self.redView.transform = transform;
                } completion:^(BOOL finished) {// 完成后输出旋转完成
                    NSLog(@"旋转完成");
                }];
            }];
            break;
        }
        case 8:// 通过中心下移
        {
            CGPoint center = self.redView.center;
            // 下移100点
            center.y += 100;
            [UIView animateWithDuration:UIAnimationDuration animations:^{
                // 新的中心
                self.redView.center = center;
            }];
            break;
        }
        case 9:// 渐变方式进行缩放
        {
            // 缩放一半
            CGAffineTransform transform = self.redView.transform;
            transform = CGAffineTransformScale(transform, 0.5, 0.5);
            // 产生动画效果
            [UIView animateWithDuration:UIAnimationDuration delay:1.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
                // 新的transform属性
                self.redView.transform = transform;
            } completion:^(BOOL finished) {
                NSLog(@"缩放完成");
            }];
            break;
        }
        case 10:// 通过中心让蓝色和红色方块同时右移
        {
            CGPoint redCenter = self.redView.center;
            CGPoint blueCenter = self.blueView.center;
            
            // 下移100点
            redCenter.x += 100;
            blueCenter.x += 100;
            
            // 产生动画效果
            [UIView animateWithDuration:UIAnimationDuration animations:^{
                self.redView.center = redCenter;
                self.blueView.center = blueCenter;
            }];
            break;
        }
        case 11:// 重复来回移动
        {
            CGPoint center = self.redView.center;
            
            // 下移100点
            center.y += 100;
            // 重复/自动逆向
            [UIView animateWithDuration:UIAnimationDuration delay:0 options:UIViewAnimationOptionRepeat|UIViewAnimationOptionAutoreverse animations:^{
                self.redView.center = center;
            } completion:nil];
            break;
        }
        default:
            break;
    }
}

二、Core Animation

1、简介

a、Core Animation
区别

UIView动画可以看成是对Core Animation的封装,和UIView动画不同的是,通过Core Animation改变layer的状态(比如position),动画执行完毕后实际上是没有改变的(表面上看起来已改变)。

优点

1)性能强大,使用硬件加速,可以同时向多个图层添加不同的动画效果
2)接口易用,只需要少量的代码就可以实现复杂的动画效果
3)运行在后台线程中,在动画过程中可以响应交互事件(UIView动画默认动画过程中不响应交互事件)

层次结构
b、CABasicAnimation 和 CAKeyframeAnimation
a、简介

都是CAPropertyAnimation的子类,通过描绘路径来形成动画。CABasicAnimation通过设定起始点,终点,时间,可以看成是只有两个点的特殊的CAKeyFrameAnimation动画。CAKeyFrameAnimation则可以设置路径为更多的点构成的路径,动画会沿着我们设置的多个点进行移动

常用属性
kCAMediaTimingFunctionLinear:匀速运动,默认函数,立即加速并且保持匀速到达终点的场景会有意义(例如射出枪膛的子弹)
kCAMediaTimingFunctionEaseIn:一个慢慢加速然后突然停止的方法。对于自由落体的例子来说很适合,或者对准一个目标的导弹的发射。
kCAMediaTimingFunctionEaseOut:以一个全速开始,然后慢慢减速停止 有一个削弱的效果,应用的场景比如一扇门慢慢地关上,而不是砰地一声
kCAMediaTimingFunctionEaseInEaseOut:慢慢加速然后再慢慢减速的过程 
kCAMediaTimingFunctionDefault
kCAFillModeForwards :为了使动画结束之后layer保持结束状态,应将removedOnCompletion设置为NO
kCAFillModeRemoved:动画将在设置的 beginTime 开始执行(如没有设置beginTime属性,则动画立即执行),动画执行完成后将会layer的改变恢复原状。
c、CATransition
d、CASpringAnimation

2、CABasicAnimation

a、CABasicAnimation的渐变加载Demo
加载
LoadingView.h
// view宽度必须使用此值
FOUNDATION_EXPORT const CGFloat LoadingViewSize;

// 全局loading
@interface LoadingView : UIView

/** 开始动画 */
- (void)startAnimating;

/** 结束动画 */
- (BOOL)isAnimating;

@end
LoadingView.m
const CGFloat LoadingViewSize = 200.0;// 加载视图的大小
const CGFloat LoadingLayerWidth = 80.0;// 加载图层的宽度

@interface LoadingView ()<CAAnimationDelegate>

// 是否正在动画
@property (nonatomic, assign, getter=isAnimating) BOOL animating;

// 用于显示加载效果的渐变图层
@property (nonatomic, strong) CAGradientLayer *loadingLayer;

@end

@implementation LoadingView

@end
绘图
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        self.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.5];
        self.layer.cornerRadius = 3.0;
        _animating = NO;// 未启动动画
 
        // 绘图
        [self drawLoadingLayer];
    }
    return self;
}

- (void)drawLoadingLayer
{
    CGSize size = self.bounds.size;
    CGPoint center = CGPointMake(size.width/2, size.height/2);// 圆心
    CGFloat radius = LoadingLayerWidth/2;// 半径
    CGFloat strokeWidth = 2.0;// 描边宽度
    CGFloat angleDelta = M_PI / 2;// 角度变化量

// 1.创建渐变图层
    // 初始化渐变图层
    _loadingLayer = [CAGradientLayer layer];
    _loadingLayer.frame = CGRectMake(center.x - LoadingLayerWidth/2, center.y - LoadingLayerWidth/2, LoadingLayerWidth, LoadingLayerWidth);
    [self.layer addSublayer:_loadingLayer];
    
    // 填充色不可用带透明度的颜色(会导致颜色叠加),要使用实色
    _loadingLayer.colors = @[(id)[UIColor whiteColor].CGColor, (id)[UIColor whiteColor].CGColor, (id)[UIColor redColor].CGColor];
    _loadingLayer.locations = @[@0.0, @0.5, @1.0];// 关键位置
    _loadingLayer.startPoint = CGPointMake(0.5, 0.0);// 开始点
    _loadingLayer.endPoint = CGPointMake(0.5, 1.0);// 结束点
    _loadingLayer.type = kCAGradientLayerAxial;// 线性渐变
    
// 2.创建圆圈形状
    // 创建外部弧
    center = CGPointMake(radius, radius);
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:angleDelta endAngle:angleDelta*4 clockwise:YES];
    
    // 添加右边弧
    CGFloat strokeRaduis = strokeWidth / 2;
    [maskPath addArcWithCenter:CGPointMake(center.x+radius-strokeRaduis, center.y) radius:strokeRaduis startAngle:0 endAngle:angleDelta*2 clockwise:YES];
    
    // 添加内部弧
    [maskPath addArcWithCenter:center radius:(radius-strokeWidth) startAngle:angleDelta*4 endAngle:angleDelta clockwise:NO];
    
    // 添加底边弧
    [maskPath addArcWithCenter:CGPointMake(center.x, center.y+radius-strokeRaduis) radius:strokeRaduis startAngle:-angleDelta endAngle:angleDelta clockwise:YES];
    
    // 完成路径绘制,闭合路径
    [maskPath closePath];// 注释掉好像也没影响
    
    // 添加路径到形状图层上,_loadingLayer没有path属性,只有mask属性
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = _loadingLayer.bounds;// 二者重合
    maskLayer.path = maskPath.CGPath;
    
    // 3.将形状图层作为渐变图层的遮罩,不加这句得到的只是个旋转的矩形
    _loadingLayer.mask = maskLayer;
}
配置动画
// 展示视图动画
- (void)showAnimation
{
    // 透明度动画
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    animation.fromValue = @(0.0);// 初始值
    animation.toValue = @(1.0);// 结束值
    animation.removedOnCompletion = NO;// 完成后不移除
    animation.duration = 0.3;// 持续0.3秒
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];// 渐渐退出
    // 添加到视图图层中
    [self.layer addAnimation:animation forKey:@"showAnimation"];
}

// 隐藏视图动画
- (void)hideAnimation
{
    // 透明度动画
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    animation.fromValue = @(1.0);// 初始值
    animation.toValue = @(0.0);// 结束值
    animation.removedOnCompletion = NO;// 完成后不移除
    animation.duration = 0.3;// 持续0.3秒
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];// 渐渐退出
    // 添加到视图图层中
    [self.layer addAnimation:animation forKey:@"hideAnimation"];
}

// 加载视图动画
- (void)loadingAnimating
{
    // 绕Z轴旋转动画
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    animation.fromValue = @(0);// 初始值
    animation.toValue = @(M_PI*2);// 结束值
    animation.removedOnCompletion = NO;// 完成后不移除
    animation.repeatCount = HUGE_VALF;// 重复无限次
    animation.duration = 1.0;// 持续1秒
    // 添加到加载图层中
    [_loadingLayer addAnimation:animation forKey:@"loadingAnimating"];
}
操作
// 开始动画
- (void)startAnimating
{
    // 已经在动画状态中则直接返回
    if (_animating)
    {
        return;
    }
    
    // 清除之前的旧动画
    [self cleanAnimations];
    
    // 设置为动画状态
    _animating = YES;
    // 不再隐藏视图
    self.hidden = NO;
    
    // 展示视图动画
    [self showAnimation];
    // 加载视图动画
    [self loadingAnimating];
}

// 停止动画
- (void)stopAnimating
{
    // 清除动画
    [self cleanAnimations];
    // 隐藏视图
    self.hidden = YES;
    // 设置为非动画状态
    _animating = NO;
}

// 清除动画
- (void)cleanAnimations
{
    [self.layer removeAnimationForKey:@"showAnimating"];// 移除显示动画
    [self.layer removeAnimationForKey:@"hideAnimating"];// 移除隐藏动画
    [_loadingLayer removeAnimationForKey:@"loadingAnimating"];// 移除加载动画
}
使用
// 渐变
- (void)createLoadingView
{
    // 创建视图
    self.loadingView = [[LoadingView alloc] initWithFrame:CGRectMake(100, 300, LoadingViewSize, LoadingViewSize)];
    [self.view addSubview:self.loadingView];
    
    // 开始动画
    [self.loadingView startAnimating];
}

3、CAKeyframeAnimation

a、8个球加载Demo
8个球
BallsLoadingView.h文件
FOUNDATION_EXPORT const CGFloat BallsLoadingLayerSize;

@interface BallsLoadingView : UIView

/** 开始动画 */
- (void)startAnimation;

/** 结束动画 */
- (void)stopAnimation;

@end
BallsLoadingView.m文件
const CGFloat BallsLoadingLayerSize = 100.0;// 加载图层的大小

@interface BallsLoadingView ()

@property (nonatomic, strong) CALayer *loadingLayer;// 加载图层

@end

@implementation BallsLoadingView

@end
绘图
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        [self drawInnerLoadingLayer];
    }
    return self;
}

// 加载视图为8个球
- (void)drawInnerLoadingLayer
{
    CGSize size = self.bounds.size;
    CGFloat radius = size.width/2;// 半径
    CGFloat dotRadius = 7.5 * (size.width/BallsLoadingLayerSize);// 球的半径
    CGFloat postionRadius = radius - dotRadius;// 距离
    CGFloat angleDelta = M_PI / 4;// 角度变化量
    
    // 1.创建加载图层
    _loadingLayer = [CALayer layer];
    _loadingLayer.frame = self.bounds;
    [self.layer addSublayer:_loadingLayer];
    
    
    // 2.绘制8个球
    UIColor *ballColor = [UIColor blueColor];// 球的颜色
    for (int i=7; i>=0; I--)
    {
        // 计算每个球的圆心
        CGFloat postionX = radius + postionRadius * cos(angleDelta*i);
        CGFloat postionY = radius + postionRadius * sin(angleDelta*i);
        
        // 绘制每个球的路径
        UIBezierPath *dotPath = [UIBezierPath bezierPath];
        [dotPath addArcWithCenter:CGPointZero radius:dotRadius startAngle:0 endAngle:M_PI*2 clockwise:YES];
        [dotPath closePath];
        
        // 创建形状图层
        CAShapeLayer *dotLayer = [CAShapeLayer layer];
        dotLayer.position = CGPointMake(postionX, postionY);// 球的放置位置
        dotLayer.anchorPoint = CGPointZero;// 球的锚点
        dotLayer.path = dotPath.CGPath;// 绘制路径
        dotLayer.fillColor = ballColor.CGColor;// 填充颜色
        
        // 将形状图层添加到加载图层上
        [_loadingLayer addSublayer:dotLayer];
    }
}
配置动画
// 小球依次缩放和透明
- (void)configureAnimation
{
    // 获取加载图层中的每个球图层
    NSArray<CALayer *> *sublayers = _loadingLayer.sublayers;
    // 球的数量
    NSUInteger count = sublayers.count;
    // 依次延迟0.12秒展示
    CGFloat delayDelta = 0.12;
    
    // 依次为每个球添加动画
    for (int i=0; i<count; I++)
    {
        // 获取每个球的图层
        CALayer *subLayer = sublayers[I];
        
        NSArray *scales = @[@1.0, @0.4, @1.0];// 缩放 1->0.4->1
        NSArray *opacities = @[@1.0, @0.3, @1.0];// 透明度 1->0.3->1
        NSArray *keyTimes = @[@0, @0.5, @1.0];// 关键时刻 0->0.5->1
        CGFloat duration = 2.0;// 时长
        
        // xScale: X轴上的缩放动画
        CAKeyframeAnimation *xScaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.x"];
        xScaleAnimation.values = scales;
        xScaleAnimation.keyTimes = keyTimes;
        xScaleAnimation.duration = duration;
        
        // yScale: Y轴上的缩放动画
        CAKeyframeAnimation *yScaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.y"];
        yScaleAnimation.values = scales;
        yScaleAnimation.keyTimes = keyTimes;
        yScaleAnimation.duration = duration;
        
        // opacity: 透明度动画
        CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
        opacityAnimation.values = opacities;
        opacityAnimation.keyTimes = keyTimes;
        opacityAnimation.duration = duration;
        
        // 组合动画
        CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
        groupAnimation.animations = @[xScaleAnimation, yScaleAnimation, opacityAnimation];
        groupAnimation.duration = duration;
        groupAnimation.removedOnCompletion = NO;// 完成后不移除动画
        groupAnimation.repeatCount = HUGE_VALF;// 无限次重复
        groupAnimation.beginTime = delayDelta * i;// 依次延迟0.12秒展示
        
        // 将组合动画添加到球图层上
        [subLayer addAnimation:groupAnimation forKey:@"groupAnimation"];
    }
}
操作
// 开始动画
- (void)startAnimation
{
    [self configureAnimation];
}

// 结束动画
- (void)stopAnimation
{
    // 获取加载图层中的每个球图层
    NSArray<CALayer *> *sublayers = _loadingLayer.sublayers;
    NSUInteger count = sublayers.count;
    
    for (int i=0; i<count; I++)
    {
        // 获取每个球的图层
        CALayer *subLayer = sublayers[I];
        // 移除球图层上的所有动画
        [subLayer removeAllAnimations];
    }
}
使用
// 8个球
- (void)createBallsLoadingView
{
    // 创建视图
    self.ballsLoadingView = [[BallsLoadingView alloc] initWithFrame:CGRectMake(150, 300, BallsLoadingLayerSize, BallsLoadingLayerSize)];
    [self.view addSubview:self.ballsLoadingView];
    
    // 开始动画
    [self.ballsLoadingView startAnimation];
}

4、CATransition

a、过渡到蓝色视图
过渡到蓝色视图
TransitionViewController
// 过渡动画视图
@interface TransitionViewController : UIViewController

@property (nonatomic, strong) UIView *blueView;

@end

@implementation TransitionViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.blueView = [[UIView alloc] initWithFrame:CGRectZero];
    self.blueView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:self.blueView];
    [self.blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
        make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom);
        make.left.equalTo(self.view.mas_left);
        make.right.equalTo(self.view.mas_right);
    }];
}

@end
CoreAnimationViewController
// 过渡动画
- (void)transitionAnimation
{
    TransitionViewController *transitonVC = [[TransitionViewController alloc] init];
    [self.navigationController pushViewController:transitonVC animated:NO];
    
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionPush;// Push方式
    transition.subtype = kCATransitionFromLeft;// 从左部滑出
    // 在图层上添加动画
    [transitonVC.view.layer addAnimation:transition forKey:@"transition"];
}

5、CASpringAnimation

6、CAAnimationGroup

a、波纹动画
波纹动画
RippleLayer.h文件
#import <QuartzCore/QuartzCore.h>

@interface RippleLayer : CALayer

/** 开始动画 */
- (void)startAnimation;

/** 结束动画 */
- (void)stopAnimation;

@end
RippleLayer.m文件
#import <FLAnimatedImage/FLAnimatedImage.h>

@interface RippleLayer ()<CAAnimationDelegate>// 动画委托

// 计时器
@property (nonatomic, strong) NSTimer *timer;

@end

@implementation RippleLayer

@end
创建波纹图
- (CAShapeLayer *)rippleLayer
{
    // 路径
    CGSize size = self.bounds.size;
    CGFloat raduis = size.width/2;// 半径
    CGPoint center = CGPointMake(size.width/2, size.height/2);// 圆心
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:raduis startAngle:0 endAngle:M_PI*2 clockwise:YES];// 圆
    [path closePath];// 闭合
    
    UIColor *strokeColor = [UIColor colorWithRed:189.0/255 green:141.0/255 blue:4.0/255 alpha:0.45];// 描边颜色
    UIColor *fillColor = [UIColor colorWithRed:189.0/255 green:141.0/255 blue:4.0/255 alpha:0.08];// 填充颜色
    
    // 图层
    CAShapeLayer *layer = [CAShapeLayer layer];
    layer.path = path.CGPath;
    layer.lineWidth = 0.5;
    layer.strokeColor = strokeColor.CGColor;
    layer.fillColor = fillColor.CGColor;
    
    // 勿改变设置顺序
    layer.anchorPoint = CGPointMake(0.5, 0.5);// 铆点 0~1 这里指视图中心
    layer.frame = self.bounds;
    layer.transform = CATransform3DMakeScale(1.5, 1.5, 1.0);// 缩放
    layer.opacity = 0.0;// 透明
    
    return layer;
}
配置波纹动画:透明度 + 缩放
- (void)fireRippleAnimation
{
    // 添加轮胎图层
    CAShapeLayer *layer = [self rippleLayer];
    [self addSublayer:layer];
    
// scalePath
    UIBezierPath *scalePath = [UIBezierPath bezierPath];
    [scalePath moveToPoint:CGPointMake(1.5, 0)];
    [scalePath addQuadCurveToPoint:CGPointMake(1.8, 1.0) controlPoint:CGPointMake(1.2, 0.2)];
    [scalePath addQuadCurveToPoint:CGPointMake(3.0, 3.0) controlPoint:CGPointMake(3.0, 2.5)];
    
// opacityPath
    UIBezierPath *opacityPath = [UIBezierPath bezierPath];
    [opacityPath moveToPoint:CGPointZero];
    [opacityPath addLineToPoint:CGPointMake(0.8, 0.2)];
    [opacityPath addLineToPoint:CGPointMake(0.0, 2.5)];
    [opacityPath addLineToPoint:CGPointMake(0.0, 3.0)];
    
    CFTimeInterval duration = 3.0;
    
// ScaleAnimation
    CAKeyframeAnimation *xScaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.x"];
    xScaleAnimation.calculationMode = kCAAnimationPaced;
    xScaleAnimation.path = scalePath.CGPath;
    xScaleAnimation.duration = duration;
    
    CAKeyframeAnimation *yScaleAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.y"];
    yScaleAnimation.calculationMode = kCAAnimationPaced;
    yScaleAnimation.path = scalePath.CGPath;
    yScaleAnimation.duration = duration;
    
// OpacityAnimation
    CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
    opacityAnimation.calculationMode = kCAAnimationPaced;
    opacityAnimation.path = opacityPath.CGPath;
    opacityAnimation.duration = duration;
    
// GroupAnimation
    CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
    groupAnimation.animations = @[xScaleAnimation,yScaleAnimation,opacityAnimation];
    groupAnimation.duration = duration;
    groupAnimation.delegate = self;
    groupAnimation.removedOnCompletion = NO;
    
    // 给图层添加动画
    [layer addAnimation:groupAnimation forKey:@"groupAnimation"];
}
操作
// 开始动画
- (void)startAnimation
{
    // 已经在计时则直接返回
    if (_timer)
    {
        return;
    }
    
    // 代理机制防止计时器的循环引用
    FLWeakProxy *target = [FLWeakProxy weakProxyForObject:self];
    _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:target selector:@selector(fireRippleAnimation) userInfo:nil repeats:YES];
}

// 结束动画
- (void)stopAnimation
{
    [_timer invalidate];
    _timer = nil;
}

// 动画停止时调用
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
{
    // 删除最低部的layer
    CALayer *layer = [self.sublayers firstObject];
    [layer removeFromSuperlayer];
}
使用
- (void)createRippleLayerView
{
    // 创建View
    CGSize viewSize = self.view.bounds.size;
    UIView *rippleView = [[UIView alloc] initWithFrame:CGRectMake(viewSize.width/2, 300, 100, 100)];
    
    // 创建Layer
    self.rippleLayer = [RippleLayer layer];
    self.rippleLayer.frame = rippleView.bounds;
    
    // 将Layer添加到View
    [rippleView.layer addSublayer:self.rippleLayer];
    // 添加View
    [self.view addSubview:rippleView];
    
    // 启动动画
    [self.rippleLayer startAnimation];
}

7、CADisplayLink

a、下拉加载动画
下拉加载动画
RefreshView.h
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <CoreGraphics/CoreGraphics.h>

FOUNDATION_EXPORT const CGFloat PullLoadingViewSize;

@interface RefreshView : UIView

/** 关联滑动视图 */
@property (weak, nonatomic) UIScrollView *scrollView;

/** 拉多少距离转一周 */
@property (nonatomic, assign) CGFloat distanceForTurnOneCycle;

/** 下拉加载 */
- (void)loading;

/** 当取消或者加载完成的时机会调用这个方法 */
- (void)loadingFinished;

@end
RefreshView.m
const CGFloat PullLoadingViewSize = 30.0;

@interface RefreshView ()

@property (nonatomic, strong) CALayer *wheelLayer;
@property (nonatomic, strong) NSArray *windLayers;
@property (nonatomic, strong) NSArray *windOffsets;

@property (nonatomic, strong) CADisplayLink *displayLink;

@end

@implementation RefreshView

@end
绘制视图
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        [self drawRefreshView];
    }
    return self;
}

// 绘制加载风车视图
- (void)drawRefreshView
{
    // 下拉80点转动一周
    _distanceForTurnOneCycle = 80;
    // 类似计时器,但每一帧都会调用
    _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayDidRefresh)];
    // 放到NSRunLoopCommonModes,防止下拉切换mode导致计时器失效
    [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    

    CGFloat halfWidth = self.bounds.size.width / 2;// 视图一半
    CGPoint center = CGPointMake(halfWidth, halfWidth);// 轮胎中心点
    
    // 创建轮胎视图
    _wheelLayer = [CALayer layer];
    _wheelLayer.frame = self.bounds;
    [self.layer addSublayer:_wheelLayer];
    
//拆分为两部分,轮胎使用描边,扇叶使用填充
    //轮胎描边
    UIBezierPath *wheelMaskPath = [UIBezierPath bezierPathWithArcCenter:center radius:halfWidth startAngle:0 endAngle:M_PI*2 clockwise:YES];// 路径为外圆
    CAShapeLayer *wheelMask = [CAShapeLayer layer];// 外层轮胎图层
    wheelMask.frame = self.bounds;
    wheelMask.path = wheelMaskPath.CGPath;// 外层轮胎图层上的路径
    
    CGFloat tyreRadius = 23/2 - 1.5;// 内层轮胎半径
    UIColor *tyreColor = [UIColor redColor];// 内层轮胎颜色为红色
    CAShapeLayer *tyreLayer = [CAShapeLayer layer];// 内层轮胎图层
    UIBezierPath *tyrePath =[UIBezierPath bezierPathWithArcCenter:center radius:tyreRadius startAngle:0 endAngle:M_PI * 2 clockwise:YES];// 路径为内圆
    [tyrePath closePath];// 关闭路径
    tyreLayer.path = tyrePath.CGPath;// 内层轮胎图层上的路径
    tyreLayer.strokeColor = tyreColor.CGColor;// 描边颜色
    tyreLayer.lineWidth = 3;// 描边宽度
    tyreLayer.fillColor = [UIColor clearColor].CGColor;// 填充颜色
    tyreLayer.frame = self.bounds;
    tyreLayer.mask = wheelMask;// 遮罩
    [_wheelLayer addSublayer:tyreLayer];// 将形状图层添加到轮胎图层上
    
    // 扇叶
    CGFloat innerRadius = 7/2;
    CGFloat outerRadius = 15/2;
    CGFloat angleDelta = M_PI * 2 / 5;// 转动角度
    CGFloat arcAngle = angleDelta * 2 / 3;// 圆弧角度
    
    UIBezierPath *maskPath = [UIBezierPath bezierPath];// 遮罩路径
    for (int i=0; i<5; I++)
    {
        CGFloat startAngle = angleDelta * i;// 开始角度
        CGFloat endAngle = startAngle + arcAngle;// 开始角度 + 弧度 = 结束角度
        CGPoint innerArcStart = [self calcPointWithAngle:endAngle radius:innerRadius center:center];// 计算开始点
        CGPoint outerArcStart = [self calcPointWithAngle:startAngle radius:outerRadius center:center];// 计算结束点
        
        // 绘制路径
        UIBezierPath *path = [UIBezierPath bezierPath];
        // 外部弧
        [path addArcWithCenter:center radius:outerRadius startAngle:startAngle endAngle:endAngle clockwise:YES];
        // 连线
        [path addLineToPoint:innerArcStart];
        // 内部弧
        [path addArcWithCenter:center radius:innerRadius startAngle:endAngle endAngle:startAngle clockwise:NO];
        // 连线
        [path addLineToPoint:outerArcStart];
        // 闭合
        [path closePath];
        [maskPath appendPath:path];
    }
    
    // 内部环
    CGFloat dotRadius = 3.0 / 2;// 点半径
    UIBezierPath *dotPath = [UIBezierPath bezierPathWithArcCenter:center radius:dotRadius startAngle:0 endAngle:M_PI*2 clockwise:YES];// 画圆
    [dotPath closePath];// 闭合
    [maskPath appendPath:dotPath];// 添加到遮罩路径
    
    // 遮罩图层
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = self.bounds;
    maskLayer.path = maskPath.CGPath;
    
    UIColor *fanBladeColor = [UIColor redColor];// 风扇颜色
    CALayer *fanBladeLayer = [CALayer layer];// 风扇图层
    fanBladeLayer.backgroundColor = fanBladeColor.CGColor;// 背景颜色为红色
    fanBladeLayer.frame = self.bounds;
    fanBladeLayer.mask = maskLayer;// 遮罩图层
    // 也可将fanBladeLayer添加到tyreLayer中
    [tyreLayer addSublayer:fanBladeLayer];
    
// 风
    UIColor *windColor = [UIColor redColor];// 风的颜色为红色
    NSMutableArray *windLayers = [NSMutableArray array];
    for (int i=0; i<3; I++)
    {
        // 创建风图层
        CAShapeLayer *layer = [CAShapeLayer layer];// 图层
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(0, 0)];
        [path addLineToPoint:CGPointMake(6, 0)];// 绘制线条
        layer.path = path.CGPath;// 图层的线条
        layer.strokeColor = windColor.CGColor;// 描边颜色为风的颜色
        layer.lineWidth = 1.0;// 描边宽度
        layer.position = [self resetWindLayerPositionWithIndex:i];// 根据index计算风视图的开始位置
        layer.opacity = 0.0;// 刚开始透明
        [self.layer addSublayer:layer];// 添加风图层
        [windLayers addObject:layer];// 放到风图层的集合中
    }
    _windLayers = windLayers;
    _windOffsets = @[@18, @23, @16];// 根据index计算风视图的X轴上的坐标偏移量
}
让视图动起来
// 轮胎动画
- (void)wheelAnimation
{
    // 设置Z轴上的旋转动画
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    animation.fromValue = @(M_PI*2);
    animation.toValue = @(0);// 转动一圈
    animation.removedOnCompletion = NO;// 完成后不移除
    animation.repeatCount = HUGE_VALF;// 重复
    animation.duration = 1.0;// 1秒
    [_wheelLayer addAnimation:animation forKey:@"loading"];// 作为加载动画
}

// 吹风动画
- (void)windAnimation
{
    // 为3个风图层分别添加动画
    for (int i=0; i<3; I++)
    {
        [self addAnimationToWindLayerForIndex:i];
    }
}

// 在风视图上添加动画
- (void)addAnimationToWindLayerForIndex:(NSInteger)index
{
    CGFloat duration = 1.5;// 1.5秒

// 移动动画
    // 根据index计算风视图的开始位置
    CGPoint startPosition = [self resetWindLayerPositionWithIndex:index];
    // 根据index计算风视图的X轴上的坐标偏移量
    CGFloat offsetX = [_windOffsets[index] floatValue];
    // 风视图的结束位置
    CGPoint endPosition = CGPointMake(startPosition.x+offsetX, startPosition.y);
    
    // 设置关键位置:开始->结束->开始
    NSArray *positions = @[[NSValue valueWithCGPoint:startPosition],
                           [NSValue valueWithCGPoint:endPosition],
                           [NSValue valueWithCGPoint:endPosition],
                           [NSValue valueWithCGPoint:startPosition]];
    // 设置时间点
    NSArray *positionKeyTimes = @[@0, @(1.0/duration), @(1.3/duration), @1.0];
    // 设置动画效果
    NSArray *positionFunctions = @[[CAMediaTimingFunction functionWithName:
                                   kCAMediaTimingFunctionEaseOut],
                                  [CAMediaTimingFunction functionWithName:
                                   kCAMediaTimingFunctionLinear],
                                  [CAMediaTimingFunction functionWithName:
                                   kCAMediaTimingFunctionLinear]];
    
// 透明度动画
    // 设置关键点的透明度
    NSArray *opacities = @[@0.0, @1.0, @1.0, @0.0, @0.0];
    // 设置时间点
    NSArray *opacityKeyTimes = @[@0, @(0.3/duration), @(1.0/duration), @(1.3/duration), @1.0];
    // 设置动画效果
    NSArray *opacityFunctions = @[[CAMediaTimingFunction functionWithName:
                                kCAMediaTimingFunctionEaseOut],
                               [CAMediaTimingFunction functionWithName:
                                kCAMediaTimingFunctionLinear],
                               [CAMediaTimingFunction functionWithName:
                                kCAMediaTimingFunctionEaseOut],
                               [CAMediaTimingFunction functionWithName:
                                kCAMediaTimingFunctionLinear]];
    
    // 根据index获取创建好的风图层,并将其本地化
    CALayer *windLayer = _windLayers[index];
    
    // 创建透明度动画
    CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
    opacityAnimation.values = opacities;
    opacityAnimation.keyTimes = opacityKeyTimes;
    opacityAnimation.timingFunctions = opacityFunctions;
    
    // 创建位置动画
    CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    positionAnimation.values = positions;
    positionAnimation.keyTimes = positionKeyTimes;
    positionAnimation.timingFunctions = positionFunctions;
    
    // 创建组合动画
    CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
    groupAnimation.animations = @[opacityAnimation, positionAnimation];
    groupAnimation.duration = duration;
    groupAnimation.repeatCount = HUGE_VALF;// 重复无限次
    
    // 将组合动画作为风动画添加到风图层上
    [windLayer addAnimation:groupAnimation forKey:@"wind"];
}

// 根据index计算风视图的开始位置
- (CGPoint)resetWindLayerPositionWithIndex:(NSInteger)index
{
    CGFloat positionX = self.bounds.size.width/2 + 23/2 - 3;
    CGFloat positionYDelta = 5;
    CGFloat positionYStart = self.bounds.size.width/2 - positionYDelta;
    return CGPointMake(positionX, positionYStart + positionYDelta*index);
}

- (CGPoint)calcPointWithAngle:(CGFloat)angle radius:(CGFloat)radius center:(CGPoint)center
{
    // 正弦和余弦函数计算做坐标点
    CGFloat postionX = center.x + radius * cos(angle);
    CGFloat postionY = center.y + radius * sin(angle);
    return CGPointMake(postionX, postionY);
}
和scrollView联动
// 滚动视图存在则实现下拉加载联动效果
- (void)displayDidRefresh
{
    if (_scrollView)
    {
        // 总下拉距离
        CGFloat distance = _scrollView.contentOffset.y;
        // 2PI *(总下拉距离 / 80)= 转动几圈
        CGFloat angle = - (M_PI * 2 * distance / _distanceForTurnOneCycle);
        // 仿射变换实现旋转效果
        CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
        // 在轮胎图层上添加旋转动画
        [_wheelLayer setAffineTransform:transform];
    }
}
操作方法
// 下拉加载,开始风和轮胎动画
- (void)loading
{
    // 停止关联
    _displayLink.paused = YES;
    [self wheelAnimation];
    [self windAnimation];
}

// 当取消或者加载完成的时机会调用这个方法
- (void)loadingFinished
{
    // 启动关联
    _displayLink.paused = NO;
    // 移除加载动画
    [_wheelLayer removeAnimationForKey:@"loading"];
    for (CALayer *layer in _windLayers)
    {
        [layer removeAllAnimations];
    }
}

- (void)dealloc
{
    [_displayLink invalidate];
}
使用
@interface RefreshViewController() <UIScrollViewDelegate>

@property(nonatomic, strong) RefreshView *refreshView;

@end

@implementation RefreshViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(10, 500, 200, 300)];
    scrollView.contentSize = CGSizeMake(200, 600);
    scrollView.backgroundColor = [UIColor yellowColor];
    scrollView.delegate = self;
    [self.view addSubview:scrollView];
    
    RefreshView *refreshView = [[RefreshView alloc] initWithFrame:CGRectMake(100, 100, PullLoadingViewSize, PullLoadingViewSize)];
    refreshView.scrollView = scrollView;
    [self.view addSubview:refreshView];
    self.refreshView = refreshView;
}

// 下拉开始的时候加载
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    [self.refreshView loading];
}

// 下拉减速的时候结束加载
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
    [self.refreshView loadingFinished];
}
 
@end

Demo

Demo在我的Github上,欢迎下载。
AnimationDemo

参考文献

上一篇下一篇

猜你喜欢

热点阅读