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 { /** * 動畫的思路: * 一、造一個view來執行動畫,看上去就像get按鈕自己在形變移動,實際上是這個view * 二、改變更畫view的背景顏色,變色的過程是整個動畫效果執行的過程 * 三、讓按鈕變寬 * 四、變寬完成後,變高 * 五、變高完成後,同步執行如下四步 * 5.0、讓帳號密碼按鈕出現 * 5.一、讓Login按鈕出現 * 5.二、移動這個view,帶彈簧效果 * 5.三、移動logo圖片,帶彈簧效果 * 5.四、移動logo文字,帶彈簧效果 */ // 一、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; // 二、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]; // 三、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]; // 四、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.一、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.二、按鈕移動動畫 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.三、圖片移動動畫 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.四、文字移動動畫 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]; // 一、fromVC背景變白 (fromVC.view默認已經加到了containerView中,因此不用再添加) [UIView animateWithDuration:0.15 animations:^{ fromVC.view.backgroundColor = [UIColor whiteColor]; }completion:^(BOOL finished) { [fromVC.view removeFromSuperview]; }]; // 二、帳號密碼輸入框消失 [containerView addSubview:fromVC.userTextField]; [containerView addSubview:fromVC.passwordTextField]; [UIView animateWithDuration:0.1 animations:^{ fromVC.userTextField.alpha = 0.0; fromVC.passwordTextField.alpha = 0.0; }]; // 三、logo圖片移動消失 [containerView addSubview:fromVC.LoginImage]; [UIView animateWithDuration:0.15 delay:0.15 options:UIViewAnimationOptionCurveEaseInOut animations:^{ fromVC.LoginImage.alpha = 0.0; } completion:^(BOOL finished) { }]; // 四、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) { }]; // 五、圓(登陸加載的那個圓)的移動,由於登陸頁面的那個圓有正在動的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 /* 如何生成一個動畫讓控件執行 現流行的方式主要有三種: 一、基本動畫 二、核心動畫 三、三方框架——POP框架(由Facebook開發) 一、控件的位置、大小等是否是真的發生了改變: 基本動畫、pop動畫:是給控件添加動畫(通常也不會有用基本動畫給layer添加動畫的作法),全部動畫完成時,控件的屬性已經改變; 核心動畫:是給控件的圖層(view.layer)添加動畫,看似發生了位置大小的變化,實際上控件自己的屬性並未改變。 基本動畫 優點:代碼簡單,代碼量少 劣勢:功能相對單一 核心動畫 優點:功能強大、流暢性好、連續幾個動畫之間的銜接度好。流暢主要是由於操做layer是輕量級的,不容易產生動畫卡頓的感受。 劣勢:代碼量大;容易寫錯(某些參數沒有定義宏,寫錯了都不知道);若有須要,還要手動在動畫完成時將控件的屬性同步修改了。 pop動畫 優點:比核心動畫代碼要簡單,最大的優點在於,容易作彈簧效果,因此不少有「Q彈」感受的都用pop動畫作 劣勢:要在一個動畫完成時開始另外一個動畫,pop動畫不擅長,主要由於它的動畫執行時間由"速度"和"彈性係數"兩個參數控制,很差直觀判斷動畫執行了多久,而若是在pop動畫完成回調的block裏提交下一個動畫,會不連貫(親測,緣由不詳)。 一、點擊了GET按鈕,logo圖和logo文字上移 移動屬於比較簡單的操做,但這個移動效果具備彈簧效果,因此能夠採用核心動畫中的關鍵幀動畫CAKeyframeAnimation,或者pop動畫來實現,這裏我用了pop,後面登陸失敗按鈕左右擺動的動畫,我用了CAKeyframeAnimation。 二、get按鈕的變化 get按鈕分別進行了變寬、變寬的同時圓角變小,而後變高,而後向上移動,整個過程顏色由初始顏色變白。因爲這是N個動畫,有同時執行的,有接着上一步執行的,因此我選擇核心動畫CABasicAnimation,更容易控制每一個動畫的執行時間、開始時間,容易銜接得流暢。 三、點擊LOGIN,按鈕轉圈 點擊了LOGIN,按鈕先從寬變圓,而後給按鈕添加一條半圓的白色圓弧線,而後讓這個按鈕開始旋轉。 四、登陸失敗按鈕抖動 這個效果跟pop動畫移動後抖動的效果很相似,這裏我選擇用關鍵幀動畫CAKeyframeAnimation作,它與CABasicAnimation略有不一樣,CABasicAnimation是從一個值到另外一個值,CAKeyframeAnimation是值變化的數組。 一、LOGO圖逐漸消失; 二、LOGO文字逐漸變小、上移至B中頭部文字的位置; 三、A控制器的登陸框消失、A控制器背景顏色變白; 四、轉圈控件通過弧線運動到右下角,白色加號逐漸造成 五、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