登陆动画

 

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

 

posted @ 2018-01-02 11:25  FMDN  阅读(491)  评论(0编辑  收藏  举报