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