![]()
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