Castie!

正态分布, 优劣伴生

北冥有鱼,其名为鲲(kūn)。鲲之大,不知其几千里也;化而为鸟,其名为鹏。鹏之背,不知其几千里也;怒而飞,其翼若垂天之云。是鸟也,海运则将徙于南冥。南冥者,天池也。


北海若曰:“井鼃不可以语于海者,拘于虚也;夏虫不可以语于冰者,笃于时也;曲士不可以语于道者,束于教也。今尔出于崖涘,观于大海,乃知尔丑,尔将可与语大理矣。

iOS 核心动画的应用及内存泄漏

这一周本来是想要再看下SceneKit方面的内容的, 不过接到一个引导页动画的需求, 对于好久不敲OC代码的我, 其实内心是很抵触的, 看了设计给的用AE的动画效果, 诶… 一个引导页搞那么复杂, 也不看看BAT三巨头和TMD独角兽的引导页不都是简单的图片咩…

动画分析

作为技术, 有需求就一定要实现, 其实很想拒绝这种伪需求的, 但是怎么办呢, 怼回去显得技术能力差似的, 好吧, 我们来看看设计给的一些素材来进一步分析实现呗…

AE 渲染动画

好吧, 我们来分析一下如何分析这个动画吧, 动画一共有14个元素, 主要分为五步动画, 1. 圆形背景弹出动画, 2. 手机向上位移动画, 3, 手指点触动画, 4, 四个圆形弹出动画, 5 文字弹出, 光效位移, 圆形无规则旋转动画. 然后设计抛出一张牛逼闪闪的帧率图…

AE 帧率图

什么鬼, 就给些资源, 不告诉我控件摆放的位置, 不告诉我动画持续的时间, 这就把我打发了, 图片还是给我1080P的, 你是做的爽了, 我还要做屏幕适配呢, 感觉是在逗我呀. 算了, 我忍, 还是想想技术选型吧…

iOS动画方面可以选择UIView的动画, Core Animation核心动画, iOS10 属性动画, 因为是公司项目, 所有iOS10新的API肯定是不能用的, UIView的动画做不了手机弹出的那个蒙层效果, 接下来能够选的就只有Core Animation核心动画了, 核心动画是通过操作图层来实现动画, 但所有的动画都是假象, 假象就假象吧, 能实现就好呗… 核心动画就决定是你了!!

动画布局

制作动画效果, 我们肯定是需要将所有的元素都先放置在界面上, 先做屏幕适配为妙.

@property (nonatomic,strong) UIImageView * backgroundView; //圆形的背景图
@property (nonatomic,strong) UIImageView * phoneView; //手机
@property (nonatomic,strong) CALayer * maskLayer; //蒙层
@property (nonatomic,strong) CALayer * phoneLayer; //手机层
@property (nonatomic,strong) UIImageView * handView; //手指
@property (nonatomic,strong) UIImageView * homeView; //家
@property (nonatomic,strong) UIImageView * topIcon; //上面的圆
@property (nonatomic,strong) UIImageView * leftIcon; //左边的圆
@property (nonatomic,strong) UIImageView * bottomIcon; //下面的圆
@property (nonatomic,strong) UIImageView * rightIcon; //右面的圆
@property (nonatomic,strong) UIImageView * coverView; //光效
@property (nonatomic,strong) UIImageView * charView1; //文字1
@property (nonatomic,strong) UIImageView * charView2; //文字2
@property (nonatomic,strong) UIImageView * lineView1; //线1
@property (nonatomic,strong) UIImageView * lineView2; //线2

所有的控件都通过懒加载创建, 篇幅原因省略…

- (void)setupSubviews {
    [self addSubview:self.backgroundView];
    [self addSubview:self.coverView];
    [self.backgroundView addSubview:self.phoneView];
    [self.phoneView.layer addSublayer:self.phoneLayer]; //将手机展示层添加入View
    [self addSubview:self.handView];
    [self addSubview:self.homeView];
    [self addSubview:self.topIcon];
    [self addSubview:self.leftIcon];
    [self addSubview:self.bottomIcon];
    [self addSubview:self.rightIcon];
    [self addSubview:self.charView1];
    [self addSubview:self.charView2];
    [self addSubview:self.lineView1];
    [self addSubview:self.lineView2];
    
    UIButton * btn = [UIButton buttonWithType:UIButtonTypeContactAdd]; //创建一个测试的动画执行按钮
    btn.frame = CGRectMake(0, 100, 100, 100);
    [btn addTarget:self action:@selector(loadAnimations) forControlEvents:64];
    [self addSubview:btn];
}

通过layoutSubviews布局子控件, 由于篇幅原因, 仅展示部分代码

- (void)layoutSubviews {
    [super layoutSubviews];
    
    CGFloat width = self.width * 0.75; // 由于设计不给标尺寸, 我们就根据屏幕的宽度比来确定对照比例.
    CGFloat height = width * 1.10704961; //然后通过计算机算出比例 .... 真是醉了....
    CGFloat length = width * 0.82114883;
    
    _backgroundView.bounds = CGRectMake(0, 0, width, height);
    _backgroundView.center = (CGPoint){self.center.x, self.center.y - 20};
    
    CGFloat coverViewW = width * 1.06396867;
    CGFloat coverViewH = coverViewW * 1.00736196;
    _coverView.bounds = CGRectMake(0, 0, coverViewW, coverViewH);
    _coverView.center = _backgroundView.center;
    
    CGFloat phoneViewW = length * 0.68203498;
    CGFloat phoneViewH = phoneViewW * 1.44988345;
    CGFloat phoneViewX = (width - phoneViewW) * 0.5;
    CGFloat phoneViewY = (height - phoneViewH) * 0.5;
    _phoneView.frame = CGRectMake(phoneViewX, phoneViewY, phoneViewW, phoneViewH);
    
    CGFloat phoneLayerX = 0;
    CGFloat phoneLayerY = phoneViewH;
    CGFloat phoneLayerW = phoneViewW;
    CGFloat phoneLayerH = phoneViewH;
    _phoneLayer.frame = CGRectMake(phoneLayerX, phoneLayerY, phoneLayerW, phoneLayerH);

    CGFloat maskLayerW = length;
    CGFloat maskLayerH = length;
    CGFloat maskLayerX = (phoneViewW - maskLayerW) * 0.5;
    CGFloat maskLayerY = (phoneViewH - maskLayerH) * 0.5;
    _maskLayer.frame = CGRectMake(maskLayerX, maskLayerY, maskLayerW, maskLayerH);
    ...
}

以上代码均可以通过之前写的代码生成器进行生成 –> 传送门

动画换算

角度和弧度的换算以及帧数和描述的换算. 由于AE导出显示为1秒25帧, 可得出一帧是0.04秒.

#define SQAngle(x) ((x) / 180.0f * M_PI)
#define SQFrame(x) ((x) * 0.04)

动画封装

1.手机弹出的时候下面一半动画显示的效果, 对于没看过Layer层API的同学肯定是感觉很难实现的

- (UIImageView *)phoneView {
    
    if (!_phoneView) {
        _phoneView = [UIImageView new];
        _phoneView.layer.mask = self.maskLayer; //关键点, 将蒙版层赋值给蒙版属性. (alpha 通道 自行百度)
    }
    return _phoneView;
}

- (CALayer *)phoneLayer {
    
    if (!_phoneLayer) {
        _phoneLayer = [CALayer new];
        _phoneLayer.contents = (__bridge id)[UIImage imageNamed:@"手机"].CGImage; //寄宿图方式渲染图层
    }
    return _phoneLayer;
}

- (CALayer *)maskLayer {
    
    if (!_maskLayer) {
        _maskLayer = [CALayer new];
        _maskLayer.contents = (__bridge id)[UIImage imageNamed:@"圆-黑"].CGImage;
    }
    return _maskLayer;
}

这样phoneView的层次结构就变成了 phoneView > phoneLayer / maskLayer, 通过操纵phoneLayer, 就能够实现这个动画效果了.而maskLayer是固定Alpha全黑蒙版, 进行图像的穿透和遮蔽.

2.位移动画的封装

- (CABasicAnimation *)translationWithText:(UIView *)text {

    CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"position.y"];
    animation.fromValue = @(text.center.y + kscaleDeviceLength(50));
    animation.toValue = @(text.center.y);
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    return animation;
}

- (CABasicAnimation *)translationWithIcon:(UIView *)icon {

    CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(CGRectGetMidX(_homeView.frame), CGRectGetMaxY(_homeView.frame))];
    animation.toValue = [NSValue valueWithCGPoint:icon.center];
    return  animation;
}

3.旋转动画的封装

- (CAKeyframeAnimation *)rotateWithIcon:(UIView *)icon clockwise:(BOOL)clockwise  {

    UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(icon.center.x, icon.center.y) radius:(arc4random()%5) + 1 startAngle:SQAngle(0) endAngle:SQAngle(360) clockwise:clockwise]; 随机生成路径
    CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    animation.duration = 1.0;
    animation.path = path.CGPath; //根据路径生成动画
    animation.repeatCount = MAXFLOAT; //永久动画
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    return animation;
}

4.渐变及缩放动画的封装

- (CABasicAnimation *)opacityAnimation {

    CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    animation.fromValue = @(0);
    animation.toValue = @(1);
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    return animation;
}

- (CABasicAnimation *)scaleAnimation {
    
    CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    animation.fromValue = @(0);
    animation.toValue = @(1);
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    return animation;
}

5.组合动画 将多种动画进行组合封装

- (CAAnimationGroup *)groupAnimation:(CABasicAnimation *)animation {
    
    CAAnimationGroup * group = [CAAnimationGroup animation];
    group.fillMode = kCAFillModeForwards;
    group.removedOnCompletion = NO;
    group.duration = SQFrame(10);
    group.delegate = self;
    group.animations = @[[self opacityAnimation], animation, [self scaleAnimation]];
    return group;
}

动画制作

1.圆形背景弹出动画

    CAKeyframeAnimation * backgroundViewAnim = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"]; //关键帧动画
    backgroundViewAnim.values = @[@0.3, @1.2, @1.0];
    backgroundViewAnim.keyTimes = @[@0, @0.6, @1.0];
    backgroundViewAnim.fillMode = kCAFillModeForwards; //需要保留后一帧
    backgroundViewAnim.removedOnCompletion = NO; //完成后不移除动画
    backgroundViewAnim.duration = SQFrame(6); //6帧
    backgroundViewAnim.delegate = self;
    [_backgroundView.layer addAnimation:backgroundViewAnim forKey:@"animation1"];
    
    CABasicAnimation * lineViewAnim = [self scaleAnimation];
    lineViewAnim.duration = SQFrame(6);
    [_lineView1.layer addAnimation:lineViewAnim forKey:nil];
    [_lineView2.layer addAnimation:lineViewAnim forKey:nil];

2.手机向上位移动画

    if ([_backgroundView.layer animationForKey:@"animation1"] == anim) {
        
        CABasicAnimation * phoneLayerAnim = [CABasicAnimation animationWithKeyPath:@"position.y"];
        phoneLayerAnim.fromValue = @(_phoneView.height + kscaleDeviceLength(180));
        phoneLayerAnim.toValue = @(_phoneView.height - kscaleDeviceLength(70));
        phoneLayerAnim.fillMode = kCAFillModeForwards;
        phoneLayerAnim.removedOnCompletion = NO;
        phoneLayerAnim.duration = SQFrame(10);
        phoneLayerAnim.delegate = self;
        [_phoneLayer addAnimation:phoneLayerAnim forKey:@"animation2"];
    }

3.手指点触动画

    if ([_phoneLayer animationForKey:@"animation2"] == anim) {
        
        CABasicAnimation * translation = [CABasicAnimation animationWithKeyPath:@"position"];
        translation.fromValue = [NSValue valueWithCGPoint:CGPointMake(_backgroundView.center.x + kscaleDeviceLength(50), _backgroundView.center.y + kscaleDeviceLength(60))];
        translation.toValue = [NSValue valueWithCGPoint:CGPointMake(_backgroundView.center.x, _backgroundView.center.y + kscaleDeviceLength(30))];
        
        CAAnimationGroup * group = [CAAnimationGroup animation]; //动画组
        group.fillMode = kCAFillModeForwards;
        group.removedOnCompletion = NO;
        group.duration = SQFrame(19);
        group.animations = @[translation, [self opacityAnimation]];
        group.delegate = self;
        [_handView.layer addAnimation:group forKey:@"animation3"];
    }

4.四个圆形弹出动画

    if ([_handView.layer animationForKey:@"animation3"] == anim) {
        
        CABasicAnimation * animation = [self opacityAnimation];
        animation.duration = SQFrame(5);
        [_homeView.layer addAnimation:animation forKey:nil];
        
        [_topIcon.layer addAnimation:[self groupAnimation:[self translationWithIcon:_topIcon]] forKey:@"animation4"];
        [_rightIcon.layer addAnimation:[self groupAnimation:[self translationWithIcon:_rightIcon]] forKey:nil];
        [_bottomIcon.layer addAnimation:[self groupAnimation:[self translationWithIcon:_bottomIcon]] forKey:nil];
        [_leftIcon.layer addAnimation:[self groupAnimation:[self translationWithIcon:_leftIcon]] forKey:nil];
    }

5.文字弹出, 光效位移, 圆形无规则旋转动画

 if ([_topIcon.layer animationForKey:@"animation4"] == anim) {

        [_topIcon.layer addAnimation:[self rotateWithIcon:_topIcon clockwise:YES] forKey:nil];
        [_rightIcon.layer addAnimation:[self rotateWithIcon:_rightIcon clockwise:NO] forKey:nil];
        [_bottomIcon.layer addAnimation:[self rotateWithIcon:_bottomIcon clockwise:YES] forKey:nil];
        [_leftIcon.layer addAnimation:[self rotateWithIcon:_leftIcon clockwise:NO] forKey:nil];
        
        CABasicAnimation * opacity = [self opacityAnimation];
        opacity.duration = SQFrame(5);
        
        CABasicAnimation * opacity2 = [self opacityAnimation];
        opacity2.beginTime = SQFrame(5);
        opacity2.duration = SQFrame(5);
        
        CABasicAnimation * charView1Anim = [self translationWithText:_charView1];
        charView1Anim.duration = SQFrame(10);
        
        CABasicAnimation * charView2Anim = [self translationWithText:_charView2];
        charView2Anim.beginTime = SQFrame(5);
        charView2Anim.duration = SQFrame(10);
        
        CAAnimationGroup * group = [CAAnimationGroup animation];
        group.fillMode = kCAFillModeForwards;
        group.removedOnCompletion = NO;
        group.duration = SQFrame(15);
        group.animations = @[opacity, charView1Anim];
        
        CAAnimationGroup * group2 = [CAAnimationGroup animation];
        group2.fillMode = kCAFillModeForwards;
        group2.removedOnCompletion = NO;
        group2.duration = SQFrame(15);
        group2.animations = @[opacity2, charView2Anim];
        
        [_charView1.layer addAnimation:group forKey:nil];
        [_charView2.layer addAnimation:group2 forKey:nil];
        
        CABasicAnimation * translation = [CABasicAnimation animationWithKeyPath:@"position"];
        translation.fromValue = [NSValue valueWithCGPoint:CGPointMake(_coverView.center.x - kscaleDeviceLength(50), _coverView.center.y + kscaleDeviceLength(50))];
        translation.toValue = [NSValue valueWithCGPoint:_coverView.center];
        translation.duration = SQFrame(10);
        
        CAAnimationGroup * group3 = [CAAnimationGroup animation];
        group3.fillMode = kCAFillModeForwards;
        group3.removedOnCompletion = NO;
        group3.duration = SQFrame(10);
        group3.animations = @[opacity, translation];
        [_coverView.layer addAnimation:group3 forKey:nil];
    }

动画效果

来看一下我们的程序跑起来的样子如何.

代码实现效果

内存泄漏

由于动画部分并没有什么技术含量, 仅仅贴出源码, 就不上传github了. 但是这里要请大家来帮个忙, 找找我源码中的内存泄漏的部分, 因为没有走dealloc方法, 网上说CAAnimation的delegate 是strong修饰的导致循环引用, 释放不了就内存泄漏了, 但是如果将其释放了, animation.fillMode = kCAFillModeForwards;animation.removedOnCompletion = NO; 就没有效果了, 动画效果就不能显示了, 所以说应该不是apple的坑, 但这种情况该如何解决呢??

内存泄漏

解决内存泄漏

再次感谢@搬运工开发者提供的方法解决了内存泄漏的问题, 个中原因可以看他写的文章: http://www.jianshu.com/p/39462a71cf40, 简单来说就是先使用强引用保证动画不在运行时被释放, 再用弱引用保证不产生循环引用.

添加代理管理者, 进行自定义代理(作者说的)

@protocol HYAnimationWeakDelegate <NSObject>
@optional
- (void)animationDidStart:(CAAnimation *)anim;
- (void)animationDidStop:(CAAnimation *)anim;
@end

@interface HYAnimationDelegateManager : NSObject <CAAnimationDelegate>
@property (weak, nonatomic) id<HYAnimationWeakDelegate> delegate;
@end

@implementation HYAnimationDelegateManager
- (void)animationDidStart:(CAAnimation *)anim {
    if (_delegate && [_delegate respondsToSelector:@selector(animationDidStart:)]) {
        [_delegate animationDidStart:anim];
    }
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    if (_delegate && [_delegate respondsToSelector:@selector(animationDidStop:)]) {
        [_delegate animationDidStop:anim];
    }
}
@end

如何使用:

    HYAnimationDelegateManager * manager = [HYAnimationDelegateManager new]; //创建实例
    manager.delegate = self; //进行弱引用
    backgroundViewAnim.delegate = manager; //进行强引用

实测解决了内存泄漏的问题, delloc进行释放.

完整源码

//
//  HYGuideView.m
//  Mall
//
//  Created by 双泉 朱 on 17/6/13.
//  Copyright © 2017年 _Zhizi_. All rights reserved.
//

#import "HYGuideView.h"

#define SQAngle(x) ((x) / 180.0f * M_PI)
#define SQFrame(x) ((x) * 0.04)

@protocol HYAnimationWeakDelegate <NSObject>
@optional
- (void)animationDidStart:(CAAnimation *)anim;
- (void)animationDidStop:(CAAnimation *)anim;
@end

@interface HYAnimationDelegateManager : NSObject <CAAnimationDelegate>
@property (weak, nonatomic) id<HYAnimationWeakDelegate> delegate;
@end

@implementation HYAnimationDelegateManager
- (void)animationDidStart:(CAAnimation *)anim {
    if (_delegate && [_delegate respondsToSelector:@selector(animationDidStart:)]) {
        [_delegate animationDidStart:anim];
    }
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    if (_delegate && [_delegate respondsToSelector:@selector(animationDidStop:)]) {
        [_delegate animationDidStop:anim];
    }
}
@end

@interface HYGuideView () <HYAnimationWeakDelegate>

@property (nonatomic,strong) UIImageView * backgroundView;
@property (nonatomic,strong) UIImageView * phoneView;
@property (nonatomic,strong) CALayer * maskLayer;
@property (nonatomic,strong) CALayer * phoneLayer;
@property (nonatomic,strong) UIImageView * handView;
@property (nonatomic,strong) UIImageView * homeView;
@property (nonatomic,strong) UIImageView * topIcon;
@property (nonatomic,strong) UIImageView * leftIcon;
@property (nonatomic,strong) UIImageView * bottomIcon;
@property (nonatomic,strong) UIImageView * rightIcon;
@property (nonatomic,strong) UIImageView * coverView;
@property (nonatomic,strong) UIImageView * charView1;
@property (nonatomic,strong) UIImageView * charView2;
@property (nonatomic,strong) UIImageView * lineView1;
@property (nonatomic,strong) UIImageView * lineView2;

@end

@implementation HYGuideView

- (void)dealloc {
    NSLog(@"%@ - execute %s",NSStringFromClass([self class]),__func__);
}

- (instancetype)initWithFrame:(CGRect)frame {
    
    self = [super initWithFrame:frame];
    if (self) {
        [self setupSubviews];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)coder  {
    
    self = [super initWithCoder:coder];
    if (self) {
        [self setupSubviews];
    }
    return self;
}

- (UIImageView *)backgroundView {
    
    if (!_backgroundView) {
        _backgroundView = [UIImageView new];
        _backgroundView.image = [UIImage imageNamed:@"背景"];
        _backgroundView.transform = CGAffineTransformMakeScale(0.3, 0.3);
    }
    return _backgroundView;
}

- (UIImageView *)phoneView {
    
    if (!_phoneView) {
        _phoneView = [UIImageView new];
        _phoneView.layer.mask = self.maskLayer;
    }
    return _phoneView;
}

- (CALayer *)phoneLayer {
    
    if (!_phoneLayer) {
        _phoneLayer = [CALayer new];
        _phoneLayer.contents = (__bridge id)[UIImage imageNamed:@"手机"].CGImage;
    }
    return _phoneLayer;
}

- (CALayer *)maskLayer {
    
    if (!_maskLayer) {
        _maskLayer = [CALayer new];
        _maskLayer.contents = (__bridge id)[UIImage imageNamed:@"圆-黑"].CGImage;
    }
    return _maskLayer;
}

- (UIImageView *)handView {
    
    if (!_handView) {
        _handView = [UIImageView new];
        _handView.image = [UIImage imageNamed:@"手"];
        _handView.layer.anchorPoint = CGPointZero;
        _handView.layer.opacity = 0;
    }
    return _handView;
}

- (UIImageView *)homeView {
    
    if (!_homeView) {
        _homeView = [UIImageView new];
        _homeView.image = [UIImage imageNamed:@"home"];
        _homeView.layer.opacity = 0;
    }
    return _homeView;
}

- (UIImageView *)topIcon {
    
    if (!_topIcon) {
        _topIcon = [UIImageView new];
        _topIcon.image = [UIImage imageNamed:@"全球购"];
        _topIcon.transform = CGAffineTransformMakeScale(0, 0);
    }
    return _topIcon;
}

- (UIImageView *)leftIcon {
    
    if (!_leftIcon) {
        _leftIcon = [UIImageView new];
        _leftIcon.image = [UIImage imageNamed:@"互助圈"];
        _leftIcon.transform = CGAffineTransformMakeScale(0, 0);
    }
    return _leftIcon;
}

- (UIImageView *)bottomIcon {
    
    if (!_bottomIcon) {
        _bottomIcon = [UIImageView new];
        _bottomIcon.image = [UIImage imageNamed:@"签到"];
        _bottomIcon.transform = CGAffineTransformMakeScale(0, 0);
    }
    return _bottomIcon;
}

- (UIImageView *)rightIcon {
    
    if (!_rightIcon) {
        _rightIcon = [UIImageView new];
        _rightIcon.image = [UIImage imageNamed:@"折"];
        _rightIcon.transform = CGAffineTransformMakeScale(0, 0);
    }
    return _rightIcon;
}

- (UIImageView *)coverView {
    
    if (!_coverView) {
        _coverView = [UIImageView new];
        _coverView.image = [UIImage imageNamed:@"光效"];
        _coverView.layer.opacity = 0;
    }
    return _coverView;
}

- (UIImageView *)charView1 {
    
    if (!_charView1) {
        _charView1 = [UIImageView new];
        _charView1.image = [UIImage imageNamed:@"全新首页"];
        _charView1.layer.opacity = 0;
    }
    return _charView1;
}

- (UIImageView *)charView2 {
    
    if (!_charView2) {
        _charView2 = [UIImageView new];
        _charView2.image = [UIImage imageNamed:@"让您体验不一样的医药电商"];
        _charView2.layer.opacity = 0;
    }
    return _charView2;
}

- (UIImageView *)lineView1 {
    
    if (!_lineView1) {
        _lineView1 = [UIImageView new];
        _lineView1.image = [UIImage imageNamed:@"line1"];
    }
    return _lineView1;
}

- (UIImageView *)lineView2 {
    
    if (!_lineView2) {
        _lineView2 = [UIImageView new];
        _lineView2.image = [UIImage imageNamed:@"line2"];
    }
    return _lineView2;
}

- (void)setupSubviews {
    [self addSubview:self.backgroundView];
    [self addSubview:self.coverView];
    [self.backgroundView addSubview:self.phoneView];
    [self.phoneView.layer addSublayer:self.phoneLayer];
    [self addSubview:self.handView];
    [self addSubview:self.homeView];
    [self addSubview:self.topIcon];
    [self addSubview:self.leftIcon];
    [self addSubview:self.bottomIcon];
    [self addSubview:self.rightIcon];
    [self addSubview:self.charView1];
    [self addSubview:self.charView2];
    [self addSubview:self.lineView1];
    [self addSubview:self.lineView2];
    
    UIButton * btn = [UIButton buttonWithType:UIButtonTypeContactAdd];
    btn.frame = CGRectMake(0, 100, 100, 100);
    [btn addTarget:self action:@selector(loadAnimations) forControlEvents:64];
    [self addSubview:btn];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    
    CGFloat width = self.width * 0.75;
    CGFloat height = width * 1.10704961;
    CGFloat length = width * 0.82114883;
    
    _backgroundView.bounds = CGRectMake(0, 0, width, height);
    _backgroundView.center = (CGPoint){self.center.x, self.center.y - 20};
    
    CGFloat coverViewW = width * 1.06396867;
    CGFloat coverViewH = coverViewW * 1.00736196;
    _coverView.bounds = CGRectMake(0, 0, coverViewW, coverViewH);
    _coverView.center = _backgroundView.center;
    
    CGFloat phoneViewW = length * 0.68203498;
    CGFloat phoneViewH = phoneViewW * 1.44988345;
    CGFloat phoneViewX = (width - phoneViewW) * 0.5;
    CGFloat phoneViewY = (height - phoneViewH) * 0.5;
    _phoneView.frame = CGRectMake(phoneViewX, phoneViewY, phoneViewW, phoneViewH);
    
    CGFloat phoneLayerX = 0;
    CGFloat phoneLayerY = phoneViewH;
    CGFloat phoneLayerW = phoneViewW;
    CGFloat phoneLayerH = phoneViewH;
    _phoneLayer.frame = CGRectMake(phoneLayerX, phoneLayerY, phoneLayerW, phoneLayerH);

    CGFloat maskLayerW = length;
    CGFloat maskLayerH = length;
    CGFloat maskLayerX = (phoneViewW - maskLayerW) * 0.5;
    CGFloat maskLayerY = (phoneViewH - maskLayerH) * 0.5;
    _maskLayer.frame = CGRectMake(maskLayerX, maskLayerY, maskLayerW, maskLayerH);
    
    CGFloat handViewX = _backgroundView.center.x + kscaleDeviceLength(50);
    CGFloat handViewY = _backgroundView.center.y + kscaleDeviceLength(60);
    CGFloat handViewW = width * 0.56396867;
    CGFloat handViewH = handViewW * 0.98611111;
    _handView.frame = CGRectMake(handViewX, handViewY, handViewW, handViewH);
    
    CGFloat homeViewW = width * 0.27154047;
    CGFloat homeViewH = homeViewW * 1.02884615;
    CGFloat homeViewX = _backgroundView.center.x - homeViewW * 0.5;
    CGFloat homeViewY = _backgroundView.center.y - homeViewH * 0.5 + kscaleDeviceLength(15);
    _homeView.frame = CGRectMake(homeViewX, homeViewY, homeViewW, homeViewH);
    
    CGFloat topIconW = width * 0.32375979;
    CGFloat topIconH = topIconW;
    _topIcon.bounds = CGRectMake(0, 0, topIconW, topIconH);
    _topIcon.center = CGPointMake(kscaleDeviceLength(200), kscaleDeviceLength(130));
    
    CGFloat leftIconW = width * 0.28720627;
    CGFloat leftIconH = leftIconW;
    _leftIcon.bounds = CGRectMake(0, 0, leftIconW, leftIconH);
    _leftIcon.center = CGPointMake(kscaleDeviceLength(40), kscaleDeviceLength(200));

    CGFloat bottomIconW = width * 0.30417755;
    CGFloat bottomIconH = bottomIconW;
    _bottomIcon.bounds = CGRectMake(0, 0, bottomIconW, bottomIconH);
    _bottomIcon.center = CGPointMake(kscaleDeviceLength(50), kscaleDeviceLength(360));

    CGFloat rightIconW = width * 0.25195822;
    CGFloat rightIconH = rightIconW;
    _rightIcon.bounds = CGRectMake(0, 0, rightIconW, rightIconH);
    _rightIcon.center = CGPointMake(kscaleDeviceLength(260), kscaleDeviceLength(260));

    CGFloat charView2W = width * 0.94386423;
    CGFloat charView2H = charView2W * 0.07745505;
    CGFloat charView2X = (self.width - charView2W) * 0.5;
    CGFloat charView2Y = self.height - charView2H - kscaleDeviceLength(50);
    _charView2.frame = CGRectMake(charView2X, charView2Y, charView2W, charView2H);

    CGFloat charView1W = width * 0.4308094;
    CGFloat charView1H = charView1W * 0.23939394;
    CGFloat charView1X = (self.width - charView1W) * 0.5;
    CGFloat charView1Y = charView2Y - charView1H - kscaleDeviceLength(15);
    _charView1.frame = CGRectMake(charView1X, charView1Y, charView1W, charView1H);
    
    CGFloat lineView1X = kscaleDeviceLength(238.5);
    CGFloat lineView1Y = kscaleDeviceLength(131);
    CGFloat lineView1W = width * 0.07832898;
    CGFloat lineView1H = lineView1W;
    _lineView1.frame = CGRectMake(lineView1X, lineView1Y, lineView1W, lineView1H);

    CGFloat lineView2X = kscaleDeviceLength(80);
    CGFloat lineView2Y = kscaleDeviceLength(400);
    CGFloat lineView2W = lineView1W;
    CGFloat lineView2H = lineView1W;
    _lineView2.frame = CGRectMake(lineView2X, lineView2Y, lineView2W, lineView2H);
}

- (void)loadAnimations {
    
    CAKeyframeAnimation * backgroundViewAnim = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
    backgroundViewAnim.values = @[@0.3, @1.2, @1.0];
    backgroundViewAnim.keyTimes = @[@0, @0.6, @1.0];
    backgroundViewAnim.fillMode = kCAFillModeForwards;
    backgroundViewAnim.removedOnCompletion = NO;
    backgroundViewAnim.duration = SQFrame(6);
    HYAnimationDelegateManager * manager = [HYAnimationDelegateManager new];
    manager.delegate = self;
    backgroundViewAnim.delegate = manager;
    [_backgroundView.layer addAnimation:backgroundViewAnim forKey:@"animation1"];
    
    CABasicAnimation * lineViewAnim = [self scaleAnimation];
    lineViewAnim.duration = SQFrame(6);
    [_lineView1.layer addAnimation:lineViewAnim forKey:nil];
    [_lineView2.layer addAnimation:lineViewAnim forKey:nil];
}

- (void)animationDidStop:(CAAnimation *)anim {
    
    if ([_backgroundView.layer animationForKey:@"animation1"] == anim) {
        
        CABasicAnimation * phoneLayerAnim = [CABasicAnimation animationWithKeyPath:@"position.y"];
        phoneLayerAnim.fromValue = @(_phoneView.height + kscaleDeviceLength(180));
        phoneLayerAnim.toValue = @(_phoneView.height - kscaleDeviceLength(70));
        phoneLayerAnim.fillMode = kCAFillModeForwards;
        phoneLayerAnim.removedOnCompletion = NO;
        phoneLayerAnim.duration = SQFrame(10);
        HYAnimationDelegateManager * manager = [HYAnimationDelegateManager new];
        manager.delegate = self;
        phoneLayerAnim.delegate = manager;
        [_phoneLayer addAnimation:phoneLayerAnim forKey:@"animation2"];
    }
    
    if ([_phoneLayer animationForKey:@"animation2"] == anim) {
        
        CABasicAnimation * translation = [CABasicAnimation animationWithKeyPath:@"position"];
        translation.fromValue = [NSValue valueWithCGPoint:CGPointMake(_backgroundView.center.x + kscaleDeviceLength(50), _backgroundView.center.y + kscaleDeviceLength(60))];
        translation.toValue = [NSValue valueWithCGPoint:CGPointMake(_backgroundView.center.x, _backgroundView.center.y + kscaleDeviceLength(30))];
        
        CAAnimationGroup * group = [CAAnimationGroup animation];
        group.fillMode = kCAFillModeForwards;
        group.removedOnCompletion = NO;
        group.duration = SQFrame(19);
        group.animations = @[translation, [self opacityAnimation]];
        HYAnimationDelegateManager * manager = [HYAnimationDelegateManager new];
        manager.delegate = self;
        group.delegate = manager;
        [_handView.layer addAnimation:group forKey:@"animation3"];
    }
    
    if ([_handView.layer animationForKey:@"animation3"] == anim) {
        
        CABasicAnimation * animation = [self opacityAnimation];
        animation.duration = SQFrame(5);
        [_homeView.layer addAnimation:animation forKey:nil];
        
        [_topIcon.layer addAnimation:[self groupAnimation:[self translationWithIcon:_topIcon]] forKey:@"animation4"];
        [_rightIcon.layer addAnimation:[self groupAnimation:[self translationWithIcon:_rightIcon]] forKey:nil];
        [_bottomIcon.layer addAnimation:[self groupAnimation:[self translationWithIcon:_bottomIcon]] forKey:nil];
        [_leftIcon.layer addAnimation:[self groupAnimation:[self translationWithIcon:_leftIcon]] forKey:nil];
    }

    if ([_topIcon.layer animationForKey:@"animation4"] == anim) {

        [_topIcon.layer addAnimation:[self rotateWithIcon:_topIcon clockwise:YES] forKey:nil];
        [_rightIcon.layer addAnimation:[self rotateWithIcon:_rightIcon clockwise:NO] forKey:nil];
        [_bottomIcon.layer addAnimation:[self rotateWithIcon:_bottomIcon clockwise:YES] forKey:nil];
        [_leftIcon.layer addAnimation:[self rotateWithIcon:_leftIcon clockwise:NO] forKey:nil];
        
        CABasicAnimation * opacity = [self opacityAnimation];
        opacity.duration = SQFrame(5);
        
        CABasicAnimation * opacity2 = [self opacityAnimation];
        opacity2.beginTime = SQFrame(5);
        opacity2.duration = SQFrame(5);
        
        CABasicAnimation * charView1Anim = [self translationWithText:_charView1];
        charView1Anim.duration = SQFrame(10);
        
        CABasicAnimation * charView2Anim = [self translationWithText:_charView2];
        charView2Anim.beginTime = SQFrame(5);
        charView2Anim.duration = SQFrame(10);
        
        CAAnimationGroup * group = [CAAnimationGroup animation];
        group.fillMode = kCAFillModeForwards;
        group.removedOnCompletion = NO;
        group.duration = SQFrame(15);
        group.animations = @[opacity, charView1Anim];
        
        CAAnimationGroup * group2 = [CAAnimationGroup animation];
        group2.fillMode = kCAFillModeForwards;
        group2.removedOnCompletion = NO;
        group2.duration = SQFrame(15);
        group2.animations = @[opacity2, charView2Anim];
        
        [_charView1.layer addAnimation:group forKey:nil];
        [_charView2.layer addAnimation:group2 forKey:nil];
        
        CABasicAnimation * translation = [CABasicAnimation animationWithKeyPath:@"position"];
        translation.fromValue = [NSValue valueWithCGPoint:CGPointMake(_coverView.center.x - kscaleDeviceLength(50), _coverView.center.y + kscaleDeviceLength(50))];
        translation.toValue = [NSValue valueWithCGPoint:_coverView.center];
        translation.duration = SQFrame(10);
        
        CAAnimationGroup * group3 = [CAAnimationGroup animation];
        group3.fillMode = kCAFillModeForwards;
        group3.removedOnCompletion = NO;
        group3.duration = SQFrame(10);
        group3.animations = @[opacity, translation];
        [_coverView.layer addAnimation:group3 forKey:nil];
    }
}

- (CABasicAnimation *)translationWithText:(UIView *)text {

    CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"position.y"];
    animation.fromValue = @(text.center.y + kscaleDeviceLength(50));
    animation.toValue = @(text.center.y);
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    return animation;
}

- (CABasicAnimation *)translationWithIcon:(UIView *)icon {

    CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(CGRectGetMidX(_homeView.frame), CGRectGetMaxY(_homeView.frame))];
    animation.toValue = [NSValue valueWithCGPoint:icon.center];
    return  animation;
}

- (CAKeyframeAnimation *)rotateWithIcon:(UIView *)icon clockwise:(BOOL)clockwise  {

    UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(icon.center.x, icon.center.y) radius:(arc4random()%5) + 1 startAngle:SQAngle(0) endAngle:SQAngle(360) clockwise:clockwise];
    CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    animation.duration = 1.0;
    animation.path = path.CGPath;
    animation.repeatCount = MAXFLOAT;
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    return animation;
}

- (CAAnimationGroup *)groupAnimation:(CABasicAnimation *)animation {
    
    CAAnimationGroup * group = [CAAnimationGroup animation];
    group.fillMode = kCAFillModeForwards;
    group.removedOnCompletion = NO;
    group.duration = SQFrame(10);
    HYAnimationDelegateManager * manager = [HYAnimationDelegateManager new];
    manager.delegate = self;
    group.delegate = manager;
    group.animations = @[[self opacityAnimation], animation, [self scaleAnimation]];
    return group;
}

- (CABasicAnimation *)opacityAnimation {

    CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    animation.fromValue = @(0);
    animation.toValue = @(1);
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    return animation;
}

- (CABasicAnimation *)scaleAnimation {
    
    CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    animation.fromValue = @(0);
    animation.toValue = @(1);
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    return animation;
}

@end

最近的文章

Web 是时候用前端写个简历了!

最近感觉自己又好像处于瓶颈期了, 感觉就像是便秘一样的难受, 当然我从来没有便秘过, 就是一个说辞, 上一次又这种感觉还是在14年的时候, 那时候我还是在做空运关务的工作, 整天面对着的就是提单, 清关等杂事, 觉得总不能碌碌无为的混一辈子吧, 那时正值国内移动端开发的蓝海期, 看了当年WWDC大会的我毅然决然的投入了移动端开发的浪潮, 但是做了几年, 又有一种莫名的无力感逐渐的涌现, 不久感觉又要进行人生的选择了.越来越发现自己是一个很丧的人, 虽然丧, 但是做事很积极什么事也没耽误...…

前端开发继续阅读
更早的文章

SceneKit 不会 Unity3D 的另一种选择

上周一, 相信很多人和我一样, 全程观看了WWDC2017的开发者大会, 其中虽然亮点平平但也能些许的看出苹果未来的战略, 虽然已经从先驱者变成跟随者, 但强者恒强的道理是亘古不变的真理, 而且在生态链的建设上也是无人能出其右, 虽然在消费者眼中最为关注的是HomePod和iPad Pro10.5, 而在开发者眼中为之眼前一亮的则是ARKit和Core ML.Core ML 刚发布的时候还以为是终于能用Swift进行模型的训练了, 终于不用学习缩进地狱的Python了, 然而这仅仅是一...…

移动开发继续阅读