服务器之家

服务器之家 > 正文

IOS登录页面动画、转场动画开发详解

时间:2021-04-18 21:40     来源/作者:iOS开发网

IOS登录页面动画、转场动画开发详解

动画效果

需求分析

分析方法

下载这个gif动图,用mac默认的打开方式打开这个gif图(双击图片即可),效果如下

IOS登录页面动画、转场动画开发详解

鼠标选中红色箭头所指的位置,然后按住键盘方向键下键,图片会以缓慢的可控的速度播放,便于分析动画的构成。

小tips:macos系统想正常浏览一个gif动图,可以鼠标单击图片后按空格,也可以选择用浏览器打开,gif图会以正常速度播放。

技术点分析

如何生成一个动画让控件执行?

现流行的方式主要有三种:

1、基本动画

2、核心动画

3、三方框架——pop框架(由facebook开发)

它们的主要差别:

1、控件的位置、大小等是不是真的发生了改变?

基本动画、pop动画,是给控件添加动画(一般也不会有用基本动画给layer添加动画的做法),所有动画完成时,控件的属性已经改变,而核心动画,是给控件的图层(view.layer)添加动画,看似发生了位置大小的变化,实际上控件本身的属性并未改变。

2、它们分别的优劣势

2.1、基本动画

优势:代码简单,代码量少

劣势:功能相对单一

2.2、核心动画的优势

优势:功能强大、流畅性好、连续几个动画之间的衔接度好。流畅主要是因为操作layer是轻量级的,不容易产生动画卡顿的感觉。

劣势:代码量大;容易写错(某些参数没有定义宏,写错了都不知道);如有需要,还要手动在动画完成时将控件的属性同步修改了。

2.3、pop动画的优势

优势:比核心动画代码要简单,最大的优势在于,容易做弹簧效果,所以很多有“q弹”感觉的都用pop动画做

劣势:要在一个动画完成时开始另一个动画,pop动画不擅长,主要因为它的动画执行时间由"速度"和"弹性系数"两个参数控制,不好直观判断动画执行了多久,而如果在pop动画完成回调的block里提交下一个动画,会不连贯(亲测,原因不详)。

转场动画怎么实现?

明明从a控制器跳往b控制器,各是各的页面,各是各的控件,怎么做到a里的控件变化形成了b的控件的效果?

的确,a和b是两个独立的页面,它们跳转过程需要动画的效果时,需要另外一个呈现于屏幕上的载体(或者称页面)来装那些做动画的控件,然后在动画完成、转场结束时,把这个载体移除掉,宣告转场结束,这个时候把真正的b的页面展示出来。

这就需要转场代理transitioningdelegate发挥作用了,具体做法和原理下文详述。

登录页分解、实现

点击了get按钮,logo图和logo文字上移

IOS登录页面动画、转场动画开发详解

思路:

移动属于比较简单的操作,但这个移动效果具有弹簧效果,所以可以采用核心动画中的关键帧动画cakeyframeanimation,或者pop动画来实现,这里我用了pop,后面登录失败按钮左右摆动的动画,我用了cakeyframeanimation。

代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//图片移动动画
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按钮出现输入框

IOS登录页面动画、转场动画开发详解

1、get按钮的变化

思路:

get按钮分别进行了变宽、变宽的同时圆角变小,然后变高,然后向上移动,整个过程颜色由初始颜色变白。由于这是n个动画,有同时执行的,有接着上一步执行的,所以我选择核心动画cabasicanimation,更容易控制每个动画的执行时间、开始时间,容易衔接得流畅。

代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//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);
  }
}

点击login,按钮转圈

IOS登录页面动画、转场动画开发详解

思路:

点击了login,按钮先从宽变圆,然后给按钮添加一条半圆的白色圆弧线,然后让这个按钮开始旋转。

代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//执行登录按钮转圈动画的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];
}];

登录失败按钮抖动

IOS登录页面动画、转场动画开发详解

思路:

这个效果跟pop动画移动后抖动的效果很类似,这里我选择用关键帧动画cakeyframeanimation做,它与cabasicanimation略有不同,cabasicanimation是从一个值到另一个值,cakeyframeanimation是值变化的数组。

代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//给按钮添加左右摆动的效果(关键帧动画)
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。

具体做法、步骤:

1、从a控制器跳到b控制器,写跳转的代码时候,赋值代理

?
1
2
3
yyfirstviewcontroller *vc = [[yyfirstviewcontroller alloc] init];
vc.transitioningdelegate = self;//也就是这里
[self presentviewcontroller:vc animated:yes completion:nil];

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//遵守代理
@interface yyloginviewcontroller () #pragma mark uiviewcontrollertransitioningdelegate(转场动画代理)
//这个是b回到a时执行的方法
- (id)animationcontrollerfordismissedcontroller:(uiviewcontroller *)dismissed
{
  //暂时别纠结yylogintranslation是什么,看下文
  yylogintranslation *logintranslation = [[yylogintranslation alloc] init];
  return logintranslation;
}
//这个是a跳到b时执行的方法
- (id)animationcontrollerforpresentedcontroller:(uiviewcontroller *)presented presentingcontroller:(uiviewcontroller *)presenting sourcecontroller:(uiviewcontroller *)source
{
  yylogintranslation *logintranslation = [[yylogintranslation alloc] init];
  return logintranslation;
}

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//类的.h文件
#import #import @interface yylogintranslation : nsobject @end
//类的.m文件
#import "yylogintranslation.h"
@interface yylogintranslation () @end
@implementation yylogintranslation
//代码方法-转场时长
- (nstimeinterval)transitionduration:(id)transitioncontext
{
  return 1.0;
}
//代理方法-转场动画的代码
- (void)animatetransition:(id)transitioncontext
{
  //transitioncontext:转场上下文
   
  //转场过程中显示的view,所有动画控件都应该加在这上面
  //这就是那个所谓的载体
  uiview* containerview = [transitioncontext containerview];
   
  //在这里把要做动画效果的控件往containerview上面加
  //开始开心的做动画
   
  //最后,在动画完成的时候,记得标识转场结束
  [transitioncontext completetransition:yes];
}

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

转场动画分解、实现

IOS登录页面动画、转场动画开发详解

IOS登录页面动画、转场动画开发详解

思路:

如上图ab控制器本来的样子是这样,转场动画需要完成一下操作:

1、logo图逐渐消失;

2、logo文字逐渐变小、上移至b中头部文字的位置;

3、a控制器的登录框消失、a控制器背景颜色变白;

4、转圈控件经过弧线运动到右下角,白色加号逐渐形成

5、b控制器背景图上移的动画。

下面分析下第4步和第2步的做法。

圆形的弧线位移、加号的出现

IOS登录页面动画、转场动画开发详解

圆形的弧线位移、加号的出现.gif

思路:

先用设定一条曲线,然后让圆沿着曲线移动,最后把加号展示出来。

代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//设定曲线
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"];

生成曲线的原理:

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

IOS登录页面动画、转场动画开发详解

logo文字的缩小、移动

IOS登录页面动画、转场动画开发详解

思路:

这是一个uilabel,它的形变就不能靠改变frame实现了,因为如果你缩小它的宽度,当宽度不够装内容时,内容会显示不全,显示不下的会用...代替。所以缩小uilabel需要靠专门的形变属性。至于移动就好说了,只需要算准位置。

代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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) {
   
}];

退出登录动画

IOS登录页面动画、转场动画开发详解

思路:

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

代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//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];
}];

备注

pop框架的手动集成报错的问题

pop框架推荐使用pods集成,如果要手动集成的话,比较麻烦,由于处理这个问题的时间已经有点久了,集成的麻烦点记不全了,大概就是它框架里的所有头文件的import方式要从<>改成"",还有它好像有个.cpp文件,要把后缀改成.mm,还有什么记不住了。

如果需要手动集成pop框架,可以下这个demo,里面有手动集成的pop框架,直接把整个文件夹拖走即可。

demo下载地址:https://github.com/yyprogrammer/yylogintranslationdemo

以上就是本次小编为大家整理的全部内容,感谢你对服务器之家的支持。

相关文章

热门资讯

2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
yue是什么意思 网络流行语yue了是什么梗
yue是什么意思 网络流行语yue了是什么梗 2020-10-11
背刺什么意思 网络词语背刺是什么梗
背刺什么意思 网络词语背刺是什么梗 2020-05-22
Intellij idea2020永久破解,亲测可用!!!
Intellij idea2020永久破解,亲测可用!!! 2020-07-29
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总 2020-11-13
返回顶部