• 登陆动画


    LoginViewController.h
    
    #import <UIKit/UIKit.h>
    
    @interface LoginViewController : UIViewController
    
    @property (nonatomic,strong) UIImageView * LoginImage;  // logo图
    @property (nonatomic,strong) UILabel     * LoginWord;   // logo下面的文字
    @property (nonatomic,strong) UIButton    * GetButton;   // get按钮
    
    @property (nonatomic,strong) UITextField * userTextField;     // 账号输入框
    @property (nonatomic,strong) UITextField * passwordTextField; // 密码输入框
    @property (nonatomic,strong) UIButton    * LoginButton;       // login按钮
    
    @property (nonatomic,strong) UIView      * HUDView;       // 登录时加一个看不见的蒙版,让控件不能再被点击
    @property (nonatomic,strong) UIView      * LoginAnimView; // 执行登录按钮动画的view (动画效果不是按钮本身,而是这个view)
    
    @property (nonatomic,strong) CAShapeLayer * shapeLayer; // 登录转圈的那条白线所在的layer
    @property (nonatomic,strong) UIView       * animView;   // get按钮动画view
    
    
    
    - (void)reloadView;
    
    @end
    LoginViewController.m
    
    
    #import "LoginViewController.h"
    #import "POP.h"
    #import "Masonry.h"
    #import "UIView+YYExtension.h"
    #import "ViewController.h"
    #import "LoginTranslation.h"
    
    
    #define UIColorFromRGB(rgbValue) 
    [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]
    #define BG_COLOR UIColorFromRGB(0xefeff4)
    
    #define ScreenW [UIScreen mainScreen].bounds.size.width
    #define ScreenH [UIScreen mainScreen].bounds.size.height
    
    #define ButtonColor [UIColor colorWithRed:156/255.0 green:197/255.0 blue:251/255.0 alpha:1.0]
    
    static CGFloat const springSpeed = 6.0;
    static CGFloat const springBounciness = 16.0;
    
    @interface LoginViewController () <CAAnimationDelegate, UIViewControllerTransitioningDelegate>
    // 转场动画管理对象()
    @property (nonatomic,strong) LoginTranslation * loginTranslation;
    @end
    
    @implementation LoginViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self SetupUIComponent];
    }
    
    #pragma mark - 懒加载
    
    - (LoginTranslation *)loginTranslation {
        if (!_loginTranslation)
        {
            _loginTranslation = [[LoginTranslation alloc] init];
        }
        return _loginTranslation;
    }
    
    - (UIImageView *)LoginImage {
        if (!_LoginImage)
        {
            _LoginImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"logo.png"]];
            [self.view addSubview:_LoginImage];
        }
        return _LoginImage;
    }
    
    - (UILabel *)LoginWord {
        if (!_LoginWord)
        {
            _LoginWord = [[UILabel alloc] init];
            [self.view addSubview:_LoginWord];
            _LoginWord.font =  [UIFont fontWithName:@"TimesNewRomanPS-ItalicMT" size:34.0f];
            _LoginWord.textColor = [UIColor blackColor];
            _LoginWord.text = @"YY Anim Demo";
            [_LoginWord sizeToFit];
        }
        return _LoginWord;
    }
    
    - (UIButton *)GetButton {
        if (!_GetButton)
        {
            _GetButton = [UIButton buttonWithType:UIButtonTypeCustom];
            [self.view addSubview:_GetButton];
            [_GetButton.layer setMasksToBounds:YES];
            [_GetButton.layer setCornerRadius:22.0];
            [_GetButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
            [_GetButton setTitle:@"GET" forState:UIControlStateNormal];
            _GetButton.backgroundColor = ButtonColor;
            [_GetButton addTarget:self action:@selector(GetButtonClick) forControlEvents:UIControlEventTouchUpInside];
        }
        return _GetButton;
    }
    
    
    
    - (UITextField *)userTextField {
        if(_userTextField == nil) {
            _userTextField = [[UITextField alloc] init];
            _userTextField.font = [UIFont systemFontOfSize:15];
            _userTextField.placeholder = @"Username";
            _userTextField.alpha = 0.0;
            [_userTextField setValue:UIColorFromRGB(0xcccccc) forKeyPath:@"_placeholderLabel.textColor"];
            [_userTextField setValue:[UIFont systemFontOfSize:15.0] forKeyPath:@"_placeholderLabel.font"];
            _userTextField.textAlignment = NSTextAlignmentCenter;
            _userTextField.keyboardType = UIKeyboardTypePhonePad;
            _userTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
            _userTextField.tintColor = ButtonColor;
            
            UIView *seperatorLine = [[UIView alloc] init];
            [_userTextField addSubview:seperatorLine];
            seperatorLine.backgroundColor = UIColorFromRGB(0xe1e1e1);
            [seperatorLine mas_makeConstraints:^(MASConstraintMaker *make) {
                make.left.right.bottom.mas_equalTo(_userTextField);
                make.height.mas_equalTo(1.5);
            }];
            
            [self.view addSubview:_userTextField];
            _userTextField.frame = CGRectMake(ScreenW * 0.2, ScreenH * 0.7-(ScreenH * 0.3 - 44) * 0.5 - 75 + 25, ScreenW * 0.6, 50);
        }
        return _userTextField;
    }
    
    
    - (UITextField *)passwordTextField {
        if(_passwordTextField == nil) {
            _passwordTextField = [[UITextField alloc] init];
            _passwordTextField.font = [UIFont systemFontOfSize:15];
            _passwordTextField.borderStyle = UITextBorderStyleNone;
            _passwordTextField.placeholder = @"Password";
            _passwordTextField.alpha = 0.0;
            [_passwordTextField setValue:UIColorFromRGB(0xcccccc) forKeyPath:@"_placeholderLabel.textColor"];
            [_passwordTextField setValue:[UIFont systemFontOfSize:15.0] forKeyPath:@"_placeholderLabel.font"];
            _passwordTextField.textAlignment = NSTextAlignmentCenter;
            _passwordTextField.secureTextEntry = YES;
            _passwordTextField.tintColor = ButtonColor;
            
            UIView *seperatorLine = [[UIView alloc] init];
            [_passwordTextField addSubview:seperatorLine];
            seperatorLine.backgroundColor = UIColorFromRGB(0xe1e1e1);
            [seperatorLine mas_makeConstraints:^(MASConstraintMaker *make) {
                make.left.right.bottom.mas_equalTo(_passwordTextField);
                make.height.mas_equalTo(1.5);
            }];
            
            [self.view addSubview:_passwordTextField];
            _passwordTextField.frame = CGRectMake(ScreenW * 0.2, ScreenH * 0.7 - (ScreenH * 0.3 - 44) * 0.5 - 75 + 10 + 50 + 25, ScreenW * 0.6, 50);
        }
        return _passwordTextField;
    }
    
    - (UIButton *)LoginButton {
        if (!_LoginButton){
            _LoginButton = [UIButton buttonWithType:UIButtonTypeCustom];
            [self.view addSubview:_LoginButton];
            _LoginButton.frame = CGRectMake(0, 0, 0, 0);
            _LoginButton.hidden = YES;
            [_LoginButton.layer setMasksToBounds:YES];
            [_LoginButton.layer setCornerRadius:5.0];
            [_LoginButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
            [_LoginButton setTitle:@"LOGIN" forState:UIControlStateNormal];
            _LoginButton.backgroundColor = ButtonColor;
            [_LoginButton addTarget:self action:@selector(LoginButtonClick) forControlEvents:UIControlEventTouchUpInside];
        }
        return _LoginButton;
    }
    
    /** 初始化UI */
    
    - (void)SetupUIComponent {
        self.view.backgroundColor = BG_COLOR;
        
        // 文字布局
        self.LoginWord.yy_centerX = self.view.yy_centerX;
        self.LoginWord.yy_y = self.view.yy_centerY-self.LoginWord.yy_height;
        
        // logo布局
        CGFloat LoginImageWH = ScreenW * 0.25;
        self.LoginImage.frame = CGRectMake((ScreenW - LoginImageWH) * 0.5, CGRectGetMinY(self.LoginWord.frame) - 40 -LoginImageWH, LoginImageWH, LoginImageWH);
        
        // 按钮布局
        CGFloat GetButtonW = ScreenW * 0.4;
        CGFloat GetButtonH = 44;
        self.GetButton.frame = CGRectMake((ScreenW - GetButtonW) * 0.5, ScreenH * 0.7, GetButtonW, GetButtonH);
    }
    
    #pragma mark - get按钮点击事件——执行动画
    
    - (void)GetButtonClick {
        
        /**
         *  动画的思路:
         *  1、造一个view来执行动画,看上去就像get按钮本身在形变移动,其实是这个view
         *  2、改变动画view的背景颜色,变色的过程是整个动画效果执行的过程
         *  3、让按钮变宽
         *  4、变宽完成后,变高
         *  5、变高完成后,同步执行以下四步
         *      5.0、让账号密码按钮出现
         *      5.1、让Login按钮出现
         *      5.2、移动这个view,带弹簧效果
         *      5.3、移动logo图片,带弹簧效果
         *      5.4、移动logo文字,带弹簧效果
         */
        
        // 1、get按钮动画的view
        UIView * animView = [[UIView alloc] init];
        self.animView = animView;
        animView = [[UIView alloc] initWithFrame:self.GetButton.frame];
        animView.layer.cornerRadius = 10;
        animView.frame = self.GetButton.frame;
        animView.backgroundColor = self.GetButton.backgroundColor;
        [self.view addSubview:animView];
        self.GetButton.hidden = YES;
        self.LoginButton.hidden = NO;
        
        // 2、get背景颜色
        CABasicAnimation * changeColor1 = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
        changeColor1.fromValue = (__bridge id)ButtonColor.CGColor;
        changeColor1.toValue = (__bridge id)[UIColor whiteColor].CGColor;
        changeColor1.duration = 0.8f;
        changeColor1.beginTime = CACurrentMediaTime();
        changeColor1.fillMode = kCAFillModeForwards;
        changeColor1.removedOnCompletion = false;
        [animView.layer addAnimation:changeColor1 forKey:changeColor1.keyPath];
        
        // 3、get按钮变宽
        CABasicAnimation * anim1 = [CABasicAnimation animationWithKeyPath:@"bounds.size.width"];
        anim1.fromValue = @(CGRectGetWidth(animView.bounds));
        anim1.toValue = @(ScreenW * 0.8);
        anim1.duration = 0.1;
        anim1.beginTime = CACurrentMediaTime();
        anim1.fillMode = kCAFillModeForwards;
        anim1.removedOnCompletion = false;
        [animView.layer addAnimation:anim1 forKey:anim1.keyPath];
        
        // 4、get按钮变高
        CABasicAnimation *anim2 = [CABasicAnimation animationWithKeyPath:@"bounds.size.height"];
        anim2.fromValue = @(CGRectGetHeight(animView.bounds));
        anim2.toValue = @(ScreenH * 0.3);
        anim2.duration = 0.1;
        anim2.beginTime = CACurrentMediaTime() + 0.1;
        anim2.fillMode = kCAFillModeForwards;
        anim2.removedOnCompletion = false;
        anim2.delegate = self; // 变高完成,给它加个阴影
        [animView.layer addAnimation:anim2 forKey:anim2.keyPath];
        
        // 5.0、账号密码按钮出现
        self.userTextField.alpha = 0.0;
        self.passwordTextField.alpha = 0.0;
        [UIView animateWithDuration:0.4 delay:0.2 options:UIViewAnimationOptionCurveEaseInOut animations:^{
            self.userTextField.alpha = 1.0;
            self.passwordTextField.alpha = 1.0;
        } completion:^(BOOL finished) {
            
        }];
        
        // 5.1、login按钮出现动画
        self.LoginButton.yy_centerX = ScreenW * 0.5;
        self.LoginButton.yy_centerY = ScreenH * 0.7 + 44 + (ScreenH * 0.3 - 44) * 0.5 - 75;
        
        CABasicAnimation * animLoginBtn = [CABasicAnimation animationWithKeyPath:@"bounds.size"];
        animLoginBtn.fromValue = [NSValue valueWithCGSize:CGSizeMake(0, 0)];
        animLoginBtn.toValue = [NSValue valueWithCGSize:CGSizeMake(ScreenW * 0.5, 44)];
        animLoginBtn.duration = 0.4;
        animLoginBtn.beginTime = CACurrentMediaTime() + 0.2;
        animLoginBtn.fillMode = kCAFillModeForwards;
        animLoginBtn.removedOnCompletion = false;
        animLoginBtn.delegate = self;  // 在代理方法(动画完成回调)里,让按钮真正的宽高改变,而不仅仅是它的layer,否则看得到点不到
        [self.LoginButton.layer addAnimation:animLoginBtn forKey:animLoginBtn.keyPath];
        
        // 5.2、按钮移动动画
        POPSpringAnimation * anim3 = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter];
        anim3.fromValue = [NSValue valueWithCGRect:CGRectMake(animView.yy_centerX, animView.yy_centerY, animView.yy_width, animView.yy_height)];
        anim3.toValue = [NSValue valueWithCGRect:CGRectMake(animView.yy_centerX, animView.yy_centerY-75, animView.yy_width, animView.yy_height)];
        anim3.beginTime = CACurrentMediaTime() + 0.2;
        anim3.springBounciness = springBounciness;
        anim3.springSpeed = springSpeed;
        [animView pop_addAnimation:anim3 forKey:nil];
        
        // 5.3、图片移动动画
        POPSpringAnimation * anim4 = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        anim4.fromValue = [NSValue valueWithCGRect:CGRectMake(self.LoginImage.yy_x, self.LoginImage.yy_y, self.LoginImage.yy_width, self.LoginImage.yy_height)];
        anim4.toValue = [NSValue valueWithCGRect:CGRectMake(self.LoginImage.yy_x, self.LoginImage.yy_y-75, self.LoginImage.yy_width, self.LoginImage.yy_height)];
        anim4.beginTime = CACurrentMediaTime()+0.2;
        anim4.springBounciness = springBounciness;
        anim4.springSpeed = springSpeed;
        [self.LoginImage pop_addAnimation:anim4 forKey:nil];
        
        // 5.4、文字移动动画
        POPSpringAnimation *anim5 = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
        anim5.fromValue = [NSValue valueWithCGRect:CGRectMake(self.LoginWord.yy_x, self.LoginWord.yy_y, self.LoginWord.yy_width, self.LoginWord.yy_height)];
        anim5.toValue = [NSValue valueWithCGRect:CGRectMake(self.LoginWord.yy_x, self.LoginWord.yy_y-75, self.LoginWord.yy_width, self.LoginWord.yy_height)];
        anim5.beginTime = CACurrentMediaTime()+0.2;
        anim5.springBounciness = springBounciness;
        anim5.springSpeed = springSpeed;
        [self.LoginWord pop_addAnimation:anim5 forKey:nil];
    }
    
    
    #pragma mark - 动画代理
    /** 动画执行结束回调 */
    - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
        if([((CABasicAnimation *)anim).keyPath isEqualToString:@"bounds.size.height"]) {
            // 阴影颜色
            self.animView.layer.shadowColor = [UIColor redColor].CGColor;
            // 阴影的透明度
            self.animView.layer.shadowOpacity = 0.8f;
            // 阴影的圆角
            self.animView.layer.shadowRadius = 5.0f;
            // 阴影偏移量
            self.animView.layer.shadowOffset = CGSizeMake(1,1);
            self.userTextField.alpha = 1.0;
            self.passwordTextField.alpha = 1.0;
        } else if ([((CABasicAnimation *)anim).keyPath isEqualToString:@"bounds.size"]) {
            self.LoginButton.bounds = CGRectMake(ScreenW * 0.5, ScreenH * 0.7 + 44 + (ScreenH * 0.3 - 44) * 0.5 - 75, ScreenW * 0.5, 44);
        }
    }
    
    #pragma mark - login按钮点击事件——执行动画
    
    - (void)LoginButtonClick {
        
        // HUDView,盖住view,以屏蔽掉点击事件
        self.HUDView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, ScreenW, ScreenH)];
        [[UIApplication sharedApplication].keyWindow addSubview:self.HUDView];
        self.HUDView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.0];
        
        // 执行登录按钮转圈动画的view
        self.LoginAnimView = [[UIView alloc] initWithFrame:self.LoginButton.frame];
        self.LoginAnimView.layer.cornerRadius = 10;
        self.LoginAnimView.layer.masksToBounds = YES;
        self.LoginAnimView.frame = self.LoginButton.frame;
        self.LoginAnimView.backgroundColor = self.LoginButton.backgroundColor;
        [self.view addSubview:self.LoginAnimView];
        self.LoginButton.hidden = YES;
        
        // 把view从宽的样子变圆
        CGPoint centerPoint = self.LoginAnimView.center;
        CGFloat radius = MIN(self.LoginButton.frame.size.width, self.LoginButton.frame.size.height);
        [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
            self.LoginAnimView.frame = CGRectMake(0, 0, radius, radius);
            self.LoginAnimView.center = centerPoint;
            self.LoginAnimView.layer.cornerRadius = radius/2;
            self.LoginAnimView.layer.masksToBounds = YES;
        }completion:^(BOOL finished) {
            // 给圆加一条不封闭的白色曲线
            self.shapeLayer = [[CAShapeLayer alloc] init];
            self.shapeLayer.lineWidth = 1.5;
            self.shapeLayer.strokeColor = [UIColor whiteColor].CGColor;
            self.shapeLayer.fillColor = self.LoginButton.backgroundColor.CGColor;
            self.shapeLayer.frame = CGRectMake(0, 0, radius, radius);
            
            UIBezierPath * path = [[UIBezierPath alloc] init];
            [path addArcWithCenter:CGPointMake(radius/2, radius/2) radius:(radius/2 - 5) startAngle:0 endAngle:M_PI_2 * 2 clockwise:YES];
            self.shapeLayer.path = path.CGPath;
            [self.LoginAnimView.layer addSublayer:self.shapeLayer];
            
            // 让圆转圈,实现"加载中"的效果
            CABasicAnimation * baseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
            baseAnimation.duration = 0.4;
            baseAnimation.fromValue = @(0);
            baseAnimation.toValue = @(2 * M_PI);
            baseAnimation.repeatCount = MAXFLOAT;
            [self.LoginAnimView.layer addAnimation:baseAnimation forKey:nil];
            
            // 开始登录
            [self doLogin];
        }];
    }
    
    /** 模拟登录 */
    
    - (void)doLogin {
        // 延时,模拟网络请求的延时
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            if ([self.userTextField.text isEqualToString:@""] || [self.passwordTextField.text isEqualToString:@""]) {
                // 登录失败
                [self loginFail];
            } else {
                // 登录成功
                [self loginSuccess];
            }
        });
    }
    
    /** 登录成功 */
    - (void)loginSuccess {
        // 移除蒙版
        [self.HUDView removeFromSuperview];
        // 跳转到另一个控制器
        ViewController * vc = [[ViewController alloc] init];
        vc.transitioningDelegate = self;
        [self presentViewController:vc animated:YES completion:nil];
    }
    
    /** 登录失败 */
    - (void)loginFail {
        // 把蒙版、动画view等隐藏,把真正的login按钮显示出来
        self.LoginButton.hidden = NO;
        [self.HUDView removeFromSuperview];
        [self.LoginAnimView removeFromSuperview];
        [self.LoginAnimView.layer removeAllAnimations];
        
        // 给按钮添加左右摆动的效果(路径动画)
        // CABasicAnimation是从一个值到另一个值,关键帧动画CAKeyframeAnimation是值变化的数组
        CAKeyframeAnimation * keyFrame = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        CGPoint point = self.LoginAnimView.layer.position;
        keyFrame.values = @[[NSValue valueWithCGPoint:CGPointMake(point.x, point.y)],
                            [NSValue valueWithCGPoint:CGPointMake(point.x - 10, point.y)],
                            [NSValue valueWithCGPoint:CGPointMake(point.x + 10, point.y)],
                            [NSValue valueWithCGPoint:CGPointMake(point.x - 10, point.y)],
                            [NSValue valueWithCGPoint:CGPointMake(point.x + 10, point.y)],
                            [NSValue valueWithCGPoint:CGPointMake(point.x - 10, point.y)],
                            [NSValue valueWithCGPoint:CGPointMake(point.x + 10, point.y)],
                            [NSValue valueWithCGPoint:point]];
        
        // timingFunction意思是动画执行的效果,kCAMediaTimingFunctionEaseInEaseOut表示淡入淡出
        keyFrame.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        keyFrame.duration = 0.5f;
        [self.LoginButton.layer addAnimation:keyFrame forKey:keyFrame.keyPath];
    }
    
    /** 点击退回键盘 */
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [self.view endEditing:YES];
    }
    
    
    #pragma mark UIViewControllerTransitioningDelegate(转场动画代理)
    
    - (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
        self.loginTranslation.doLogin = NO;
        return self.loginTranslation;
    }
    
    - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
        self.loginTranslation.doLogin = YES;
        
        // 需要返回一个遵守了这个代理的对象,需要新建一个类遵守这个代理,实现两个代理方法。
        return self.loginTranslation;
    }
    
    /** 移除并置空所有控件,重新生成控件,对于防止内存泄漏有好处 */
    - (void)reloadView {
        int i = [[NSString stringWithFormat:@"%lu",(self.view.subviews.count-1)] intValue];
        for (; i >= 0; i--)
        {
            UIView *subView = self.view.subviews[i];
            [subView removeFromSuperview];
            subView = nil;
        }
        self.LoginImage = nil;
        self.LoginWord = nil;
        self.GetButton = nil;
        self.LoginButton = nil;
        self.HUDView = nil;
        self.LoginAnimView = nil;
        self.shapeLayer = nil;
        self.animView = nil;
        self.userTextField = nil;
        self.passwordTextField = nil;
        
        [self SetupUIComponent];
    }
    
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    
    
    @end
    LoginTranslation.h
    
    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    
    @interface LoginTranslation : NSObject <UIViewControllerAnimatedTransitioning>
    /** 登录或注销 YES:登录 */
    @property (nonatomic,assign) BOOL doLogin;
    @end
    LoginTranslation.m
    
    #import "LoginTranslation.h"
    #import "LoginViewController.h"
    #import "ViewController.h"
    #import "POP.h"
    #import "UIView+YYExtension.h"
    
    #define ScreenW [UIScreen mainScreen].bounds.size.width
    #define ScreenH [UIScreen mainScreen].bounds.size.height
    
    @interface LoginTranslation () <CAAnimationDelegate>
    // 做弧线运动的那个圆
    @property (strong, nonatomic) UIView * circularAnimView;
    @end
    
    @implementation LoginTranslation
    
    // 转场时间
    - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
        return 1.0;
    }
    
    // 转场动画
    - (void)animateTransition:(id)transitionContext {
        if (self.doLogin)// 登录转场动画
        {
            // transitionContext:转场上下文
            // 转场过程中显示的view,所有动画控件都应该加在这上面
            __block UIView * containerView = [transitionContext containerView];
            
            // 转场去的控制器
            ViewController * toVC = (ViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
            // 转场来的控制器
            LoginViewController * fromVC = (LoginViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
            
            // 1、fromVC背景变白 (fromVC.view默认已经加到了containerView中,所以不用再添加)
            [UIView animateWithDuration:0.15 animations:^{
                fromVC.view.backgroundColor = [UIColor whiteColor];
            }completion:^(BOOL finished) {
                [fromVC.view removeFromSuperview];
            }];
            
            // 2、账号密码输入框消失
            [containerView addSubview:fromVC.userTextField];
            [containerView addSubview:fromVC.passwordTextField];
            
            [UIView animateWithDuration:0.1 animations:^{
                fromVC.userTextField.alpha = 0.0;
                fromVC.passwordTextField.alpha = 0.0;
            }];
            
            // 3、logo图片移动消失
            [containerView addSubview:fromVC.LoginImage];
            
            [UIView animateWithDuration:0.15 delay:0.15 options:UIViewAnimationOptionCurveEaseInOut animations:^{
                fromVC.LoginImage.alpha = 0.0;
            } completion:^(BOOL finished) {
                
            }];
            
            // 4、logo文字缩小、移动
            [containerView addSubview:fromVC.LoginWord];
            
            CGFloat proportion = toVC.navWord.yy_width / fromVC.LoginWord.yy_width;
            CABasicAnimation * LoginWordScale = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
            LoginWordScale.fromValue = [NSNumber numberWithFloat:1.0];
            LoginWordScale.toValue = [NSNumber numberWithFloat:proportion];
            LoginWordScale.duration = 0.4;
            LoginWordScale.beginTime = CACurrentMediaTime()+0.15;
            LoginWordScale.removedOnCompletion = NO;
            LoginWordScale.fillMode = kCAFillModeForwards;
            [fromVC.LoginWord.layer addAnimation:LoginWordScale forKey:LoginWordScale.keyPath];
            
            CGPoint newPosition = [toVC.view convertPoint:toVC.navWord.center fromView:toVC.navView];
            
            [UIView animateWithDuration:0.4 delay:0.15 options:UIViewAnimationOptionCurveEaseInOut animations:^{
                fromVC.LoginWord.yy_centerX = newPosition.x;
                fromVC.LoginWord.yy_centerY = newPosition.y;
            } completion:^(BOOL finished) {
                
            }];
            
            // 5、圆(登录加载的那个圆)的移动,因为登录页面的那个圆有正在动的sublayer,所以这里新建了个圆来做动画
            UIView * circularAnimView = [[UIView alloc] initWithFrame:fromVC.LoginAnimView.frame];
            self.circularAnimView = circularAnimView;
            circularAnimView.layer.cornerRadius = circularAnimView.yy_width * 0.5;
            circularAnimView.layer.masksToBounds = YES;
            circularAnimView.frame = fromVC.LoginAnimView.frame;
            circularAnimView.backgroundColor = fromVC.LoginAnimView.backgroundColor;
            self.circularAnimView = circularAnimView;
            [containerView addSubview:circularAnimView];
            [fromVC.LoginAnimView removeFromSuperview];
            
            CGFloat bntSize = 44;
            fromVC.LoginAnimView.layer.cornerRadius = bntSize * 0.5;
            CGFloat originalX = toVC.view.yy_width - bntSize - 15;
            CGFloat originalY = toVC.view.yy_height - bntSize - 15 - 49;
            
            // CGContextRef,CGPath和UIBezierPath。本质上都是一样的,都是使用Quartz来绘画。只不过把绘图操作暴露在不同的API层面上,在具体实现上,当然也会有一些细小的差别。
            CGMutablePathRef path = CGPathCreateMutable();
            CGPathMoveToPoint(path, NULL, (circularAnimView.yy_x + circularAnimView.yy_width * 0.5), (circularAnimView.yy_y + circularAnimView.yy_height * 0.5));
            CGPathAddQuadCurveToPoint(path, NULL, ScreenW * 0.9, circularAnimView.yy_y + circularAnimView.yy_height, (originalX + circularAnimView.yy_width * 0.5), (originalY + circularAnimView.yy_height * 0.5));
            
            CAKeyframeAnimation * animate = [CAKeyframeAnimation animationWithKeyPath:@"position"];
            animate.delegate = self;
            animate.duration = 0.4;
            animate.beginTime = CACurrentMediaTime()+0.15;
            animate.fillMode = kCAFillModeForwards;
            animate.repeatCount = 0;
            animate.path = path;
            animate.removedOnCompletion = NO;
            CGPathRelease(path);
            [circularAnimView.layer addAnimation:animate forKey:@"circleMoveAnimation"];
            
            // 导航栏出现
            UIView * navView = [[UIView alloc] init];
            navView.frame = toVC.navView.frame;
            navView.backgroundColor = toVC.navView.backgroundColor;
            [containerView insertSubview:navView atIndex:1];
            navView.alpha = 0.0;
            [UIView animateWithDuration:0.6 delay:0.15 options:UIViewAnimationOptionCurveEaseInOut animations:^{
                navView.alpha = 1.0;
            } completion:^(BOOL finished) {
                
            }];
            
            // 背景出现、移动
            UIImageView * backImage = [[UIImageView alloc] init];
            backImage.image = toVC.backImage.image;
            backImage.frame = toVC.backImage.frame;
            [containerView insertSubview:backImage atIndex:1];
            backImage.alpha = 0.0;
            backImage.yy_y += 100;
            
            POPSpringAnimation * backImageMove = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter];
            backImageMove.fromValue = [NSValue valueWithCGRect:CGRectMake(backImage.yy_centerX, backImage.yy_centerY, backImage.yy_width, toVC.backImage.yy_height)];
            backImageMove.toValue = [NSValue valueWithCGRect:CGRectMake(backImage.yy_centerX, backImage.yy_centerY-100, backImage.yy_width, backImage.yy_height)];
            backImageMove.beginTime = CACurrentMediaTime()+0.15+0.2;
            backImageMove.springBounciness = 5.0;
            backImageMove.springSpeed = 10.0;
            [backImage pop_addAnimation:backImageMove forKey:nil];
            
            [UIView animateWithDuration:0.6 delay:0.15 + 0.2 options:UIViewAnimationOptionCurveEaseInOut animations:^{
                backImage.alpha = 1.0;
            } completion:^(BOOL finished) {
                [self cleanContainerView:containerView];   // 移除所有子控件
                [containerView addSubview:toVC.view];      // 将目标控制器的vc添加上去
                [transitionContext completeTransition:YES];// 标志转场结束
                containerView = nil;
                [fromVC reloadView]; // 登录界面重载UI
            }];
        }
        else // 退出登录转场动画
        {
            // transitionContext:转场上下文
            // 转场过程中显示的view,所有动画控件都应该加在这上面
            UIView * containerView = [transitionContext containerView];
            // 转场的来源控制器
            LoginViewController * toVC = (LoginViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
            // 转场去往的控制器
            ViewController * fromVC = (ViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
            
            // 做一个淡入淡出的效果
            toVC.view.alpha = 0;
            [containerView addSubview:toVC.view];
            [UIView animateWithDuration:1.0 animations:^{
                fromVC.view.alpha = 0;
            } completion:^(BOOL finished) {
                
            }];
            [UIView animateWithDuration:0.6 delay:0.4 options:UIViewAnimationOptionCurveEaseInOut animations:^{
                toVC.view.alpha = 1;
            } completion:^(BOOL finished) {
                [transitionContext completeTransition:YES];
            }];
        }
    }
    
    /** 移除containerView的子控件 */
    - (void)cleanContainerView:(UIView *)containerView {
        int i = [[NSString stringWithFormat:@"%lu",(containerView.subviews.count-1)] intValue];
        for (; i >= 0; i--) {
            UIView * subView = containerView.subviews[i];
            [subView removeFromSuperview];
        }
    }
    
    
    /** 核心动画动画代理 */
    - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
        if ([self.circularAnimView.layer animationForKey:@"circleMoveAnimation"] == anim) {
            /** 这里是在做加号按钮内部的白色加号的伸展开的效果 */
            //画线
            CGRect rect = self.circularAnimView.frame;
            CGPoint centerPoint = CGPointMake(rect.size.width*0.5, rect.size.height*0.5);
            
            //贝瑟尔线
            UIBezierPath *path1 = [UIBezierPath bezierPath];
            [path1 moveToPoint:centerPoint];
            [path1 addLineToPoint:CGPointMake(rect.size.width*0.5, rect.size.height*0.25)];
            UIBezierPath *path2 = [UIBezierPath bezierPath];
            [path2 moveToPoint:centerPoint];
            [path2 addLineToPoint:CGPointMake(rect.size.width*0.25, rect.size.height*0.5)];
            UIBezierPath *path3 = [UIBezierPath bezierPath];
            [path3 moveToPoint:centerPoint];
            [path3 addLineToPoint:CGPointMake(rect.size.width*0.5, rect.size.height*0.75)];
            UIBezierPath *path4 = [UIBezierPath bezierPath];
            [path4 moveToPoint:centerPoint];
            [path4 addLineToPoint:CGPointMake(rect.size.width*0.75, rect.size.height*0.5)];
            
            //ShapeLayer
            CAShapeLayer *shape1 = [self makeShapeLayerWithPath:path1 lineWidth:rect.size.width*0.07];
            [self.circularAnimView.layer addSublayer:shape1];
            CAShapeLayer *shape2 = [self makeShapeLayerWithPath:path2 lineWidth:rect.size.width*0.07];
            [self.circularAnimView.layer addSublayer:shape2];
            CAShapeLayer *shape3 = [self makeShapeLayerWithPath:path3 lineWidth:rect.size.width*0.07];
            [self.circularAnimView.layer addSublayer:shape3];
            CAShapeLayer *shape4 = [self makeShapeLayerWithPath:path4 lineWidth:rect.size.width*0.07];
            [self.circularAnimView.layer addSublayer:shape4];
            
            //动画
            CABasicAnimation *checkAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
            checkAnimation.duration = 0.25f;
            checkAnimation.fromValue = @(0.0f);
            checkAnimation.toValue = @(1.0f);
            checkAnimation.delegate = self;
            
            [shape1 addAnimation:checkAnimation forKey:@"checkAnimation"];
            [shape2 addAnimation:checkAnimation forKey:@"checkAnimation"];
            [shape3 addAnimation:checkAnimation forKey:@"checkAnimation"];
            [shape4 addAnimation:checkAnimation forKey:@"checkAnimation"];
        }
    }
    
    - (CAShapeLayer *)makeShapeLayerWithPath:(UIBezierPath *)path lineWidth:(CGFloat)lineWidth {
        CAShapeLayer * shape=[CAShapeLayer layer];
        shape.lineWidth = lineWidth;
        shape.fillColor = [UIColor clearColor].CGColor;
        shape.strokeColor = [UIColor whiteColor].CGColor;
        shape.lineCap = kCALineCapRound;
        shape.lineJoin = kCALineJoinRound;
        shape.path = path.CGPath;
        
        return shape;
    }
    
    @end
    ViewController.h
    
    #import <UIKit/UIKit.h>
    #import "AddView.h"
    
    @interface ViewController : UIViewController
    
    @property (nonatomic,strong) UIView  * navView; //导航栏
    @property (nonatomic,strong) UILabel * navWord; //导航栏上面的文字
    @property (nonatomic,strong) AddView * addView; //加号按钮
    @property (nonatomic,strong) UIImageView * backImage; //背景
    
    @end
    ViewController.m
    
    /*
      如何生成一个动画让控件执行
      现流行的方式主要有三种:
      1、基本动画
      2、核心动画
      3、三方框架——POP框架(由Facebook开发)
     
     1、控件的位置、大小等是不是真的发生了改变:
     基本动画、pop动画:是给控件添加动画(一般也不会有用基本动画给layer添加动画的做法),所有动画完成时,控件的属性已经改变;
     核心动画:是给控件的图层(view.layer)添加动画,看似发生了位置大小的变化,实际上控件本身的属性并未改变。
     
     基本动画
     优势:代码简单,代码量少
     劣势:功能相对单一
     
     核心动画
     优势:功能强大、流畅性好、连续几个动画之间的衔接度好。流畅主要是因为操作layer是轻量级的,不容易产生动画卡顿的感觉。
     劣势:代码量大;容易写错(某些参数没有定义宏,写错了都不知道);如有需要,还要手动在动画完成时将控件的属性同步修改了。
     
     pop动画
     优势:比核心动画代码要简单,最大的优势在于,容易做弹簧效果,所以很多有“Q弹”感觉的都用pop动画做
     劣势:要在一个动画完成时开始另一个动画,pop动画不擅长,主要因为它的动画执行时间由"速度"和"弹性系数"两个参数控制,不好直观判断动画执行了多久,而如果在pop动画完成回调的block里提交下一个动画,会不连贯(亲测,原因不详)。
     
     
     1、点击了GET按钮,logo图和logo文字上移
     移动属于比较简单的操作,但这个移动效果具有弹簧效果,所以可以采用核心动画中的关键帧动画CAKeyframeAnimation,或者pop动画来实现,这里我用了pop,后面登录失败按钮左右摆动的动画,我用了CAKeyframeAnimation。
     
     2、get按钮的变化
     get按钮分别进行了变宽、变宽的同时圆角变小,然后变高,然后向上移动,整个过程颜色由初始颜色变白。由于这是N个动画,有同时执行的,有接着上一步执行的,所以我选择核心动画CABasicAnimation,更容易控制每个动画的执行时间、开始时间,容易衔接得流畅。
     
     3、点击LOGIN,按钮转圈
     点击了LOGIN,按钮先从宽变圆,然后给按钮添加一条半圆的白色圆弧线,然后让这个按钮开始旋转。
     
     4、登录失败按钮抖动
     这个效果跟pop动画移动后抖动的效果很类似,这里我选择用关键帧动画CAKeyframeAnimation做,它与CABasicAnimation略有不同,CABasicAnimation是从一个值到另一个值,CAKeyframeAnimation是值变化的数组。
     
     1、LOGO图逐渐消失;
     
     2、LOGO文字逐渐变小、上移至B中头部文字的位置;
     
     3、A控制器的登录框消失、A控制器背景颜色变白;
     
     4、转圈控件经过弧线运动到右下角,白色加号逐渐形成
     
     5、B控制器背景图上移的动画。
     */
    
    #import "ViewController.h"
    #import "UIView+YYExtension.h"
    #import "AddView.h"
    
    
    #define ScreenW [UIScreen mainScreen].bounds.size.width
    #define ScreenH [UIScreen mainScreen].bounds.size.height
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    #pragma mark - 懒加载
    
    - (UIImageView *)backImage {
        if (!_backImage) {
            _backImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"backImg.jpg"]];
            [self.view addSubview:_backImage];
            _backImage.frame = CGRectMake(0, 0, ScreenW, ScreenH);
        }
        return _backImage;
    }
    
    - (UIView *)navView {
        if (!_navView) {
            _navView = [[UIView alloc] init];
            [self.view addSubview:_navView];
            _navView.backgroundColor = [UIColor whiteColor];
            _navView.frame = CGRectMake(0, 0, ScreenW, 64);
        }
        return _navView;
    }
    
    - (UILabel *)navWord {
        if (!_navWord) {
            _navWord = [[UILabel alloc] init];
            _navWord.font =  [UIFont fontWithName:@"TimesNewRomanPS-ItalicMT" size:24.0f];
            _navWord.textColor = [UIColor blackColor];
            _navWord.text = @"YY Anim Demo";
            _navWord.hidden = NO;
            [_navWord sizeToFit];
        }
        return _navWord;
    }
    
    - (AddView *)addView
    {
        if (!_addView) {
            CGFloat bntSize = 44;
            _addView = [[AddView alloc] initWithFrame:CGRectMake(0, 0, bntSize, bntSize)];
            [self.view addSubview:_addView];
            _addView.userInteractionEnabled = YES;
            [_addView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(addViewClick)]];
            _addView.frame = CGRectMake(ScreenW - 15 - bntSize, ScreenH - 15 - 49 - bntSize, bntSize, bntSize);
        }
        return _addView;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self setupUIComponent];
    }
    
    /** 初始化UI */
    - (void)setupUIComponent {
        self.backImage.hidden = NO;
        self.navView.hidden = NO;
        self.addView.hidden = NO;
        
        [self.navView addSubview:self.navWord];
        self.navWord.yy_centerX = self.navView.yy_centerX;
        self.navWord.yy_centerY = self.navView.yy_centerY + 10;
    }
    
    /** 点击加号按钮 */
    - (void)addViewClick {
        //退回登录页面
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    
    @end
    AddView.m
    
    #import "AddView.h"
    
    #define MAIN_COLOR [UIColor colorWithRed:156/255.0 green:197/255.0 blue:251/255.0 alpha:1.0]
    
    @implementation AddView
    
    - (instancetype)initWithFrame:(CGRect)frame
    {
        if (self = [super initWithFrame:frame])
        {
            self.backgroundColor = [UIColor clearColor];
        }
        return self;
    }
    
    - (void)drawRect:(CGRect)rect {
        CGPoint center = CGPointMake(rect.size.width * 0.5,rect.size.height * 0.5);
        UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:center radius:(rect.size.width * 0.5 - rect.size.width * 0.03) startAngle:0 endAngle:M_PI*2 clockwise:YES];
        
        [MAIN_COLOR set];
        
        // 填充:必须是一个完整的封闭路径,默认就会自动关闭路径
        [path fill];
        
        UIBezierPath * path1 = [UIBezierPath bezierPath];
        path1.lineWidth = rect.size.width * 0.07;
        [[UIColor whiteColor] set];
        // 设置起点
        [path1 moveToPoint:CGPointMake(rect.size.width * 0.25, rect.size.height * 0.5)];
        
        // 添加一根线到某个点
        [path1 addLineToPoint:CGPointMake(rect.size.width * 0.75, rect.size.height * 0.5)];
        
        // 绘制路径
        UIBezierPath * path2 = [UIBezierPath bezierPath];
        path2.lineWidth = rect.size.width * 0.07;
        [[UIColor whiteColor] set];
        // 设置起点
        [path2 moveToPoint:CGPointMake(rect.size.width * 0.5, rect.size.height * 0.25)];
        
        // 添加一根线到某个点
        [path2 addLineToPoint:CGPointMake(rect.size.width * 0.5, rect.size.height * 0.75)];
        
        // 绘制路径
        //    [path2 stroke];
        [self.layer addSublayer:[self makeShapeLayerWithPath:path1 lineWidth:path1.lineWidth]];
        [self.layer addSublayer:[self makeShapeLayerWithPath:path2 lineWidth:path2.lineWidth]];
        
        self.layer.shadowColor = MAIN_COLOR.CGColor;
        //阴影的透明度
        self.layer.shadowOpacity = 0.5f;
        //阴影的圆角
        self.layer.shadowRadius = 4.0f;
        //阴影偏移量
        self.layer.shadowOffset = CGSizeMake(0,0);
    }
    
    - (CAShapeLayer *)makeShapeLayerWithPath:(UIBezierPath *)path lineWidth:(CGFloat)lineWidth {
        CAShapeLayer * shape = [CAShapeLayer layer];
        shape.lineWidth = lineWidth;
        shape.fillColor = [UIColor clearColor].CGColor;
        shape.strokeColor = [UIColor whiteColor].CGColor;
        shape.lineCap = kCALineCapRound;
        shape.lineJoin = kCALineJoinRound;
        shape.path = path.CGPath;
        return shape;
    }
    
    @end
  • 相关阅读:
    Core Animation简介
    objective-c 常用函数、变量
    NSString判断纯数字
    自定义对话框AlterView
    IOS 6 自动布局 入门-1(IOS中autolayout和之前版本autoresize的差异)
    真机调试问题 错误集合
    block使用小结、在arc中使用block、如何防止循环引用
    View和viewController的生命周期
    IOS侧滑和webview
    Linker Error、MRC与ARC、导航条背景
  • 原文地址:https://www.cnblogs.com/fengmin/p/8176310.html
Copyright © 2020-2023  润新知