iOS学习笔记iOS技术点自己尝试等装一下

iOS开发——登录页面动画、转场动画

2017-08-03  本文已影响1587人  YY程序猿

需求

如下图。DEMO下载地址:https://github.com/YYProgrammer/YYLoginTranslationDemo

动画效果

需求分析

技术点分析

登录页分解、实现

//图片移动动画
POPSpringAnimation *anim4 = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];//kPOPViewFrame表示改变的值是frame
//动画开始的值(.yy_x是我写的分类的语法,等同于.frame.origin.x,其它同理)
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 = YYSpringBounciness;//YYSpringBounciness是我定义的静态变量,值是16.0
//速度
anim4.springSpeed = YYSpringSpeed;//YYSpringSpeed是我定义的静态变量,值是6.0
//加到控件上执行
[self.LoginImage pop_addAnimation:anim4 forKey:nil];

//文字移动动画
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 = YYSpringBounciness;
anim5.springSpeed = YYSpringSpeed;
[self.LoginWord pop_addAnimation:anim5 forKey:nil];
//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();
//以下两个参数,是为了动画完成后,控件的样子不回到动画前的样子
//因为上文中提到过,核心动画是给layer做动画,控件本身的属性不会变
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 = @(YYScreenW*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 = @(YYScreenH*0.3);
anim2.duration = 0.1;
anim2.beginTime = CACurrentMediaTime()+0.1;
anim2.fillMode = kCAFillModeForwards;
anim2.removedOnCompletion = false;
[animView.layer addAnimation:anim2 forKey:anim2.keyPath];

//get按钮移动动画
//这里的移动跟logo的移动是同步的,所以用pop
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 = YYSpringBounciness;
anim3.springSpeed = YYSpringSpeed;
[animView pop_addAnimation:anim3 forKey:nil];

2、输入框出现、LOGIN按钮出现
思路:
输入框是透明度的改变,LOGIN按钮是大小的改变。
代码:

//账号密码输入框出现
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) {
    
}];
//login按钮出现动画
self.LoginButton.yy_centerX = YYScreenW*0.5;
self.LoginButton.yy_centerY = YYScreenH*0.7+44+(YYScreenH*0.3-44)*0.5-75;
CABasicAnimation *animLoginBtn = [CABasicAnimation animationWithKeyPath:@"bounds.size"];
animLoginBtn.fromValue = [NSValue valueWithCGSize:CGSizeMake(0, 0)];
animLoginBtn.toValue = [NSValue valueWithCGSize:CGSizeMake(YYScreenW*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];

/** 动画执行结束回调 */
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    if ([((CABasicAnimation *)anim).keyPath isEqualToString:@"bounds.size"])
    {
        self.LoginButton.bounds = CGRectMake(YYScreenW*0.5, YYScreenH*0.7+44+(YYScreenH*0.3-44)*0.5-75, YYScreenW*0.5, 44);
    }
}
//执行登录按钮转圈动画的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) {
    
    //给圆加一条不封闭的白色曲线
    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 = [[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);
    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];
}];
//给按钮添加左右摆动的效果(关键帧动画)
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意思是动画执行的效果(这个属性玩HTML+CSS的童鞋应该很熟悉吧)
//kCAMediaTimingFunctionEaseInEaseOut表示淡入淡出
keyFrame.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
keyFrame.duration = 0.5f;
[self.LoginButton.layer addAnimation:keyFrame forKey:keyFrame.keyPath];

转场动画的原理和实现方法

上文说到,从A跳向B,需要一个中间载体来做动画,那么怎么得到这个载体呢?
需要用到转场代理transitioningDelegate。

    YYFirstViewController *vc = [[YYFirstViewController alloc] init];
    vc.transitioningDelegate = self;//也就是这里
    [self presentViewController:vc animated:YES completion:nil];

2、A控制器遵守代理,实现代理方法

//遵守代理
@interface YYLoginViewController () <UIViewControllerTransitioningDelegate>

#pragma mark UIViewControllerTransitioningDelegate(转场动画代理)
//这个是B回到A时执行的方法
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    //暂时别纠结YYLoginTranslation是什么,看下文
    YYLoginTranslation *loginTranslation = [[YYLoginTranslation alloc] init];
    return loginTranslation;
}
//这个是A跳到B时执行的方法
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
    YYLoginTranslation *loginTranslation = [[YYLoginTranslation alloc] init];
    return loginTranslation;
}

3、显而易见,上述两个方法需要返回一个遵守了<UIViewControllerAnimatedTransitioning>这个代理的对象,所以,现在需要新建一个类遵守这个代理,实现两个代理方法

//类的.h文件
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface YYLoginTranslation : NSObject <UIViewControllerAnimatedTransitioning>
@end

//类的.m文件
#import "YYLoginTranslation.h"

@interface YYLoginTranslation () <CAAnimationDelegate>

@end

@implementation YYLoginTranslation
//代码方法-转场时长
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
    return 1.0;
}

//代理方法-转场动画的代码
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    //transitionContext:转场上下文
    
    //转场过程中显示的view,所有动画控件都应该加在这上面
    //这就是那个所谓的载体
    UIView* containerView = [transitionContext containerView];
    
    //在这里把要做动画效果的控件往containerView上面加
    //开始开心的做动画
    
    //最后,在动画完成的时候,记得标识转场结束
    [transitionContext completeTransition:YES];
}

4、现在回头看第2步,那个返回的对象,就是我们第三步创建的类的对象。从A跳到B开始时,会先来到第2步中的"这个是A跳到B时执行的方法",根据你返回的对象,去对象中找代理方法,执行里面的代码,也就是第三步中的"代理方法-转场动画的代码"这个方法,这里代码执行结束后,控制器跳转也就完成了。

转场动画分解、实现

转场动图.gif
转场图.png

思路:
如上图AB控制器本来的样子是这样,转场动画需要完成一下操作:
1、LOGO图逐渐消失;
2、LOGO文字逐渐变小、上移至B中头部文字的位置;
3、A控制器的登录框消失、A控制器背景颜色变白;
4、转圈控件经过弧线运动到右下角,白色加号逐渐形成
5、B控制器背景图上移的动画。
下面分析下第4步和第2步的做法。

//设定曲线
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, YYScreenW*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"];

生成曲线的原理:
设置开始的点、结束的点、拉力点,首先会从开始点指结束点形成一条直线,然后向拉力点弯曲,就好像,拉力点会“伸出一只手”,把线拉弯。

曲线原理示意.png
加号逐渐绘制的效果:
另一篇文章里说到了这种线逐渐绘制的动画,可以参考一下:http://www.jianshu.com/p/ea319c97f4c3
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) {
    
}];

退出登录动画

退出登录.gif

思路:
这个效果比较简单,但同时也比较实用。实现方式就是改变两个控制器view的透明度。
代码:

//transitionContext:转场上下文
//转场过程中显示的view,所有动画控件都应该加在这上面
UIView *containerView = [transitionContext containerView];
//转场的来源控制器
YYLoginViewController* toVC = (YYLoginViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//转场去往的控制器
YYFirstViewController* fromVC = (YYFirstViewController *)[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];
}];

备注

上一篇 下一篇

猜你喜欢

热点阅读